Skip to content

Commit ba25a5f

Browse files
authored
Merge pull request #3170 from pypa/feature/nspektr
Vendor nspektr. Utilize it in Distribution._install_dependencies.
2 parents fc72349 + e7b99fa commit ba25a5f

File tree

14 files changed

+278
-19
lines changed

14 files changed

+278
-19
lines changed

changelog.d/3170.change.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Adopt nspektr (vendored) to implement Distribution._install_dependencies.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pip
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright Jason R. Coombs
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to
5+
deal in the Software without restriction, including without limitation the
6+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7+
sell copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19+
IN THE SOFTWARE.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
Metadata-Version: 2.1
2+
Name: nspektr
3+
Version: 0.3.0
4+
Summary: package inspector
5+
Home-page: https://github.com/jaraco/nspektr
6+
Author: Jason R. Coombs
7+
Author-email: [email protected]
8+
License: UNKNOWN
9+
Platform: UNKNOWN
10+
Classifier: Development Status :: 5 - Production/Stable
11+
Classifier: Intended Audience :: Developers
12+
Classifier: License :: OSI Approved :: MIT License
13+
Classifier: Programming Language :: Python :: 3
14+
Classifier: Programming Language :: Python :: 3 :: Only
15+
Requires-Python: >=3.7
16+
License-File: LICENSE
17+
Requires-Dist: jaraco.context
18+
Requires-Dist: jaraco.functools
19+
Requires-Dist: more-itertools
20+
Requires-Dist: packaging
21+
Requires-Dist: importlib-metadata (>=3.6) ; python_version < "3.10"
22+
Provides-Extra: docs
23+
Requires-Dist: sphinx ; extra == 'docs'
24+
Requires-Dist: jaraco.packaging (>=9) ; extra == 'docs'
25+
Requires-Dist: rst.linker (>=1.9) ; extra == 'docs'
26+
Provides-Extra: testing
27+
Requires-Dist: pytest (>=6) ; extra == 'testing'
28+
Requires-Dist: pytest-checkdocs (>=2.4) ; extra == 'testing'
29+
Requires-Dist: pytest-flake8 ; extra == 'testing'
30+
Requires-Dist: pytest-cov ; extra == 'testing'
31+
Requires-Dist: pytest-enabler (>=1.0.1) ; extra == 'testing'
32+
Requires-Dist: pytest-black (>=0.3.7) ; (platform_python_implementation != "PyPy") and extra == 'testing'
33+
Requires-Dist: pytest-mypy (>=0.9.1) ; (platform_python_implementation != "PyPy") and extra == 'testing'
34+
35+
.. image:: https://img.shields.io/pypi/v/nspektr.svg
36+
:target: `PyPI link`_
37+
38+
.. image:: https://img.shields.io/pypi/pyversions/nspektr.svg
39+
:target: `PyPI link`_
40+
41+
.. _PyPI link: https://pypi.org/project/nspektr
42+
43+
.. image:: https://github.com/jaraco/nspektr/workflows/tests/badge.svg
44+
:target: https://github.com/jaraco/nspektr/actions?query=workflow%3A%22tests%22
45+
:alt: tests
46+
47+
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
48+
:target: https://github.com/psf/black
49+
:alt: Code style: Black
50+
51+
.. .. image:: https://readthedocs.org/projects/skeleton/badge/?version=latest
52+
.. :target: https://skeleton.readthedocs.io/en/latest/?badge=latest
53+
54+
.. image:: https://img.shields.io/badge/skeleton-2022-informational
55+
:target: https://blog.jaraco.com/skeleton
56+
57+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
nspektr-0.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2+
nspektr-0.3.0.dist-info/LICENSE,sha256=2z8CRrH5J48VhFuZ_sR4uLUG63ZIeZNyL4xuJUKF-vg,1050
3+
nspektr-0.3.0.dist-info/METADATA,sha256=X0stV4vwFBDBxvzhBl4kAHVdGWPIjEitqAuTJItcQH0,2162
4+
nspektr-0.3.0.dist-info/RECORD,,
5+
nspektr-0.3.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6+
nspektr-0.3.0.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
7+
nspektr-0.3.0.dist-info/top_level.txt,sha256=uEA20Ixo04XS3wOIt5-Jk5ZuMkBrtlleFipRr8Y1SjQ,8
8+
nspektr/__init__.py,sha256=d6-d-ZlGAQQP-MEi_NZMiyn2vLbq8Hw3HxICgm3X0Q8,3949
9+
nspektr/__pycache__/__init__.cpython-310.pyc,,
10+
nspektr/__pycache__/_compat.cpython-310.pyc,,
11+
nspektr/_compat.py,sha256=2QoozYhuhgow_NMUATmhoM-yppBV3jiZYQgdiP-ww0s,582

setuptools/_vendor/nspektr-0.3.0.dist-info/REQUESTED

Whitespace-only changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Wheel-Version: 1.0
2+
Generator: bdist_wheel (0.37.1)
3+
Root-Is-Purelib: true
4+
Tag: py3-none-any
5+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nspektr
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import itertools
2+
import functools
3+
import contextlib
4+
5+
from setuptools.extern.packaging.requirements import Requirement
6+
from setuptools.extern.packaging.version import Version
7+
from setuptools.extern.more_itertools import always_iterable
8+
from setuptools.extern.jaraco.context import suppress
9+
from setuptools.extern.jaraco.functools import apply
10+
11+
from ._compat import metadata, repair_extras
12+
13+
14+
def resolve(req: Requirement) -> metadata.Distribution:
15+
"""
16+
Resolve the requirement to its distribution.
17+
18+
Ignore exception detail for Python 3.9 compatibility.
19+
20+
>>> resolve(Requirement('pytest<3')) # doctest: +IGNORE_EXCEPTION_DETAIL
21+
Traceback (most recent call last):
22+
...
23+
importlib.metadata.PackageNotFoundError: No package metadata was found for pytest<3
24+
"""
25+
dist = metadata.distribution(req.name)
26+
if not req.specifier.contains(Version(dist.version), prereleases=True):
27+
raise metadata.PackageNotFoundError(str(req))
28+
dist.extras = req.extras # type: ignore
29+
return dist
30+
31+
32+
@apply(bool)
33+
@suppress(metadata.PackageNotFoundError)
34+
def is_satisfied(req: Requirement):
35+
return resolve(req)
36+
37+
38+
unsatisfied = functools.partial(itertools.filterfalse, is_satisfied)
39+
40+
41+
class NullMarker:
42+
@classmethod
43+
def wrap(cls, req: Requirement):
44+
return req.marker or cls()
45+
46+
def evaluate(self, *args, **kwargs):
47+
return True
48+
49+
50+
def find_direct_dependencies(dist, extras=None):
51+
"""
52+
Find direct, declared dependencies for dist.
53+
"""
54+
simple = (
55+
req
56+
for req in map(Requirement, always_iterable(dist.requires))
57+
if NullMarker.wrap(req).evaluate(dict(extra=None))
58+
)
59+
extra_deps = (
60+
req
61+
for req in map(Requirement, always_iterable(dist.requires))
62+
for extra in always_iterable(getattr(dist, 'extras', extras))
63+
if NullMarker.wrap(req).evaluate(dict(extra=extra))
64+
)
65+
return itertools.chain(simple, extra_deps)
66+
67+
68+
def traverse(items, visit):
69+
"""
70+
Given an iterable of items, traverse the items.
71+
72+
For each item, visit is called to return any additional items
73+
to include in the traversal.
74+
"""
75+
while True:
76+
try:
77+
item = next(items)
78+
except StopIteration:
79+
return
80+
yield item
81+
items = itertools.chain(items, visit(item))
82+
83+
84+
def find_req_dependencies(req):
85+
with contextlib.suppress(metadata.PackageNotFoundError):
86+
dist = resolve(req)
87+
yield from find_direct_dependencies(dist)
88+
89+
90+
def find_dependencies(dist, extras=None):
91+
"""
92+
Find all reachable dependencies for dist.
93+
94+
dist is an importlib.metadata.Distribution (or similar).
95+
TODO: create a suitable protocol for type hint.
96+
97+
>>> deps = find_dependencies(resolve(Requirement('nspektr')))
98+
>>> all(isinstance(dep, Requirement) for dep in deps)
99+
True
100+
>>> not any('pytest' in str(dep) for dep in deps)
101+
True
102+
>>> test_deps = find_dependencies(resolve(Requirement('nspektr[testing]')))
103+
>>> any('pytest' in str(dep) for dep in test_deps)
104+
True
105+
"""
106+
107+
def visit(req, seen=set()):
108+
if req in seen:
109+
return ()
110+
seen.add(req)
111+
return find_req_dependencies(req)
112+
113+
return traverse(find_direct_dependencies(dist, extras), visit)
114+
115+
116+
class Unresolved(Exception):
117+
def __iter__(self):
118+
return iter(self.args[0])
119+
120+
121+
def missing(ep):
122+
"""
123+
Generate the unresolved dependencies (if any) of ep.
124+
"""
125+
return unsatisfied(find_dependencies(ep.dist, repair_extras(ep.extras)))
126+
127+
128+
def check(ep):
129+
"""
130+
>>> ep, = metadata.entry_points(group='console_scripts', name='pip')
131+
>>> check(ep)
132+
>>> dist = metadata.distribution('nspektr')
133+
134+
Since 'docs' extras are not installed, requesting them should fail.
135+
136+
>>> ep = metadata.EntryPoint(
137+
... group=None, name=None, value='nspektr [docs]')._for(dist)
138+
>>> check(ep)
139+
Traceback (most recent call last):
140+
...
141+
nspektr.Unresolved: [...]
142+
"""
143+
missed = list(missing(ep))
144+
if missed:
145+
raise Unresolved(missed)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import contextlib
2+
import sys
3+
4+
5+
if sys.version_info >= (3, 10):
6+
import importlib.metadata as metadata
7+
else:
8+
import setuptools.extern.importlib_metadata as metadata # type: ignore # noqa: F401
9+
10+
11+
def repair_extras(extras):
12+
"""
13+
Repair extras that appear as match objects.
14+
15+
python/importlib_metadata#369 revealed a flaw in the EntryPoint
16+
implementation. This function wraps the extras to ensure
17+
they are proper strings even on older implementations.
18+
"""
19+
with contextlib.suppress(AttributeError):
20+
return list(item.group(0) for item in extras)
21+
return extras

0 commit comments

Comments
 (0)