diff options
Diffstat (limited to 'setuptools/_vendor/nspektr/__init__.py')
-rw-r--r-- | setuptools/_vendor/nspektr/__init__.py | 145 |
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) |