summaryrefslogtreecommitdiff
path: root/pkg_resources
diff options
context:
space:
mode:
authorSteve Kowalik <steven@wedontsleep.org>2016-02-16 16:01:54 +1100
committerSteve Kowalik <steven@wedontsleep.org>2016-02-16 16:01:54 +1100
commit69175b941a74a4e2f37b856437b3ca20bc2f240a (patch)
treee0349b731b724c76963f9fd429f53b22cbf142d3 /pkg_resources
parent43d0308ad6a8c83be645b09e8c1871b36ff3c4c9 (diff)
parent8ccd428cd2a733891bffce13e017774ea82bd8d2 (diff)
downloadpython-setuptools-git-69175b941a74a4e2f37b856437b3ca20bc2f240a.tar.gz
Merge from master, resolving conflicts.
Diffstat (limited to 'pkg_resources')
-rw-r--r--pkg_resources/__init__.py82
-rw-r--r--pkg_resources/tests/test_pkg_resources.py2
-rw-r--r--pkg_resources/tests/test_resources.py125
3 files changed, 146 insertions, 63 deletions
diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
index 286c03ed..118cb63b 100644
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -46,7 +46,7 @@ except ImportError:
import imp as _imp
from pkg_resources.extern import six
-from pkg_resources.extern.six.moves import urllib
+from pkg_resources.extern.six.moves import urllib, map, filter
# capture these to bypass sandboxing
from os import utime
@@ -60,10 +60,11 @@ except ImportError:
from os import open as os_open
from os.path import isdir, split
-# Avoid try/except due to potential problems with delayed import mechanisms.
-if sys.version_info >= (3, 3) and sys.implementation.name == "cpython":
+try:
import importlib.machinery as importlib_machinery
-else:
+ # access attribute to force import under delayed import mechanisms.
+ importlib_machinery.__name__
+except ImportError:
importlib_machinery = None
try:
@@ -78,9 +79,6 @@ __import__('pkg_resources._vendor.packaging.requirements')
__import__('pkg_resources._vendor.packaging.markers')
-filter = six.moves.filter
-map = six.moves.map
-
if (3, 0) < sys.version_info < (3, 3):
msg = (
"Support for Python 3.0-3.2 has been dropped. Future versions "
@@ -757,7 +755,7 @@ class WorkingSet(object):
will be called.
"""
if insert:
- dist.insert_on(self.entries, entry)
+ dist.insert_on(self.entries, entry, replace=replace)
if entry is None:
entry = dist.location
@@ -1178,22 +1176,23 @@ class ResourceManager:
old_exc = sys.exc_info()[1]
cache_path = self.extraction_path or get_default_cache()
- err = ExtractionError("""Can't extract file(s) to egg cache
+ tmpl = textwrap.dedent("""
+ Can't extract file(s) to egg cache
-The following error occurred while trying to extract file(s) to the Python egg
-cache:
+ The following error occurred while trying to extract file(s) to the Python egg
+ cache:
- %s
+ {old_exc}
-The Python egg cache directory is currently set to:
+ The Python egg cache directory is currently set to:
- %s
+ {cache_path}
-Perhaps your account does not have write access to this directory? You can
-change the cache directory by setting the PYTHON_EGG_CACHE environment
-variable to point to an accessible directory.
-""" % (old_exc, cache_path)
- )
+ Perhaps your account does not have write access to this directory? You can
+ change the cache directory by setting the PYTHON_EGG_CACHE environment
+ variable to point to an accessible directory.
+ """).lstrip()
+ err = ExtractionError(tmpl.format(**locals()))
err.manager = self
err.cache_path = cache_path
err.original_error = old_exc
@@ -1561,10 +1560,13 @@ class DefaultProvider(EggProvider):
with open(path, 'rb') as stream:
return stream.read()
-register_loader_type(type(None), DefaultProvider)
+ @classmethod
+ def _register(cls):
+ loader_cls = getattr(importlib_machinery, 'SourceFileLoader',
+ type(None))
+ register_loader_type(loader_cls, cls)
-if importlib_machinery is not None:
- register_loader_type(importlib_machinery.SourceFileLoader, DefaultProvider)
+DefaultProvider._register()
class EmptyProvider(NullProvider):
@@ -1970,7 +1972,7 @@ def find_on_path(importer, path_item, only=False):
break
register_finder(pkgutil.ImpImporter, find_on_path)
-if importlib_machinery is not None:
+if hasattr(importlib_machinery, 'FileFinder'):
register_finder(importlib_machinery.FileFinder, find_on_path)
_declare_state('dict', _namespace_handlers={})
@@ -2016,11 +2018,28 @@ def _handle_ns(packageName, path_item):
path = module.__path__
path.append(subpath)
loader.load_module(packageName)
- for path_item in path:
- if path_item not in module.__path__:
- module.__path__.append(path_item)
+ _rebuild_mod_path(path, packageName, module)
return subpath
+
+def _rebuild_mod_path(orig_path, package_name, module):
+ """
+ Rebuild module.__path__ ensuring that all entries are ordered
+ corresponding to their sys.path order
+ """
+ sys_path = [_normalize_cached(p) for p in sys.path]
+ def position_in_sys_path(p):
+ """
+ Return the ordinal of the path based on its position in sys.path
+ """
+ parts = p.split(os.sep)
+ parts = parts[:-(package_name.count('.') + 1)]
+ return sys_path.index(_normalize_cached(os.sep.join(parts)))
+
+ orig_path.sort(key=position_in_sys_path)
+ module.__path__[:] = [_normalize_cached(p) for p in orig_path]
+
+
def declare_namespace(packageName):
"""Declare that package 'packageName' is a namespace package"""
@@ -2079,7 +2098,7 @@ def file_ns_handler(importer, path_item, packageName, module):
register_namespace_handler(pkgutil.ImpImporter, file_ns_handler)
register_namespace_handler(zipimport.zipimporter, file_ns_handler)
-if importlib_machinery is not None:
+if hasattr(importlib_machinery, 'FileFinder'):
register_namespace_handler(importlib_machinery.FileFinder, file_ns_handler)
@@ -2461,7 +2480,7 @@ class Distribution(object):
"""Ensure distribution is importable on `path` (default=sys.path)"""
if path is None:
path = sys.path
- self.insert_on(path)
+ self.insert_on(path, replace=True)
if path is sys.path:
fixup_namespace_packages(self.location)
for pkg in self._get_metadata('namespace_packages.txt'):
@@ -2538,7 +2557,7 @@ class Distribution(object):
"""Return the EntryPoint object for `group`+`name`, or ``None``"""
return self.get_entry_map(group).get(name)
- def insert_on(self, path, loc = None):
+ def insert_on(self, path, loc=None, replace=False):
"""Insert self.location in path before its nearest parent directory"""
loc = loc or self.location
@@ -2562,7 +2581,10 @@ class Distribution(object):
else:
if path is sys.path:
self.check_version_conflict()
- path.append(loc)
+ if replace:
+ path.insert(0, loc)
+ else:
+ path.append(loc)
return
# p is the spot where we found or inserted loc; now remove duplicates
diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py
index 31eee635..8b276ffc 100644
--- a/pkg_resources/tests/test_pkg_resources.py
+++ b/pkg_resources/tests/test_pkg_resources.py
@@ -12,6 +12,8 @@ import stat
import distutils.dist
import distutils.command.install_egg_info
+from pkg_resources.extern.six.moves import map
+
import pytest
import pkg_resources
diff --git a/pkg_resources/tests/test_resources.py b/pkg_resources/tests/test_resources.py
index 8066753b..909b29d3 100644
--- a/pkg_resources/tests/test_resources.py
+++ b/pkg_resources/tests/test_resources.py
@@ -1,9 +1,11 @@
+from __future__ import unicode_literals
+
import os
import sys
-import tempfile
-import shutil
import string
+from pkg_resources.extern.six.moves import map
+
import pytest
from pkg_resources.extern import packaging
@@ -158,7 +160,7 @@ class TestDistro:
for i in range(3):
targets = list(ws.resolve(parse_requirements("Foo"), ad))
assert targets == [Foo]
- list(map(ws.add,targets))
+ list(map(ws.add, targets))
with pytest.raises(VersionConflict):
ws.resolve(parse_requirements("Foo==0.9"), ad)
ws = WorkingSet([]) # reset
@@ -608,21 +610,44 @@ class TestParsing:
class TestNamespaces:
- def setup_method(self, method):
- self._ns_pkgs = pkg_resources._namespace_packages.copy()
- self._tmpdir = tempfile.mkdtemp(prefix="tests-setuptools-")
- os.makedirs(os.path.join(self._tmpdir, "site-pkgs"))
- self._prev_sys_path = sys.path[:]
- sys.path.append(os.path.join(self._tmpdir, "site-pkgs"))
-
- def teardown_method(self, method):
- shutil.rmtree(self._tmpdir)
- pkg_resources._namespace_packages = self._ns_pkgs.copy()
- sys.path = self._prev_sys_path[:]
-
- @pytest.mark.skipif(os.path.islink(tempfile.gettempdir()),
- reason="Test fails when /tmp is a symlink. See #231")
- def test_two_levels_deep(self):
+ ns_str = "__import__('pkg_resources').declare_namespace(__name__)\n"
+
+ @pytest.yield_fixture
+ def symlinked_tmpdir(self, tmpdir):
+ """
+ Where available, return the tempdir as a symlink,
+ which as revealed in #231 is more fragile than
+ a natural tempdir.
+ """
+ if not hasattr(os, 'symlink'):
+ yield str(tmpdir)
+ return
+
+ link_name = str(tmpdir) + '-linked'
+ os.symlink(str(tmpdir), link_name)
+ try:
+ yield type(tmpdir)(link_name)
+ finally:
+ os.unlink(link_name)
+
+ @pytest.yield_fixture(autouse=True)
+ def patched_path(self, tmpdir):
+ """
+ Patch sys.path to include the 'site-pkgs' dir. Also
+ restore pkg_resources._namespace_packages to its
+ former state.
+ """
+ saved_ns_pkgs = pkg_resources._namespace_packages.copy()
+ saved_sys_path = sys.path[:]
+ site_pkgs = tmpdir.mkdir('site-pkgs')
+ sys.path.append(str(site_pkgs))
+ try:
+ yield
+ finally:
+ pkg_resources._namespace_packages = saved_ns_pkgs
+ sys.path = saved_sys_path
+
+ def test_two_levels_deep(self, symlinked_tmpdir):
"""
Test nested namespace packages
Create namespace packages in the following tree :
@@ -631,19 +656,16 @@ class TestNamespaces:
Check both are in the _namespace_packages dict and that their __path__
is correct
"""
- sys.path.append(os.path.join(self._tmpdir, "site-pkgs2"))
- os.makedirs(os.path.join(self._tmpdir, "site-pkgs", "pkg1", "pkg2"))
- os.makedirs(os.path.join(self._tmpdir, "site-pkgs2", "pkg1", "pkg2"))
- ns_str = "__import__('pkg_resources').declare_namespace(__name__)\n"
- for site in ["site-pkgs", "site-pkgs2"]:
- pkg1_init = open(os.path.join(self._tmpdir, site,
- "pkg1", "__init__.py"), "w")
- pkg1_init.write(ns_str)
- pkg1_init.close()
- pkg2_init = open(os.path.join(self._tmpdir, site,
- "pkg1", "pkg2", "__init__.py"), "w")
- pkg2_init.write(ns_str)
- pkg2_init.close()
+ real_tmpdir = symlinked_tmpdir.realpath()
+ tmpdir = symlinked_tmpdir
+ sys.path.append(str(tmpdir / 'site-pkgs2'))
+ site_dirs = tmpdir / 'site-pkgs', tmpdir / 'site-pkgs2'
+ for site in site_dirs:
+ pkg1 = site / 'pkg1'
+ pkg2 = pkg1 / 'pkg2'
+ pkg2.ensure_dir()
+ (pkg1 / '__init__.py').write_text(self.ns_str, encoding='utf-8')
+ (pkg2 / '__init__.py').write_text(self.ns_str, encoding='utf-8')
import pkg1
assert "pkg1" in pkg_resources._namespace_packages
# attempt to import pkg2 from site-pkgs2
@@ -653,7 +675,44 @@ class TestNamespaces:
assert pkg_resources._namespace_packages["pkg1"] == ["pkg1.pkg2"]
# check the __path__ attribute contains both paths
expected = [
- os.path.join(self._tmpdir, "site-pkgs", "pkg1", "pkg2"),
- os.path.join(self._tmpdir, "site-pkgs2", "pkg1", "pkg2"),
+ str(real_tmpdir / "site-pkgs" / "pkg1" / "pkg2"),
+ str(real_tmpdir / "site-pkgs2" / "pkg1" / "pkg2"),
]
assert pkg1.pkg2.__path__ == expected
+
+ def test_path_order(self, symlinked_tmpdir):
+ """
+ Test that if multiple versions of the same namespace package subpackage
+ are on different sys.path entries, that only the one earliest on
+ sys.path is imported, and that the namespace package's __path__ is in
+ the correct order.
+
+ Regression test for https://bitbucket.org/pypa/setuptools/issues/207
+ """
+
+ tmpdir = symlinked_tmpdir
+ site_dirs = (
+ tmpdir / "site-pkgs",
+ tmpdir / "site-pkgs2",
+ tmpdir / "site-pkgs3",
+ )
+
+ vers_str = "__version__ = %r"
+
+ for number, site in enumerate(site_dirs, 1):
+ if number > 1:
+ sys.path.append(str(site))
+ nspkg = site / 'nspkg'
+ subpkg = nspkg / 'subpkg'
+ subpkg.ensure_dir()
+ (nspkg / '__init__.py').write_text(self.ns_str, encoding='utf-8')
+ (subpkg / '__init__.py').write_text(vers_str % number, encoding='utf-8')
+
+ import nspkg.subpkg
+ import nspkg
+ expected = [
+ str(site.realpath() / 'nspkg')
+ for site in site_dirs
+ ]
+ assert nspkg.__path__ == expected
+ assert nspkg.subpkg.__version__ == 1