summaryrefslogtreecommitdiff
path: root/pkg_resources
diff options
context:
space:
mode:
Diffstat (limited to 'pkg_resources')
-rw-r--r--pkg_resources/__init__.py41
-rw-r--r--pkg_resources/_vendor/packaging/__about__.py2
-rw-r--r--pkg_resources/_vendor/packaging/markers.py11
-rw-r--r--pkg_resources/_vendor/vendored.txt2
-rw-r--r--pkg_resources/tests/test_resources.py99
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']),