Installing namespace packages with pip inserts strange modules into sys.modules
While trying to add support for PEP420 implicit namespace packages support to py2exe's modulefinder, I noticed something strange. I'm using the wheezy.template and zope.interface packages for my tests, they are installed with pip. pip creates wheezy.template-0.1.155-py3.4-nspkg.pth and zope.interface-4.1.1-py3.4-nspkg.pth files that are not present in the package sources. The contents is like this: """ import sys,types,os; p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('wheezy',)); ie = os.path.exists(os.path.join(p,'__init__.py')); m = not ie and sys.modules.setdefault('wheezy',types.ModuleType('wheezy')); mp = (m or []) and m.__dict__.setdefault('__path__',[]); (p not in mp) and mp.append(p) """ What is the purpose of these files? (The packages seem to work also when I delete these files; which is what I expected from implicit namespace packages...) These .pth-files have the effect that they insert the modules 'wheezy' and 'zope' into sys.modules, even when I don't import anything from the packages. However, these modules are IMO damaged, they only have __doc__, __name__, and __path__ attributes (plus __loader__ and __spec__ in Python 3.4, both set to None). Reloading the package in Python 3.3 raises an error, and importlib.find_loader("zope") also:
sys.modules["zope"]
import zope zope imp.reload(zope) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "C:\Python33\lib\imp.py", line 276, in reload module.__loader__.load_module(name) AttributeError: 'module' object has no attribute '__loader__'
importlib.find_loader("zope", None) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "C:\Python33\lib\importlib\__init__.py", line 64, in find_loader loader = sys.modules[name].__loader__ AttributeError: 'module' object has no attribute '__loader__'
Trying the same in Python 3.4, behaviour is quite similar although imp.reload() 'fixes' that package:
import sys sys.modules["zope"]
dir(sys.modules["zope"]) ['__doc__', '__loader__', '__name__', '__package__', '__path__', '__spec__'] sys.modules["zope"].__spec__ import zope zope
import importlib.util importlib.util.find_spec("zope", None) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "C:\Python34\lib\importlib\util.py", line 100, in find_spec raise ValueError('{}.__spec__ is None'.format(name)) ValueError: zope.__spec__ is None import imp imp.reload(zope) importlib.util.find_spec("zope", None) ModuleSpec(name='zope', loader=None, origin='namespace', submodule_search_locations=_NamespacePath(['C:\\Python34\\lib\\site-packages\\zope']))
Thanks for any insights, Thomas
On Fri, Sep 12, 2014 at 8:38 AM, Thomas Heller
While trying to add support for PEP420 implicit namespace packages support to py2exe's modulefinder, I noticed something strange.
I'm using the wheezy.template and zope.interface packages for my tests, they are installed with pip.
pip creates wheezy.template-0.1.155-py3.4-nspkg.pth and zope.interface-4.1.1-py3.4-nspkg.pth files that are not present in the package sources. The contents is like this:
""" import sys,types,os; p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('wheezy',)); ie = os.path.exists(os.path.join(p,'__init__.py')); m = not ie and sys.modules.setdefault('wheezy',types.ModuleType('wheezy')); mp = (m or []) and m.__dict__.setdefault('__path__',[]); (p not in mp) and mp.append(p) """
What is the purpose of these files? (The packages seem to work also when I delete these files; which is what I expected from implicit namespace packages...)
These .pth-files have the effect that they insert the modules 'wheezy' and 'zope' into sys.modules, even when I don't import anything from the packages. However, these modules are IMO damaged, they only have __doc__, __name__, and __path__ attributes (plus __loader__ and __spec__ in Python 3.4, both set to None).
Reloading the package in Python 3.3 raises an error, and importlib.find_loader("zope") also:
sys.modules["zope"]
import zope zope imp.reload(zope) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "C:\Python33\lib\imp.py", line 276, in reload module.__loader__.load_module(name) AttributeError: 'module' object has no attribute '__loader__'
importlib.find_loader("zope", None) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "C:\Python33\lib\importlib\__init__.py", line 64, in find_loader loader = sys.modules[name].__loader__ AttributeError: 'module' object has no attribute '__loader__'
Trying the same in Python 3.4, behaviour is quite similar although imp.reload() 'fixes' that package:
import sys sys.modules["zope"]
dir(sys.modules["zope"]) ['__doc__', '__loader__', '__name__', '__package__', '__path__', '__spec__'] sys.modules["zope"].__spec__ import zope zope
import importlib.util importlib.util.find_spec("zope", None) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "C:\Python34\lib\importlib\util.py", line 100, in find_spec raise ValueError('{}.__spec__ is None'.format(name)) ValueError: zope.__spec__ is None import imp imp.reload(zope) importlib.util.find_spec("zope", None) ModuleSpec(name='zope', loader=None, origin='namespace', submodule_search_locations=_NamespacePath(['C:\\Python34\\lib\\site-packages\\zope']))
Hi Thomas, I've dealt with this issue myself extensively in the past, but it's been a while so I might have to give it a bit of thought before I comment more on detail (if no one else does first). But I don't think (unless there's something new I don't know about) those come directly from pip. Rather, they are created by setuptools. The issue here is that before Python had any built-in notion of namespace packages, namespace packages were a feature in setuptools, and this was the hack, I suppose, that allowed it to work. Unfortunately as different versions of Python have grown different levels of support for namespace packages over the years, the setuptools hack is still the implementation of the concept that will work on the broadest range of Python versions (and I definitely know zope.interface has been using it going far back). That said, I've been meaning to try to figure out some way of supporting all three forms of namespace packages in a way that is interfering. As I said, I've had problems with this myself :/ Erik
Am 12.09.2014 19:15, schrieb Erik Bray:
On Fri, Sep 12, 2014 at 8:38 AM, Thomas Heller
wrote:
[snip about strange .pth files installed for namespace packages]
Hi Thomas,
I've dealt with this issue myself extensively in the past, but it's been a while so I might have to give it a bit of thought before I comment more on detail (if no one else does first).
But I don't think (unless there's something new I don't know about) those come directly from pip. Rather, they are created by setuptools.
Thanks for the answer, Erik. I've lost understanding what is done by setuptools, disutils, wheels, pip, and whatever other software is working when building distributions or installing them.
The issue here is that before Python had any built-in notion of namespace packages, namespace packages were a feature in setuptools, and this was the hack, I suppose, that allowed it to work. Unfortunately as different versions of Python have grown different levels of support for namespace packages over the years, the setuptools hack is still the implementation of the concept that will work on the broadest range of Python versions (and I definitely know zope.interface has been using it going far back).
So it seems that it is a bug in setuptools: It must not create or install these pth files when installing in Python 3.3 or newer (which implement PEP 420).
That said, I've been meaning to try to figure out some way of supporting all three forms of namespace packages in a way that is interfering. As I said, I've had problems with this myself :/
Thomas
[Oops, replying to the list this time. Sorry for the dupe, Thomas.] On 9/12/2014 3:14 PM, Thomas Heller wrote:
So it seems that it is a bug in setuptools: It must not create or install these pth files when installing in Python 3.3 or newer (which implement PEP 420).
PEP 420 goes out of its way to support pkgutil.extend_path(): http://legacy.python.org/dev/peps/pep-0420/#migrating-from-legacy-namespace-... So it should be possible for some cross-version code to work. -- Eric.
Am 12.09.2014 21:24, schrieb Eric V. Smith:
[Oops, replying to the list this time. Sorry for the dupe, Thomas.]
On 9/12/2014 3:14 PM, Thomas Heller wrote:
So it seems that it is a bug in setuptools: It must not create or install these pth files when installing in Python 3.3 or newer (which implement PEP 420).
PEP 420 goes out of its way to support pkgutil.extend_path(): http://legacy.python.org/dev/peps/pep-0420/#migrating-from-legacy-namespace-...
So it should be possible for some cross-version code to work.
My point is that the source code for zope.interface (for example) has the magic code in the zope/__init__.py file, right beneath the zope/interface/ directory: """ try: import pkg_resources pkg_resources.declare_namespace(__name__) except ImportError: import pkgutil __path__ = pkgutil.extend_path(__path__, __name__) """ IMO this is to support the legacy way. See: https://github.com/zopefoundation/zope.interface/blob/master/src/zope/__init... However, when installing it in Python 3.4, this __init__.py file is NOT installed. Instead, the 'zope' directory ONLY contains the 'interface' subdirectory with all the code. But a .pth file is installed that does the strange things that I mentioned in the original post. Thomas
On Fri, Sep 12, 2014 at 3:34 PM, Thomas Heller
However, when installing it in Python 3.4, this __init__.py file is NOT installed. Instead, the 'zope' directory ONLY contains the 'interface' subdirectory with all the code. But a .pth file is installed that does the strange things that I mentioned in the original post.
The horrible .pth file in question definitely comes from setuptools. At Zope Corp., we consistently use zc.buildout to package applications, which drives setuptools with some long option whose name I forget that causes the .pth files to be suppressed, and entry-point-based scripts are generated with the right bits to assemble the sys.path needed for the application. For what it's worth. -Fred -- Fred L. Drake, Jr. <fred at fdrake.net> "A storm broke loose in my mind." --Albert Einstein
On Fri, Sep 12, 2014 at 3:24 PM, Eric V. Smith
[Oops, replying to the list this time. Sorry for the dupe, Thomas.]
On 9/12/2014 3:14 PM, Thomas Heller wrote:
So it seems that it is a bug in setuptools: It must not create or install these pth files when installing in Python 3.3 or newer (which implement PEP 420).
PEP 420 goes out of its way to support pkgutil.extend_path(): http://legacy.python.org/dev/peps/pep-0420/#migrating-from-legacy-namespace-...
So it should be possible for some cross-version code to work.
The pkgutil.extend_path() way can be made to work with PEP 420 reasonably well, but *not* with the older setuptools approach. Unfortunately there are some dark corners of setuptools I've encountered where namespace packages don't work properly during installation *unless* they were installed in the old-fashioned setuptools way. I'll have to see if I can dig up what those cases are, because they should be fixed. Erik
On Fri, Sep 12, 2014 at 3:55 PM, Erik Bray
Unfortunately there are some dark corners of setuptools I've encountered where namespace packages don't work properly during installation *unless* they were installed in the old-fashioned setuptools way. I'll have to see if I can dig up what those cases are, because they should be fixed.
I don't know if this is what you had in mind, but, the main problem I know of with changing setuptools to not install the .pth files, is that if you already have any __init__.py's installed (and possibly, any namespace .pth files to go with them), then the newly-installed package isn't going to work. PEP 420 only takes effect if there are no existing __init__.py files for the namespace. In other words, it's not just a matter of changing how things are installed, it's also a matter of upgrading existing environments with things installed by older installation tools.
participants (5)
-
Eric V. Smith
-
Erik Bray
-
Fred Drake
-
PJ Eby
-
Thomas Heller