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

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

46 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 

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 requirement 

26 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_ of 

31 # 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 e 

39 

40 self.name: str = parsed.name 

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

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

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

44 self.marker: Marker | None = None 

45 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 name 

51 

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 NotImplemented 

79 

80 return ( 

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

82 and self.extras == other.extras 

83 and self.specifier == other.specifier 

84 and self.url == other.url 

85 and self.marker == other.marker 

86 )