Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pip/_vendor/packaging/requirements.py: 28%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

67 statements  

1# This file is dual licensed under the terms of the Apache License, Version 

2# 2.0, and the BSD License. See the LICENSE file in the root of this repository 

3# for complete details. 

4from __future__ import annotations 

5 

6from typing import Iterator 

7 

8from ._parser import parse_requirement as _parse_requirement 

9from ._tokenizer import ParserSyntaxError 

10from .markers import Marker, _normalize_extra_values 

11from .specifiers import SpecifierSet 

12from .utils import canonicalize_name 

13 

14__all__ = [ 

15 "InvalidRequirement", 

16 "Requirement", 

17] 

18 

19 

20def __dir__() -> list[str]: 

21 return __all__ 

22 

23 

24class InvalidRequirement(ValueError): 

25 """ 

26 An invalid requirement was found, users should refer to PEP 508. 

27 """ 

28 

29 

30class Requirement: 

31 """Parse a requirement. 

32 

33 Parse a given requirement string into its parts, such as name, specifier, 

34 URL, and extras. Raises InvalidRequirement on a badly-formed requirement 

35 string. 

36 

37 Instances are safe to serialize with :mod:`pickle`. They use a stable 

38 format so the same pickle can be loaded in future packaging releases. 

39 

40 .. versionchanged:: 26.2 

41 

42 Added a stable pickle format. Pickles created with packaging 26.2+ can 

43 be unpickled with future releases. Backward compatibility with pickles 

44 from pip._vendor.packaging < 26.2 is supported but may be removed in a future 

45 release. 

46 """ 

47 

48 # TODO: Can we test whether something is contained within a requirement? 

49 # If so how do we do that? Do we need to test against the _name_ of 

50 # the thing as well as the version? What about the markers? 

51 # TODO: Can we normalize the name and extra name? 

52 

53 def __init__(self, requirement_string: str) -> None: 

54 try: 

55 parsed = _parse_requirement(requirement_string) 

56 except ParserSyntaxError as e: 

57 raise InvalidRequirement(str(e)) from e 

58 

59 self.name: str = parsed.name 

60 self.url: str | None = parsed.url or None 

61 self.extras: set[str] = set(parsed.extras or []) 

62 self.specifier: SpecifierSet = SpecifierSet(parsed.specifier) 

63 self.marker: Marker | None = None 

64 if parsed.marker is not None: 

65 self.marker = Marker.__new__(Marker) 

66 self.marker._markers = _normalize_extra_values(parsed.marker) 

67 

68 def _iter_parts(self, name: str) -> Iterator[str]: 

69 yield name 

70 

71 if self.extras: 

72 formatted_extras = ",".join(sorted(self.extras)) 

73 yield f"[{formatted_extras}]" 

74 

75 if self.specifier: 

76 yield str(self.specifier) 

77 

78 if self.url: 

79 yield f" @ {self.url}" 

80 if self.marker: 

81 yield " " 

82 

83 if self.marker: 

84 yield f"; {self.marker}" 

85 

86 def __getstate__(self) -> str: 

87 # Return the requirement string for compactness and stability. 

88 # Re-parsed on load to reconstruct all fields. 

89 return str(self) 

90 

91 def __setstate__(self, state: object) -> None: 

92 if isinstance(state, str): 

93 # New format (26.2+): just the requirement string. 

94 try: 

95 tmp = Requirement(state) 

96 except InvalidRequirement as exc: 

97 raise TypeError(f"Cannot restore Requirement from {state!r}") from exc 

98 self.name = tmp.name 

99 self.url = tmp.url 

100 self.extras = tmp.extras 

101 self.specifier = tmp.specifier 

102 self.marker = tmp.marker 

103 return 

104 if isinstance(state, dict): 

105 # Old format (packaging <= 26.1, no __slots__): plain __dict__. 

106 self.__dict__.update(state) 

107 return 

108 raise TypeError(f"Cannot restore Requirement from {state!r}") 

109 

110 def __str__(self) -> str: 

111 return "".join(self._iter_parts(self.name)) 

112 

113 def __repr__(self) -> str: 

114 return f"<{self.__class__.__name__}({str(self)!r})>" 

115 

116 def __hash__(self) -> int: 

117 return hash(tuple(self._iter_parts(canonicalize_name(self.name)))) 

118 

119 def __eq__(self, other: object) -> bool: 

120 if not isinstance(other, Requirement): 

121 return NotImplemented 

122 

123 return ( 

124 canonicalize_name(self.name) == canonicalize_name(other.name) 

125 and self.extras == other.extras 

126 and self.specifier == other.specifier 

127 and self.url == other.url 

128 and self.marker == other.marker 

129 )