diff options
| author | Steve Kowalik <steven@wedontsleep.org> | 2016-02-16 16:01:54 +1100 |
|---|---|---|
| committer | Steve Kowalik <steven@wedontsleep.org> | 2016-02-16 16:01:54 +1100 |
| commit | 69175b941a74a4e2f37b856437b3ca20bc2f240a (patch) | |
| tree | e0349b731b724c76963f9fd429f53b22cbf142d3 /pkg_resources | |
| parent | 43d0308ad6a8c83be645b09e8c1871b36ff3c4c9 (diff) | |
| parent | 8ccd428cd2a733891bffce13e017774ea82bd8d2 (diff) | |
| download | python-setuptools-git-69175b941a74a4e2f37b856437b3ca20bc2f240a.tar.gz | |
Merge from master, resolving conflicts.
Diffstat (limited to 'pkg_resources')
| -rw-r--r-- | pkg_resources/__init__.py | 82 | ||||
| -rw-r--r-- | pkg_resources/tests/test_pkg_resources.py | 2 | ||||
| -rw-r--r-- | pkg_resources/tests/test_resources.py | 125 |
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 |
