diff options
Diffstat (limited to 'pkg_resources')
| -rw-r--r-- | pkg_resources/__init__.py | 41 | ||||
| -rw-r--r-- | pkg_resources/_vendor/packaging/__about__.py | 2 | ||||
| -rw-r--r-- | pkg_resources/_vendor/packaging/markers.py | 11 | ||||
| -rw-r--r-- | pkg_resources/_vendor/vendored.txt | 2 | ||||
| -rw-r--r-- | pkg_resources/tests/test_resources.py | 99 |
5 files changed, 139 insertions, 16 deletions
diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index eb84f4ba..2eab8230 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -792,6 +792,8 @@ class WorkingSet(object): best = {} to_activate = [] + req_extras = _ReqExtras() + # Mapping of requirement to set of distributions that required it; # useful for reporting info about conflicts. required_by = collections.defaultdict(set) @@ -802,12 +804,10 @@ class WorkingSet(object): if req in processed: # Ignore cyclic or redundant dependencies continue - # If the req has a marker, evaluate it -- skipping the req if - # it evaluates to False. - # https://github.com/pypa/setuptools/issues/523 - _issue_523_bypass = True - if not _issue_523_bypass and req.marker and not req.marker.evaluate(): - continue + + if not req_extras.markers_pass(req): + continue + dist = best.get(req.key) if dist is None: # Find the best distribution and add it to the map @@ -840,6 +840,7 @@ class WorkingSet(object): # Register the new requirements needed by req for new_requirement in new_requirements: required_by[new_requirement].add(req.project_name) + req_extras[new_requirement] = req.extras processed[req] = True @@ -972,6 +973,26 @@ class WorkingSet(object): self.callbacks = callbacks[:] +class _ReqExtras(dict): + """ + Map each requirement to the extras that demanded it. + """ + + def markers_pass(self, req): + """ + Evaluate markers for req against each extra that + demanded it. + + Return False if the req has a marker and fails + evaluation. Otherwise, return True. + """ + extra_evals = ( + req.marker.evaluate({'extra': extra}) + for extra in self.get(req, ()) + (None,) + ) + return not req.marker or any(extra_evals) + + class Environment(object): """Searchable snapshot of distributions on a search path""" @@ -1838,7 +1859,13 @@ class FileMetadata(EmptyProvider): def get_metadata(self, name): if name=='PKG-INFO': with io.open(self.path, encoding='utf-8') as f: - metadata = f.read() + try: + metadata = f.read() + except UnicodeDecodeError as exc: + # add path context to error message + tmpl = " in {self.path}" + exc.reason += tmpl.format(self=self) + raise return metadata raise KeyError("No metadata except PKG-INFO is available") diff --git a/pkg_resources/_vendor/packaging/__about__.py b/pkg_resources/_vendor/packaging/__about__.py index baa90755..c21a758b 100644 --- a/pkg_resources/_vendor/packaging/__about__.py +++ b/pkg_resources/_vendor/packaging/__about__.py @@ -12,7 +12,7 @@ __title__ = "packaging" __summary__ = "Core utilities for Python packages" __uri__ = "https://github.com/pypa/packaging" -__version__ = "16.6" +__version__ = "16.7" __author__ = "Donald Stufft and individual contributors" __email__ = "donald@stufft.io" diff --git a/pkg_resources/_vendor/packaging/markers.py b/pkg_resources/_vendor/packaging/markers.py index bac852df..c5d29cd9 100644 --- a/pkg_resources/_vendor/packaging/markers.py +++ b/pkg_resources/_vendor/packaging/markers.py @@ -78,9 +78,18 @@ VARIABLE = ( L("platform.version") | # PEP-345 L("platform.machine") | # PEP-345 L("platform.python_implementation") | # PEP-345 + L("python_implementation") | # undocumented setuptools legacy L("extra") ) -VARIABLE.setParseAction(lambda s, l, t: Variable(t[0].replace('.', '_'))) +ALIASES = { + 'os.name': 'os_name', + 'sys.platform': 'sys_platform', + 'platform.version': 'platform_version', + 'platform.machine': 'platform_machine', + 'platform.python_implementation': 'platform_python_implementation', + 'python_implementation': 'platform_python_implementation' +} +VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0]))) VERSION_CMP = ( L("===") | diff --git a/pkg_resources/_vendor/vendored.txt b/pkg_resources/_vendor/vendored.txt index 1d03759f..46532c0a 100644 --- a/pkg_resources/_vendor/vendored.txt +++ b/pkg_resources/_vendor/vendored.txt @@ -1,3 +1,3 @@ -packaging==16.6 +packaging==16.7 pyparsing==2.0.6 six==1.10.0 diff --git a/pkg_resources/tests/test_resources.py b/pkg_resources/tests/test_resources.py index 98b8dcd7..31847dc8 100644 --- a/pkg_resources/tests/test_resources.py +++ b/pkg_resources/tests/test_resources.py @@ -171,16 +171,100 @@ class TestDistro: msg = 'Foo 0.9 is installed but Foo==1.2 is required' assert vc.value.report() == msg - @pytest.mark.xfail(reason="Functionality disabled; see #523") - def test_environment_markers(self): - """ - Environment markers are evaluated at resolution time. - """ + def test_environment_marker_evaluation_negative(self): + """Environment markers are evaluated at resolution time.""" ad = pkg_resources.Environment([]) ws = WorkingSet([]) res = ws.resolve(parse_requirements("Foo;python_version<'2'"), ad) assert list(res) == [] + def test_environment_marker_evaluation_positive(self): + ad = pkg_resources.Environment([]) + ws = WorkingSet([]) + Foo = Distribution.from_filename("/foo_dir/Foo-1.2.dist-info") + ad.add(Foo) + res = ws.resolve(parse_requirements("Foo;python_version>='2'"), ad) + assert list(res) == [Foo] + + def test_environment_marker_evaluation_called(self): + """ + If one package foo requires bar without any extras, + markers should pass for bar without extras. + """ + parent_req, = parse_requirements("foo") + req, = parse_requirements("bar;python_version>='2'") + req_extras = pkg_resources._ReqExtras({req: parent_req.extras}) + assert req_extras.markers_pass(req) + + parent_req, = parse_requirements("foo[]") + req, = parse_requirements("bar;python_version>='2'") + req_extras = pkg_resources._ReqExtras({req: parent_req.extras}) + assert req_extras.markers_pass(req) + + def test_marker_evaluation_with_extras(self): + """Extras are also evaluated as markers at resolution time.""" + ad = pkg_resources.Environment([]) + ws = WorkingSet([]) + # Metadata needs to be native strings due to cStringIO behaviour in + # 2.6, so use str(). + Foo = Distribution.from_filename( + "/foo_dir/Foo-1.2.dist-info", + metadata=Metadata(("METADATA", str("Provides-Extra: baz\n" + "Requires-Dist: quux; extra=='baz'"))) + ) + ad.add(Foo) + assert list(ws.resolve(parse_requirements("Foo"), ad)) == [Foo] + quux = Distribution.from_filename("/foo_dir/quux-1.0.dist-info") + ad.add(quux) + res = list(ws.resolve(parse_requirements("Foo[baz]"), ad)) + assert res == [Foo,quux] + + def test_marker_evaluation_with_multiple_extras(self): + ad = pkg_resources.Environment([]) + ws = WorkingSet([]) + # Metadata needs to be native strings due to cStringIO behaviour in + # 2.6, so use str(). + Foo = Distribution.from_filename( + "/foo_dir/Foo-1.2.dist-info", + metadata=Metadata(("METADATA", str("Provides-Extra: baz\n" + "Requires-Dist: quux; extra=='baz'\n" + "Provides-Extra: bar\n" + "Requires-Dist: fred; extra=='bar'\n"))) + ) + ad.add(Foo) + quux = Distribution.from_filename("/foo_dir/quux-1.0.dist-info") + ad.add(quux) + fred = Distribution.from_filename("/foo_dir/fred-0.1.dist-info") + ad.add(fred) + res = list(ws.resolve(parse_requirements("Foo[baz,bar]"), ad)) + assert sorted(res) == [fred,quux,Foo] + + def test_marker_evaluation_with_extras_loop(self): + ad = pkg_resources.Environment([]) + ws = WorkingSet([]) + # Metadata needs to be native strings due to cStringIO behaviour in + # 2.6, so use str(). + a = Distribution.from_filename( + "/foo_dir/a-0.2.dist-info", + metadata=Metadata(("METADATA", str("Requires-Dist: c[a]"))) + ) + b = Distribution.from_filename( + "/foo_dir/b-0.3.dist-info", + metadata=Metadata(("METADATA", str("Requires-Dist: c[b]"))) + ) + c = Distribution.from_filename( + "/foo_dir/c-1.0.dist-info", + metadata=Metadata(("METADATA", str("Provides-Extra: a\n" + "Requires-Dist: b;extra=='a'\n" + "Provides-Extra: b\n" + "Requires-Dist: foo;extra=='b'"))) + ) + foo = Distribution.from_filename("/foo_dir/foo-0.1.dist-info") + for dist in (a, b, c, foo): + ad.add(dist) + res = list(ws.resolve(parse_requirements("a"), ad)) + assert res == [a, c, b, foo] + def testDistroDependsOptions(self): d = self.distRequires(""" Twisted>=1.5 @@ -313,7 +397,10 @@ class TestEntryPoints: def checkSubMap(self, m): assert len(m) == len(self.submap_expect) for key, ep in self.submap_expect.items(): - assert repr(m.get(key)) == repr(ep) + assert m.get(key).name == ep.name + assert m.get(key).module_name == ep.module_name + assert sorted(m.get(key).attrs) == sorted(ep.attrs) + assert sorted(m.get(key).extras) == sorted(ep.extras) submap_expect = dict( feature1=EntryPoint('feature1', 'somemodule', ['somefunction']), |
