codekingpro/portable-devtools
114k
1from __future__ import annotations2 3import re4 5 6TYPE_CHECKING = False7 8if TYPE_CHECKING:9 from collections.abc import Iterator10 from collections.abc import Set as AbstractSet11 12 13_WHEEL_FILENAME_REGEX = re.compile(14 r'(?P<distribution>.+)-(?P<version>.+)'15 r'(-(?P<build_tag>.+))?-(?P<python_tag>.+)'16 r'-(?P<abi_tag>.+)-(?P<platform_tag>.+)\.whl'17)18 19 20def check_dependency(21 req_string: str, ancestral_req_strings: tuple[str, ...] = (), parent_extras: AbstractSet[str] = frozenset()22) -> Iterator[tuple[str, ...]]:23 """24 Verify that a dependency and all of its dependencies are met.25 26 :param req_string: Requirement string27 :param parent_extras: Extras (eg. "test" in myproject[test])28 :yields: Unmet dependencies29 """30 import packaging.requirements31 32 from ._compat import importlib33 34 req = packaging.requirements.Requirement(req_string)35 normalised_req_string = str(req)36 37 # ``Requirement`` doesn't implement ``__eq__`` so we cannot compare reqs for38 # equality directly but the string representation is stable.39 if normalised_req_string in ancestral_req_strings:40 # cyclical dependency, already checked.41 return42 43 if req.marker:44 extras = frozenset(('',)).union(parent_extras)45 # a requirement can have multiple extras but ``evaluate`` can46 # only check one at a time.47 if all(not req.marker.evaluate(environment={'extra': e}) for e in extras):48 # if the marker conditions are not met, we pretend that the49 # dependency is satisfied.50 return51 52 try:53 dist = importlib.metadata.distribution(req.name)54 except importlib.metadata.PackageNotFoundError:55 # dependency is not installed in the environment.56 yield (*ancestral_req_strings, normalised_req_string)57 else:58 if req.specifier and not req.specifier.contains(dist.version, prereleases=True):59 # the installed version is incompatible.60 yield (*ancestral_req_strings, normalised_req_string)61 elif dist.requires:62 for other_req_string in dist.requires:63 # yields transitive dependencies that are not satisfied.64 yield from check_dependency(other_req_string, (*ancestral_req_strings, normalised_req_string), req.extras)65 66 67def parse_wheel_filename(filename: str) -> re.Match[str] | None:68 return _WHEEL_FILENAME_REGEX.match(filename)69 