Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/packaging/utils.py: 50%

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

72 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. 

4 

5from __future__ import annotations 

6 

7import functools 

8import re 

9from typing import NewType, Tuple, Union, cast 

10 

11from .tags import Tag, parse_tag 

12from .version import InvalidVersion, Version, _TrimmedRelease 

13 

14BuildTag = Union[Tuple[()], Tuple[int, str]] 

15NormalizedName = NewType("NormalizedName", str) 

16 

17 

18class InvalidName(ValueError): 

19 """ 

20 An invalid distribution name; users should refer to the packaging user guide. 

21 """ 

22 

23 

24class InvalidWheelFilename(ValueError): 

25 """ 

26 An invalid wheel filename was found, users should refer to PEP 427. 

27 """ 

28 

29 

30class InvalidSdistFilename(ValueError): 

31 """ 

32 An invalid sdist filename was found, users should refer to the packaging user guide. 

33 """ 

34 

35 

36# Core metadata spec for `Name` 

37_validate_regex = re.compile( 

38 r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE 

39) 

40_canonicalize_regex = re.compile(r"[-_.]+") 

41_normalized_regex = re.compile(r"^([a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9])$") 

42# PEP 427: The build number must start with a digit. 

43_build_tag_regex = re.compile(r"(\d+)(.*)") 

44 

45 

46def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName: 

47 if validate and not _validate_regex.match(name): 

48 raise InvalidName(f"name is invalid: {name!r}") 

49 # This is taken from PEP 503. 

50 value = _canonicalize_regex.sub("-", name).lower() 

51 return cast(NormalizedName, value) 

52 

53 

54def is_normalized_name(name: str) -> bool: 

55 return _normalized_regex.match(name) is not None 

56 

57 

58@functools.singledispatch 

59def canonicalize_version( 

60 version: Version | str, *, strip_trailing_zero: bool = True 

61) -> str: 

62 """ 

63 Return a canonical form of a version as a string. 

64 

65 >>> canonicalize_version('1.0.1') 

66 '1.0.1' 

67 

68 Per PEP 625, versions may have multiple canonical forms, differing 

69 only by trailing zeros. 

70 

71 >>> canonicalize_version('1.0.0') 

72 '1' 

73 >>> canonicalize_version('1.0.0', strip_trailing_zero=False) 

74 '1.0.0' 

75 

76 Invalid versions are returned unaltered. 

77 

78 >>> canonicalize_version('foo bar baz') 

79 'foo bar baz' 

80 """ 

81 return str(_TrimmedRelease(str(version)) if strip_trailing_zero else version) 

82 

83 

84@canonicalize_version.register 

85def _(version: str, *, strip_trailing_zero: bool = True) -> str: 

86 try: 

87 parsed = Version(version) 

88 except InvalidVersion: 

89 # Legacy versions cannot be normalized 

90 return version 

91 return canonicalize_version(parsed, strip_trailing_zero=strip_trailing_zero) 

92 

93 

94def parse_wheel_filename( 

95 filename: str, 

96) -> tuple[NormalizedName, Version, BuildTag, frozenset[Tag]]: 

97 if not filename.endswith(".whl"): 

98 raise InvalidWheelFilename( 

99 f"Invalid wheel filename (extension must be '.whl'): {filename!r}" 

100 ) 

101 

102 filename = filename[:-4] 

103 dashes = filename.count("-") 

104 if dashes not in (4, 5): 

105 raise InvalidWheelFilename( 

106 f"Invalid wheel filename (wrong number of parts): {filename!r}" 

107 ) 

108 

109 parts = filename.split("-", dashes - 2) 

110 name_part = parts[0] 

111 # See PEP 427 for the rules on escaping the project name. 

112 if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None: 

113 raise InvalidWheelFilename(f"Invalid project name: {filename!r}") 

114 name = canonicalize_name(name_part) 

115 

116 try: 

117 version = Version(parts[1]) 

118 except InvalidVersion as e: 

119 raise InvalidWheelFilename( 

120 f"Invalid wheel filename (invalid version): {filename!r}" 

121 ) from e 

122 

123 if dashes == 5: 

124 build_part = parts[2] 

125 build_match = _build_tag_regex.match(build_part) 

126 if build_match is None: 

127 raise InvalidWheelFilename( 

128 f"Invalid build number: {build_part} in {filename!r}" 

129 ) 

130 build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2))) 

131 else: 

132 build = () 

133 tags = parse_tag(parts[-1]) 

134 return (name, version, build, tags) 

135 

136 

137def parse_sdist_filename(filename: str) -> tuple[NormalizedName, Version]: 

138 if filename.endswith(".tar.gz"): 

139 file_stem = filename[: -len(".tar.gz")] 

140 elif filename.endswith(".zip"): 

141 file_stem = filename[: -len(".zip")] 

142 else: 

143 raise InvalidSdistFilename( 

144 f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):" 

145 f" {filename!r}" 

146 ) 

147 

148 # We are requiring a PEP 440 version, which cannot contain dashes, 

149 # so we split on the last dash. 

150 name_part, sep, version_part = file_stem.rpartition("-") 

151 if not sep: 

152 raise InvalidSdistFilename(f"Invalid sdist filename: {filename!r}") 

153 

154 name = canonicalize_name(name_part) 

155 

156 try: 

157 version = Version(version_part) 

158 except InvalidVersion as e: 

159 raise InvalidSdistFilename( 

160 f"Invalid sdist filename (invalid version): {filename!r}" 

161 ) from e 

162 

163 return (name, version)