REDROOM
PHP 8.3.31
Path:
Logout
Edit File
Size: 9.98 KB
Close
/proc/self/root/proc/self/root/opt/hc_python/lib64/python3.12/site-packages/packaging/dependency_groups.py
Text
Base64
from __future__ import annotations import re from collections.abc import Mapping, Sequence from .errors import _ErrorCollector from .requirements import Requirement __all__ = [ "CyclicDependencyGroup", "DependencyGroupInclude", "DependencyGroupResolver", "DuplicateGroupNames", "InvalidDependencyGroupObject", "resolve_dependency_groups", ] def __dir__() -> list[str]: return __all__ # ----------- # Error Types # ----------- class DuplicateGroupNames(ValueError): """ The same dependency groups were defined twice, with different non-normalized names. """ class CyclicDependencyGroup(ValueError): """ The dependency group includes form 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}" ) # in the PEP 735 spec, the tables in dependency group lists were described as # "Dependency Object Specifiers", but the only defined type of object was a # "Dependency Group Include" -- hence the naming of this error as "Object" class InvalidDependencyGroupObject(ValueError): """ A member of a dependency group was identified as a dict, but was not in a valid format. """ # ------------------------ # Object Model & Interface # ------------------------ class DependencyGroupInclude: __slots__ = ("include_group",) def __init__(self, include_group: str) -> None: """ Initialize a DependencyGroupInclude. :param include_group: The name of the group referred to by this include. """ self.include_group = include_group def __repr__(self) -> str: return f"{self.__class__.__name__}({self.include_group!r})" 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, Sequence[str | Mapping[str, str]]], ) -> None: errors = _ErrorCollector() self.dependency_groups = _normalize_group_names(dependency_groups, errors) # 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, ...]] = {} errors.finalize("[dependency-groups] data was invalid") 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 """ group = _normalize_name(group) with _ErrorCollector().on_exit( f"[dependency-groups] data for {group!r} was malformed" ) as errors: return self._parse_group(group, errors) 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 """ group = _normalize_name(group) with _ErrorCollector().on_exit( f"[dependency-groups] data for {group!r} was malformed" ) as errors: return self._resolve(group, group, errors) def _resolve( self, group: str, requested_group: str, errors: _ErrorCollector ) -> tuple[Requirement, ...]: """ This is a helper for cached resolution to strings. It preserves the name of the group which the user initially requested in order to present a clearer error in the event that a cycle is detected. :param group: The normalized 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, errors) 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 a group is cyclic, record the error # otherwise, follow the include_group reference # # this allows us to examine all includes in a group, even in the # presence of errors if include_group in self._include_graph_ancestors.get(group, ()): errors.error( CyclicDependencyGroup( requested_group, group, item.include_group ) ) else: self._include_graph_ancestors[include_group] = ( *self._include_graph_ancestors.get(group, ()), group, ) resolved_group.extend( self._resolve(include_group, requested_group, errors) ) else: # pragma: no cover raise NotImplementedError( f"Invalid dependency group item after parse: {item}" ) # in the event that errors were detected, present the group as empty and do not # cache the result # this ensures that repeated access to a cyclic group will raise multiple errors if errors.errors: return () self._resolve_cache[group] = tuple(resolved_group) return self._resolve_cache[group] def _parse_group( self, group: str, errors: _ErrorCollector ) -> 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: errors.error(LookupError(f"Dependency group '{group}' not found")) return () raw_group = self.dependency_groups[group] if isinstance(raw_group, str): errors.error( TypeError( f"Dependency group {group!r} contained a string rather than a list." ) ) return () if not isinstance(raw_group, Sequence): errors.error( TypeError(f"Dependency group {group!r} is not a sequence type.") ) return () 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, Mapping): if tuple(item.keys()) != ("include-group",): errors.error( InvalidDependencyGroupObject( f"Invalid dependency group item: {item!r}" ) ) else: include_group = item["include-group"] elements.append(DependencyGroupInclude(include_group=include_group)) else: errors.error(TypeError(f"Invalid dependency group item: {item!r}")) self._parsed_groups[group] = tuple(elements) return self._parsed_groups[group] # -------------------- # Functional Interface # -------------------- def resolve_dependency_groups( dependency_groups: Mapping[str, Sequence[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 """ resolver = DependencyGroupResolver(dependency_groups) return tuple(str(r) for group in groups for r in resolver.resolve(group)) # ---------------- # internal helpers # ---------------- _NORMALIZE_PATTERN = re.compile(r"[-_.]+") def _normalize_name(name: str) -> str: return _NORMALIZE_PATTERN.sub("-", name).lower() def _normalize_group_names( dependency_groups: Mapping[str, Sequence[str | Mapping[str, str]]], errors: _ErrorCollector, ) -> dict[str, Sequence[str | Mapping[str, str]]]: original_names: dict[str, list[str]] = {} normalized_groups: dict[str, Sequence[str | Mapping[str, str]]] = {} 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 for normed_name, names in original_names.items(): if len(names) > 1: errors.error( DuplicateGroupNames( "Duplicate dependency group names: " f"{normed_name} ({', '.join(names)})" ) ) return normalized_groups
Save
Close
Exit & Reset
Text mode: syntax highlighting auto-detects file type.
Directory Contents
Dirs: 2 × Files: 19
Delete Selected
Select All
Select None
Sort:
Name
Size
Modified
Enable drag-to-move
Name
Size
Perms
Modified
Actions
licenses
DIR
-
drwxr-xr-x
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
__pycache__
DIR
-
drwxr-xr-x
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
dependency_groups.py
9.98 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
direct_url.py
10.66 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
errors.py
2.62 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
markers.py
16.66 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
metadata.py
37.86 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
py.typed
0 B
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
pylock.py
33.10 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
requirements.py
4.28 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
specifiers.py
69.84 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
tags.py
33.42 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
utils.py
9.62 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
version.py
37.47 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
_elffile.py
3.14 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
_manylinux.py
9.33 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
_musllinux.py
2.64 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
_parser.py
11.42 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
_structures.py
1.08 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
_tokenizer.py
5.26 KB
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
__init__.py
494 B
lrw-r--r--
2026-06-11 06:30:31
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
Zip Selected
If ZipArchive is unavailable, a
.tar
will be created (no compression).