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

77 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:48 +0000

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. 

4 

5import re 

6import string 

7import urllib.parse 

8from typing import List, Optional as TOptional, Set 

9 

10from pip._vendor.pyparsing import ( # noqa 

11 Combine, 

12 Literal as L, 

13 Optional, 

14 ParseException, 

15 Regex, 

16 Word, 

17 ZeroOrMore, 

18 originalTextFor, 

19 stringEnd, 

20 stringStart, 

21) 

22 

23from .markers import MARKER_EXPR, Marker 

24from .specifiers import LegacySpecifier, Specifier, SpecifierSet 

25 

26 

27class InvalidRequirement(ValueError): 

28 """ 

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

30 """ 

31 

32 

33ALPHANUM = Word(string.ascii_letters + string.digits) 

34 

35LBRACKET = L("[").suppress() 

36RBRACKET = L("]").suppress() 

37LPAREN = L("(").suppress() 

38RPAREN = L(")").suppress() 

39COMMA = L(",").suppress() 

40SEMICOLON = L(";").suppress() 

41AT = L("@").suppress() 

42 

43PUNCTUATION = Word("-_.") 

44IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM) 

45IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END)) 

46 

47NAME = IDENTIFIER("name") 

48EXTRA = IDENTIFIER 

49 

50URI = Regex(r"[^ ]+")("url") 

51URL = AT + URI 

52 

53EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA) 

54EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras") 

55 

56VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE) 

57VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE) 

58 

59VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY 

60VERSION_MANY = Combine( 

61 VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False 

62)("_raw_spec") 

63_VERSION_SPEC = Optional((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY) 

64_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "") 

65 

66VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") 

67VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) 

68 

69MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") 

70MARKER_EXPR.setParseAction( 

71 lambda s, l, t: Marker(s[t._original_start : t._original_end]) 

72) 

73MARKER_SEPARATOR = SEMICOLON 

74MARKER = MARKER_SEPARATOR + MARKER_EXPR 

75 

76VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER) 

77URL_AND_MARKER = URL + Optional(MARKER) 

78 

79NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) 

80 

81REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd 

82# pyparsing isn't thread safe during initialization, so we do it eagerly, see 

83# issue #104 

84REQUIREMENT.parseString("x[]") 

85 

86 

87class Requirement: 

88 """Parse a requirement. 

89 

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

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

92 string. 

93 """ 

94 

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

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

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

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

99 

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

101 try: 

102 req = REQUIREMENT.parseString(requirement_string) 

103 except ParseException as e: 

104 raise InvalidRequirement( 

105 f'Parse error at "{ requirement_string[e.loc : e.loc + 8]!r}": {e.msg}' 

106 ) 

107 

108 self.name: str = req.name 

109 if req.url: 

110 parsed_url = urllib.parse.urlparse(req.url) 

111 if parsed_url.scheme == "file": 

112 if urllib.parse.urlunparse(parsed_url) != req.url: 

113 raise InvalidRequirement("Invalid URL given") 

114 elif not (parsed_url.scheme and parsed_url.netloc) or ( 

115 not parsed_url.scheme and not parsed_url.netloc 

116 ): 

117 raise InvalidRequirement(f"Invalid URL: {req.url}") 

118 self.url: TOptional[str] = req.url 

119 else: 

120 self.url = None 

121 self.extras: Set[str] = set(req.extras.asList() if req.extras else []) 

122 self.specifier: SpecifierSet = SpecifierSet(req.specifier) 

123 self.marker: TOptional[Marker] = req.marker if req.marker else None 

124 

125 def __str__(self) -> str: 

126 parts: List[str] = [self.name] 

127 

128 if self.extras: 

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

130 parts.append(f"[{formatted_extras}]") 

131 

132 if self.specifier: 

133 parts.append(str(self.specifier)) 

134 

135 if self.url: 

136 parts.append(f"@ {self.url}") 

137 if self.marker: 

138 parts.append(" ") 

139 

140 if self.marker: 

141 parts.append(f"; {self.marker}") 

142 

143 return "".join(parts) 

144 

145 def __repr__(self) -> str: 

146 return f"<Requirement('{self}')>"