|
from __future__ import annotations |
|
|
|
import re |
|
|
|
from collections.abc import Iterator, Set |
|
|
|
|
|
_WHEEL_FILENAME_REGEX = re.compile( |
|
r'(?P<distribution>.+)-(?P<version>.+)' |
|
r'(-(?P<build_tag>.+))?-(?P<python_tag>.+)' |
|
r'-(?P<abi_tag>.+)-(?P<platform_tag>.+)\.whl' |
|
) |
|
|
|
|
|
def check_dependency( |
|
req_string: str, ancestral_req_strings: tuple[str, ...] = (), parent_extras: Set[str] = frozenset() |
|
) -> Iterator[tuple[str, ...]]: |
|
""" |
|
Verify that a dependency and all of its dependencies are met. |
|
|
|
:param req_string: Requirement string |
|
:param parent_extras: Extras (eg. "test" in myproject[test]) |
|
:yields: Unmet dependencies |
|
""" |
|
import packaging.requirements |
|
|
|
from ._compat import importlib |
|
|
|
req = packaging.requirements.Requirement(req_string) |
|
normalised_req_string = str(req) |
|
|
|
|
|
|
|
if normalised_req_string in ancestral_req_strings: |
|
|
|
return |
|
|
|
if req.marker: |
|
extras = frozenset(('',)).union(parent_extras) |
|
|
|
|
|
if all(not req.marker.evaluate(environment={'extra': e}) for e in extras): |
|
|
|
|
|
return |
|
|
|
try: |
|
dist = importlib.metadata.distribution(req.name) |
|
except importlib.metadata.PackageNotFoundError: |
|
|
|
yield (*ancestral_req_strings, normalised_req_string) |
|
else: |
|
if req.specifier and not req.specifier.contains(dist.version, prereleases=True): |
|
|
|
yield (*ancestral_req_strings, normalised_req_string) |
|
elif dist.requires: |
|
for other_req_string in dist.requires: |
|
|
|
yield from check_dependency(other_req_string, (*ancestral_req_strings, normalised_req_string), req.extras) |
|
|
|
|
|
def parse_wheel_filename(filename: str) -> re.Match[str] | None: |
|
return _WHEEL_FILENAME_REGEX.match(filename) |
|
|