Загрузить файлы в «venv/Lib/site-packages/pip/_vendor/dependency_groups»
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
from ._implementation import (
|
||||
CyclicDependencyError,
|
||||
DependencyGroupInclude,
|
||||
DependencyGroupResolver,
|
||||
resolve,
|
||||
)
|
||||
|
||||
__all__ = (
|
||||
"CyclicDependencyError",
|
||||
"DependencyGroupInclude",
|
||||
"DependencyGroupResolver",
|
||||
"resolve",
|
||||
)
|
||||
@@ -0,0 +1,65 @@
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
from ._implementation import resolve
|
||||
from ._toml_compat import tomllib
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if tomllib is None:
|
||||
print(
|
||||
"Usage error: dependency-groups CLI requires tomli or Python 3.11+",
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise SystemExit(2)
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"A dependency-groups CLI. Prints out a resolved group, newline-delimited."
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"GROUP_NAME", nargs="*", help="The dependency group(s) to resolve."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--pyproject-file",
|
||||
default="pyproject.toml",
|
||||
help="The pyproject.toml file. Defaults to trying in the current directory.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
help="An output file. Defaults to stdout.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--list",
|
||||
action="store_true",
|
||||
help="List the available dependency groups",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.pyproject_file, "rb") as fp:
|
||||
pyproject = tomllib.load(fp)
|
||||
|
||||
dependency_groups_raw = pyproject.get("dependency-groups", {})
|
||||
|
||||
if args.list:
|
||||
print(*dependency_groups_raw.keys())
|
||||
return
|
||||
if not args.GROUP_NAME:
|
||||
print("A GROUP_NAME is required", file=sys.stderr)
|
||||
raise SystemExit(3)
|
||||
|
||||
content = "\n".join(resolve(dependency_groups_raw, *args.GROUP_NAME))
|
||||
|
||||
if args.output is None or args.output == "-":
|
||||
print(content)
|
||||
else:
|
||||
with open(args.output, "w", encoding="utf-8") as fp:
|
||||
print(content, file=fp)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,209 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import re
|
||||
from collections.abc import Mapping
|
||||
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
|
||||
|
||||
def _normalize_name(name: str) -> str:
|
||||
return re.sub(r"[-_.]+", "-", name).lower()
|
||||
|
||||
|
||||
def _normalize_group_names(
|
||||
dependency_groups: Mapping[str, str | Mapping[str, str]],
|
||||
) -> Mapping[str, str | Mapping[str, str]]:
|
||||
original_names: dict[str, list[str]] = {}
|
||||
normalized_groups = {}
|
||||
|
||||
for group_name, value in dependency_groups.items():
|
||||
normed_group_name = _normalize_name(group_name)
|
||||
original_names.setdefault(normed_group_name, []).append(group_name)
|
||||
normalized_groups[normed_group_name] = value
|
||||
|
||||
errors = []
|
||||
for normed_name, names in original_names.items():
|
||||
if len(names) > 1:
|
||||
errors.append(f"{normed_name} ({', '.join(names)})")
|
||||
if errors:
|
||||
raise ValueError(f"Duplicate dependency group names: {', '.join(errors)}")
|
||||
|
||||
return normalized_groups
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class DependencyGroupInclude:
|
||||
include_group: str
|
||||
|
||||
|
||||
class CyclicDependencyError(ValueError):
|
||||
"""
|
||||
An error representing the detection of a cycle.
|
||||
"""
|
||||
|
||||
def __init__(self, requested_group: str, group: str, include_group: str) -> None:
|
||||
self.requested_group = requested_group
|
||||
self.group = group
|
||||
self.include_group = include_group
|
||||
|
||||
if include_group == group:
|
||||
reason = f"{group} includes itself"
|
||||
else:
|
||||
reason = f"{include_group} -> {group}, {group} -> {include_group}"
|
||||
super().__init__(
|
||||
"Cyclic dependency group include while resolving "
|
||||
f"{requested_group}: {reason}"
|
||||
)
|
||||
|
||||
|
||||
class DependencyGroupResolver:
|
||||
"""
|
||||
A resolver for Dependency Group data.
|
||||
|
||||
This class handles caching, name normalization, cycle detection, and other
|
||||
parsing requirements. There are only two public methods for exploring the data:
|
||||
``lookup()`` and ``resolve()``.
|
||||
|
||||
:param dependency_groups: A mapping, as provided via pyproject
|
||||
``[dependency-groups]``.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dependency_groups: Mapping[str, str | Mapping[str, str]],
|
||||
) -> None:
|
||||
if not isinstance(dependency_groups, Mapping):
|
||||
raise TypeError("Dependency Groups table is not a mapping")
|
||||
self.dependency_groups = _normalize_group_names(dependency_groups)
|
||||
# a map of group names to parsed data
|
||||
self._parsed_groups: dict[
|
||||
str, tuple[Requirement | DependencyGroupInclude, ...]
|
||||
] = {}
|
||||
# a map of group names to their ancestors, used for cycle detection
|
||||
self._include_graph_ancestors: dict[str, tuple[str, ...]] = {}
|
||||
# a cache of completed resolutions to Requirement lists
|
||||
self._resolve_cache: dict[str, tuple[Requirement, ...]] = {}
|
||||
|
||||
def lookup(self, group: str) -> tuple[Requirement | DependencyGroupInclude, ...]:
|
||||
"""
|
||||
Lookup a group name, returning the parsed dependency data for that group.
|
||||
This will not resolve includes.
|
||||
|
||||
:param group: the name of the group to lookup
|
||||
|
||||
:raises ValueError: if the data does not appear to be valid dependency group
|
||||
data
|
||||
:raises TypeError: if the data is not a string
|
||||
:raises LookupError: if group name is absent
|
||||
:raises packaging.requirements.InvalidRequirement: if a specifier is not valid
|
||||
"""
|
||||
if not isinstance(group, str):
|
||||
raise TypeError("Dependency group name is not a str")
|
||||
group = _normalize_name(group)
|
||||
return self._parse_group(group)
|
||||
|
||||
def resolve(self, group: str) -> tuple[Requirement, ...]:
|
||||
"""
|
||||
Resolve a dependency group to a list of requirements.
|
||||
|
||||
:param group: the name of the group to resolve
|
||||
|
||||
:raises TypeError: if the inputs appear to be the wrong types
|
||||
:raises ValueError: if the data does not appear to be valid dependency group
|
||||
data
|
||||
:raises LookupError: if group name is absent
|
||||
:raises packaging.requirements.InvalidRequirement: if a specifier is not valid
|
||||
"""
|
||||
if not isinstance(group, str):
|
||||
raise TypeError("Dependency group name is not a str")
|
||||
group = _normalize_name(group)
|
||||
return self._resolve(group, group)
|
||||
|
||||
def _parse_group(
|
||||
self, group: str
|
||||
) -> tuple[Requirement | DependencyGroupInclude, ...]:
|
||||
# short circuit -- never do the work twice
|
||||
if group in self._parsed_groups:
|
||||
return self._parsed_groups[group]
|
||||
|
||||
if group not in self.dependency_groups:
|
||||
raise LookupError(f"Dependency group '{group}' not found")
|
||||
|
||||
raw_group = self.dependency_groups[group]
|
||||
if not isinstance(raw_group, list):
|
||||
raise TypeError(f"Dependency group '{group}' is not a list")
|
||||
|
||||
elements: list[Requirement | DependencyGroupInclude] = []
|
||||
for item in raw_group:
|
||||
if isinstance(item, str):
|
||||
# packaging.requirements.Requirement parsing ensures that this is a
|
||||
# valid PEP 508 Dependency Specifier
|
||||
# raises InvalidRequirement on failure
|
||||
elements.append(Requirement(item))
|
||||
elif isinstance(item, dict):
|
||||
if tuple(item.keys()) != ("include-group",):
|
||||
raise ValueError(f"Invalid dependency group item: {item}")
|
||||
|
||||
include_group = next(iter(item.values()))
|
||||
elements.append(DependencyGroupInclude(include_group=include_group))
|
||||
else:
|
||||
raise ValueError(f"Invalid dependency group item: {item}")
|
||||
|
||||
self._parsed_groups[group] = tuple(elements)
|
||||
return self._parsed_groups[group]
|
||||
|
||||
def _resolve(self, group: str, requested_group: str) -> tuple[Requirement, ...]:
|
||||
"""
|
||||
This is a helper for cached resolution to strings.
|
||||
|
||||
:param group: The name of the group to resolve.
|
||||
:param requested_group: The group which was used in the original, user-facing
|
||||
request.
|
||||
"""
|
||||
if group in self._resolve_cache:
|
||||
return self._resolve_cache[group]
|
||||
|
||||
parsed = self._parse_group(group)
|
||||
|
||||
resolved_group = []
|
||||
for item in parsed:
|
||||
if isinstance(item, Requirement):
|
||||
resolved_group.append(item)
|
||||
elif isinstance(item, DependencyGroupInclude):
|
||||
include_group = _normalize_name(item.include_group)
|
||||
if include_group in self._include_graph_ancestors.get(group, ()):
|
||||
raise CyclicDependencyError(
|
||||
requested_group, group, item.include_group
|
||||
)
|
||||
self._include_graph_ancestors[include_group] = (
|
||||
*self._include_graph_ancestors.get(group, ()),
|
||||
group,
|
||||
)
|
||||
resolved_group.extend(self._resolve(include_group, requested_group))
|
||||
else: # unreachable
|
||||
raise NotImplementedError(
|
||||
f"Invalid dependency group item after parse: {item}"
|
||||
)
|
||||
|
||||
self._resolve_cache[group] = tuple(resolved_group)
|
||||
return self._resolve_cache[group]
|
||||
|
||||
|
||||
def resolve(
|
||||
dependency_groups: Mapping[str, str | Mapping[str, str]], /, *groups: str
|
||||
) -> tuple[str, ...]:
|
||||
"""
|
||||
Resolve a dependency group to a tuple of requirements, as strings.
|
||||
|
||||
:param dependency_groups: the parsed contents of the ``[dependency-groups]`` table
|
||||
from ``pyproject.toml``
|
||||
:param groups: the name of the group(s) to resolve
|
||||
|
||||
:raises TypeError: if the inputs appear to be the wrong types
|
||||
:raises ValueError: if the data does not appear to be valid dependency group data
|
||||
:raises LookupError: if group name is absent
|
||||
:raises packaging.requirements.InvalidRequirement: if a specifier is not valid
|
||||
"""
|
||||
resolver = DependencyGroupResolver(dependency_groups)
|
||||
return tuple(str(r) for group in groups for r in resolver.resolve(group))
|
||||
@@ -0,0 +1,59 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
from ._implementation import DependencyGroupResolver
|
||||
from ._toml_compat import tomllib
|
||||
|
||||
|
||||
def main(*, argv: list[str] | None = None) -> None:
|
||||
if tomllib is None:
|
||||
print(
|
||||
"Usage error: dependency-groups CLI requires tomli or Python 3.11+",
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise SystemExit(2)
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Lint Dependency Groups for validity. "
|
||||
"This will eagerly load and check all of your Dependency Groups."
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--pyproject-file",
|
||||
default="pyproject.toml",
|
||||
help="The pyproject.toml file. Defaults to trying in the current directory.",
|
||||
)
|
||||
args = parser.parse_args(argv if argv is not None else sys.argv[1:])
|
||||
|
||||
with open(args.pyproject_file, "rb") as fp:
|
||||
pyproject = tomllib.load(fp)
|
||||
dependency_groups_raw = pyproject.get("dependency-groups", {})
|
||||
|
||||
errors: list[str] = []
|
||||
try:
|
||||
resolver = DependencyGroupResolver(dependency_groups_raw)
|
||||
except (ValueError, TypeError) as e:
|
||||
errors.append(f"{type(e).__name__}: {e}")
|
||||
else:
|
||||
for groupname in resolver.dependency_groups:
|
||||
try:
|
||||
resolver.resolve(groupname)
|
||||
except (LookupError, ValueError, TypeError) as e:
|
||||
errors.append(f"{type(e).__name__}: {e}")
|
||||
|
||||
if errors:
|
||||
print("errors encountered while examining dependency groups:")
|
||||
for msg in errors:
|
||||
print(f" {msg}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("ok")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,62 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from ._implementation import DependencyGroupResolver
|
||||
from ._toml_compat import tomllib
|
||||
|
||||
|
||||
def _invoke_pip(deps: list[str]) -> None:
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", *deps])
|
||||
|
||||
|
||||
def main(*, argv: list[str] | None = None) -> None:
|
||||
if tomllib is None:
|
||||
print(
|
||||
"Usage error: dependency-groups CLI requires tomli or Python 3.11+",
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise SystemExit(2)
|
||||
|
||||
parser = argparse.ArgumentParser(description="Install Dependency Groups.")
|
||||
parser.add_argument(
|
||||
"DEPENDENCY_GROUP", nargs="+", help="The dependency groups to install."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--pyproject-file",
|
||||
default="pyproject.toml",
|
||||
help="The pyproject.toml file. Defaults to trying in the current directory.",
|
||||
)
|
||||
args = parser.parse_args(argv if argv is not None else sys.argv[1:])
|
||||
|
||||
with open(args.pyproject_file, "rb") as fp:
|
||||
pyproject = tomllib.load(fp)
|
||||
dependency_groups_raw = pyproject.get("dependency-groups", {})
|
||||
|
||||
errors: list[str] = []
|
||||
resolved: list[str] = []
|
||||
try:
|
||||
resolver = DependencyGroupResolver(dependency_groups_raw)
|
||||
except (ValueError, TypeError) as e:
|
||||
errors.append(f"{type(e).__name__}: {e}")
|
||||
else:
|
||||
for groupname in args.DEPENDENCY_GROUP:
|
||||
try:
|
||||
resolved.extend(str(r) for r in resolver.resolve(groupname))
|
||||
except (LookupError, ValueError, TypeError) as e:
|
||||
errors.append(f"{type(e).__name__}: {e}")
|
||||
|
||||
if errors:
|
||||
print("errors encountered while examining dependency groups:")
|
||||
for msg in errors:
|
||||
print(f" {msg}")
|
||||
sys.exit(1)
|
||||
|
||||
_invoke_pip(resolved)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user