codekingpro/portable-devtools
114k
1# This file is dual licensed under the terms of the Apache License, Version2# 2.0, and the BSD License. See the LICENSE file in the root of this repository3# for complete details.4from __future__ import annotations5 6from typing import Iterator7 8from ._parser import parse_requirement as _parse_requirement9from ._tokenizer import ParserSyntaxError10from .markers import Marker, _normalize_extra_values11from .specifiers import SpecifierSet12from .utils import canonicalize_name13 14 15class InvalidRequirement(ValueError):16 """17 An invalid requirement was found, users should refer to PEP 508.18 """19 20 21class Requirement:22 """Parse a requirement.23 24 Parse a given requirement string into its parts, such as name, specifier,25 URL, and extras. Raises InvalidRequirement on a badly-formed requirement26 string.27 """28 29 # TODO: Can we test whether something is contained within a requirement?30 # If so how do we do that? Do we need to test against the _name_ of31 # the thing as well as the version? What about the markers?32 # TODO: Can we normalize the name and extra name?33 34 def __init__(self, requirement_string: str) -> None:35 try:36 parsed = _parse_requirement(requirement_string)37 except ParserSyntaxError as e:38 raise InvalidRequirement(str(e)) from e39 40 self.name: str = parsed.name41 self.url: str | None = parsed.url or None42 self.extras: set[str] = set(parsed.extras or [])43 self.specifier: SpecifierSet = SpecifierSet(parsed.specifier)44 self.marker: Marker | None = None45 if parsed.marker is not None:46 self.marker = Marker.__new__(Marker)47 self.marker._markers = _normalize_extra_values(parsed.marker)48 49 def _iter_parts(self, name: str) -> Iterator[str]:50 yield name51 52 if self.extras:53 formatted_extras = ",".join(sorted(self.extras))54 yield f"[{formatted_extras}]"55 56 if self.specifier:57 yield str(self.specifier)58 59 if self.url:60 yield f" @ {self.url}"61 if self.marker:62 yield " "63 64 if self.marker:65 yield f"; {self.marker}"66 67 def __str__(self) -> str:68 return "".join(self._iter_parts(self.name))69 70 def __repr__(self) -> str:71 return f"<{self.__class__.__name__}('{self}')>"72 73 def __hash__(self) -> int:74 return hash(tuple(self._iter_parts(canonicalize_name(self.name))))75 76 def __eq__(self, other: object) -> bool:77 if not isinstance(other, Requirement):78 return NotImplemented79 80 return (81 canonicalize_name(self.name) == canonicalize_name(other.name)82 and self.extras == other.extras83 and self.specifier == other.specifier84 and self.url == other.url85 and self.marker == other.marker86 )87 