/
niceSOFT
/
python3-build
Обзор
Документация
Войти
/
niceSOFT
/
python3-build
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/build/_util.py
104 строки
4 KB
Henry Schreiner
perf: memoize check_dependency (#1122)
07 июл 2026, 23:39
Не верифицирован
07 июл 2026, 23:39
820d4e5
Код
Авторство
О чём код?
from __future__ import annotations __lazy_modules__ = [ f'{__spec__.parent}._compat', 'packaging', 'packaging.requirements', ] import sys import packaging.requirements from ._compat import importlib TYPE_CHECKING = False if TYPE_CHECKING: from collections.abc import Iterator from collections.abc import Set as AbstractSet def check_dependency( req_string: str, ancestral_req_strings: tuple[str, ...] = (), parent_extras: AbstractSet[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 """ yield from _check_dependency(req_string, ancestral_req_strings, parent_extras, set()) def _check_dependency( req_string: str, ancestral_req_strings: tuple[str, ...], parent_extras: AbstractSet[str], seen: set[str] ) -> Iterator[tuple[str, ...]]: req = packaging.requirements.Requirement(req_string) normalised_req_string = str(req) # ``seen`` holds requirements already verified as fully satisfied (nothing # yielded), so shared subtrees of a diamond dependency graph are walked once. if normalised_req_string in seen: return # ``Requirement`` doesn't implement ``__eq__`` so we cannot compare reqs for # equality directly but the string representation is stable. if normalised_req_string in ancestral_req_strings: # cyclical dependency, already checked. return if req.marker: extras = frozenset(('',)).union(parent_extras) # a requirement can have multiple extras but ``evaluate`` can # only check one at a time. if all(not req.marker.evaluate(environment={'extra': e}) for e in extras): # if the marker conditions are not met, we pretend that the # dependency is satisfied. return try: dist = importlib.metadata.distribution(req.name) except importlib.metadata.PackageNotFoundError: # dependency is not installed in the environment. yield (*ancestral_req_strings, normalised_req_string) return if req.specifier and not req.specifier.contains(dist.version, prereleases=True): # the installed version is incompatible. yield (*ancestral_req_strings, normalised_req_string) return satisfied = True for other_req_string in dist.requires or (): # yields transitive dependencies that are not satisfied. for unmet in _check_dependency(other_req_string, (*ancestral_req_strings, normalised_req_string), req.extras, seen): satisfied = False yield unmet # unmet requirements are not memoised: they must be reported once per # dependency chain that reaches them. if satisfied: seen.add(normalised_req_string) def format_unmet_dependencies(unmet: AbstractSet[tuple[str, ...]]) -> str: body = ''.join(_format_missing_dependency(chain) for chain in sorted(unmet)) return f'Unmet dependencies (checked against {sys.executable}):{body}' def _format_missing_dependency(dep_chain: tuple[str, ...]) -> str: requirement = packaging.requirements.Requirement(dep_chain[-1]) try: found = importlib.metadata.distribution(requirement.name).version except importlib.metadata.PackageNotFoundError: found = 'not installed' wanted = str(requirement.specifier) if requirement.specifier else 'any' return f'\n\t{_format_dep_chain(dep_chain)}\n\t\twanted: {wanted}\n\t\tfound: {found}' def _format_dep_chain(dep_chain: tuple[str, ...]) -> str: return ' -> '.join(dep.partition(';')[0].strip() for dep in dep_chain)