| |
| |
| |
| from __future__ import annotations |
|
|
| from typing import TYPE_CHECKING |
|
|
| from ._parser import parse_requirement as _parse_requirement |
| from ._tokenizer import ParserSyntaxError |
| from .markers import Marker, _normalize_extra_values |
| from .specifiers import InvalidSpecifier, SpecifierSet |
| from .utils import canonicalize_name |
|
|
| if TYPE_CHECKING: |
| from collections.abc import Iterator |
|
|
| __all__ = [ |
| "InvalidRequirement", |
| "Requirement", |
| ] |
|
|
|
|
| def __dir__() -> list[str]: |
| return __all__ |
|
|
|
|
| class InvalidRequirement(ValueError): |
| """ |
| An invalid requirement was found, users should refer to PEP 508. |
| |
| .. versionadded:: 16.1 |
| """ |
|
|
|
|
| class Requirement: |
| """Parse a requirement. |
| |
| Parse a given requirement string into its parts, such as name, specifier, |
| URL, and extras. Raises InvalidRequirement on a badly-formed requirement |
| string. |
| |
| .. versionadded:: 16.1 |
| |
| .. versionchanged:: 22.0 |
| Added equality (``__eq__``) and hashing (``__hash__``) so requirements |
| can be compared and stored in sets / dicts. |
| |
| .. versionchanged:: 23.2 |
| Equality and hashing began canonicalizing requirement names, so |
| requirements whose names differ only by normalization (e.g. |
| ``Requirement("Foo")`` vs ``Requirement("foo")``) now compare and hash |
| equal. |
| |
| Instances are safe to serialize with :mod:`pickle`. They use a stable |
| format so the same pickle can be loaded in future packaging releases. |
| |
| .. versionchanged:: 26.2 |
| |
| Added a stable pickle format. Pickles created with packaging 26.2+ can |
| be unpickled with future releases. Backward compatibility with pickles |
| from packaging < 26.2 is supported but may be removed in a future |
| release. |
| |
| .. versionchanged:: 26.3 |
| |
| The dedicated pickle support introduced in 26.2 did not preserve the |
| specifier's explicit :attr:`~packaging.specifiers.SpecifierSet.prereleases` |
| override; it is now included again. |
| |
| Equality and hashing normalize requirement names, extras, and |
| equivalent specifiers. The string representation still preserves the |
| parsed name and extras spelling. |
| """ |
|
|
| |
| |
| |
| |
|
|
| __slots__ = ("extras", "marker", "name", "specifier", "url") |
|
|
| def __init__(self, requirement_string: str) -> None: |
| try: |
| parsed = _parse_requirement(requirement_string) |
| except ParserSyntaxError as e: |
| raise InvalidRequirement(str(e)) from e |
|
|
| self.name: str = parsed.name |
| self.url: str | None = parsed.url or None |
| self.extras: set[str] = set(parsed.extras) |
| try: |
| self.specifier: SpecifierSet = SpecifierSet(parsed.specifier) |
| except InvalidSpecifier as e: |
| raise InvalidRequirement(str(e)) from e |
| self.marker: Marker | None = None |
| if parsed.marker is not None: |
| self.marker = Marker.__new__(Marker) |
| self.marker._markers = _normalize_extra_values(parsed.marker) |
|
|
| def _iter_parts(self, name: str) -> Iterator[str]: |
| yield name |
|
|
| if self.extras: |
| formatted_extras = ",".join(sorted(self.extras)) |
| yield f"[{formatted_extras}]" |
|
|
| if self.specifier: |
| yield str(self.specifier) |
|
|
| if self.url: |
| yield f" @ {self.url}" |
| if self.marker: |
| yield " " |
|
|
| if self.marker: |
| yield f"; {self.marker}" |
|
|
| def __getstate__(self) -> tuple[str, bool | None]: |
| |
| |
| |
| |
| return (str(self), self.specifier._prereleases) |
|
|
| def __setstate__(self, state: object) -> None: |
| if isinstance(state, str): |
| |
| requirement_string: str = state |
| prereleases: bool | None = None |
| elif ( |
| isinstance(state, tuple) |
| and len(state) == 2 |
| and isinstance(state[0], str) |
| and (state[1] is None or isinstance(state[1], bool)) |
| ): |
| |
| requirement_string, prereleases = state |
| elif isinstance(state, dict) and state.keys() >= set(self.__slots__): |
| |
| for key in self.__slots__: |
| setattr(self, key, state[key]) |
| return |
| else: |
| raise TypeError(f"Cannot restore Requirement from {state!r}") |
|
|
| try: |
| tmp = Requirement(requirement_string) |
| except InvalidRequirement as exc: |
| raise TypeError(f"Cannot restore Requirement from {state!r}") from exc |
| self.name = tmp.name |
| self.url = tmp.url |
| self.extras = tmp.extras |
| self.specifier = tmp.specifier |
| self.specifier._prereleases = prereleases |
| self.marker = tmp.marker |
|
|
| def __str__(self) -> str: |
| return "".join(self._iter_parts(self.name)) |
|
|
| def __repr__(self) -> str: |
| return f"<{self.__class__.__name__}({str(self)!r})>" |
|
|
| def __hash__(self) -> int: |
| |
| |
| |
| |
| |
| return hash( |
| ( |
| canonicalize_name(self.name), |
| frozenset(canonicalize_name(e) for e in self.extras), |
| self.specifier, |
| self.url, |
| self.marker, |
| ) |
| ) |
|
|
| def __eq__(self, other: object) -> bool: |
| if not isinstance(other, Requirement): |
| return NotImplemented |
|
|
| |
| self_extras = frozenset(canonicalize_name(e) for e in self.extras) |
| other_extras = frozenset(canonicalize_name(e) for e in other.extras) |
| return ( |
| canonicalize_name(self.name) == canonicalize_name(other.name) |
| and self_extras == other_extras |
| and self.specifier == other.specifier |
| and self.url == other.url |
| and self.marker == other.marker |
| ) |
|
|