aboutsummaryrefslogtreecommitdiff
path: root/setuptools/_vendor/nspektr/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'setuptools/_vendor/nspektr/__init__.py')
-rw-r--r--setuptools/_vendor/nspektr/__init__.py145
1 files changed, 145 insertions, 0 deletions
diff --git a/setuptools/_vendor/nspektr/__init__.py b/setuptools/_vendor/nspektr/__init__.py
new file mode 100644
index 0000000..938bbdb
--- /dev/null
+++ b/setuptools/_vendor/nspektr/__init__.py
@@ -0,0 +1,145 @@
+import itertools
+import functools
+import contextlib
+
+from setuptools.extern.packaging.requirements import Requirement
+from setuptools.extern.packaging.version import Version
+from setuptools.extern.more_itertools import always_iterable
+from setuptools.extern.jaraco.context import suppress
+from setuptools.extern.jaraco.functools import apply
+
+from ._compat import metadata, repair_extras
+
+
+def resolve(req: Requirement) -> metadata.Distribution:
+ """
+ Resolve the requirement to its distribution.
+
+ Ignore exception detail for Python 3.9 compatibility.
+
+ >>> resolve(Requirement('pytest<3')) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ importlib.metadata.PackageNotFoundError: No package metadata was found for pytest<3
+ """
+ dist = metadata.distribution(req.name)
+ if not req.specifier.contains(Version(dist.version), prereleases=True):
+ raise metadata.PackageNotFoundError(str(req))
+ dist.extras = req.extras # type: ignore
+ return dist
+
+
+@apply(bool)
+@suppress(metadata.PackageNotFoundError)
+def is_satisfied(req: Requirement):
+ return resolve(req)
+
+
+unsatisfied = functools.partial(itertools.filterfalse, is_satisfied)
+
+
+class NullMarker:
+ @classmethod
+ def wrap(cls, req: Requirement):
+ return req.marker or cls()
+
+ def evaluate(self, *args, **kwargs):
+ return True
+
+
+def find_direct_dependencies(dist, extras=None):
+ """
+ Find direct, declared dependencies for dist.
+ """
+ simple = (
+ req
+ for req in map(Requirement, always_iterable(dist.requires))
+ if NullMarker.wrap(req).evaluate(dict(extra=None))
+ )
+ extra_deps = (
+ req
+ for req in map(Requirement, always_iterable(dist.requires))
+ for extra in always_iterable(getattr(dist, 'extras', extras))
+ if NullMarker.wrap(req).evaluate(dict(extra=extra))
+ )
+ return itertools.chain(simple, extra_deps)
+
+
+def traverse(items, visit):
+ """
+ Given an iterable of items, traverse the items.
+
+ For each item, visit is called to return any additional items
+ to include in the traversal.
+ """
+ while True:
+ try:
+ item = next(items)
+ except StopIteration:
+ return
+ yield item
+ items = itertools.chain(items, visit(item))
+
+
+def find_req_dependencies(req):
+ with contextlib.suppress(metadata.PackageNotFoundError):
+ dist = resolve(req)
+ yield from find_direct_dependencies(dist)
+
+
+def find_dependencies(dist, extras=None):
+ """
+ Find all reachable dependencies for dist.
+
+ dist is an importlib.metadata.Distribution (or similar).
+ TODO: create a suitable protocol for type hint.
+
+ >>> deps = find_dependencies(resolve(Requirement('nspektr')))
+ >>> all(isinstance(dep, Requirement) for dep in deps)
+ True
+ >>> not any('pytest' in str(dep) for dep in deps)
+ True
+ >>> test_deps = find_dependencies(resolve(Requirement('nspektr[testing]')))
+ >>> any('pytest' in str(dep) for dep in test_deps)
+ True
+ """
+
+ def visit(req, seen=set()):
+ if req in seen:
+ return ()
+ seen.add(req)
+ return find_req_dependencies(req)
+
+ return traverse(find_direct_dependencies(dist, extras), visit)
+
+
+class Unresolved(Exception):
+ def __iter__(self):
+ return iter(self.args[0])
+
+
+def missing(ep):
+ """
+ Generate the unresolved dependencies (if any) of ep.
+ """
+ return unsatisfied(find_dependencies(ep.dist, repair_extras(ep.extras)))
+
+
+def check(ep):
+ """
+ >>> ep, = metadata.entry_points(group='console_scripts', name='pip')
+ >>> check(ep)
+ >>> dist = metadata.distribution('nspektr')
+
+ Since 'docs' extras are not installed, requesting them should fail.
+
+ >>> ep = metadata.EntryPoint(
+ ... group=None, name=None, value='nspektr [docs]')._for(dist)
+ >>> check(ep)
+ Traceback (most recent call last):
+ ...
+ nspektr.Unresolved: [...]
+ """
+ missed = list(missing(ep))
+ if missed:
+ raise Unresolved(missed)