Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/setuptools/_normalization.py: 35%

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

43 statements  

1""" 

2Helpers for normalization as expected in wheel/sdist/module file names 

3and core metadata 

4""" 

5 

6import re 

7 

8import packaging 

9 

10# https://packaging.python.org/en/latest/specifications/core-metadata/#name 

11_VALID_NAME = re.compile(r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.I) 

12_UNSAFE_NAME_CHARS = re.compile(r"[^A-Z0-9._-]+", re.I) 

13_NON_ALPHANUMERIC = re.compile(r"[^A-Z0-9]+", re.I) 

14_PEP440_FALLBACK = re.compile(r"^v?(?P<safe>(?:[0-9]+!)?[0-9]+(?:\.[0-9]+)*)", re.I) 

15 

16 

17def safe_identifier(name: str) -> str: 

18 """Make a string safe to be used as Python identifier. 

19 >>> safe_identifier("12abc") 

20 '_12abc' 

21 >>> safe_identifier("__editable__.myns.pkg-78.9.3_local") 

22 '__editable___myns_pkg_78_9_3_local' 

23 """ 

24 safe = re.sub(r'\W|^(?=\d)', '_', name) 

25 assert safe.isidentifier() 

26 return safe 

27 

28 

29def safe_name(component: str) -> str: 

30 """Escape a component used as a project name according to Core Metadata. 

31 >>> safe_name("hello world") 

32 'hello-world' 

33 >>> safe_name("hello?world") 

34 'hello-world' 

35 >>> safe_name("hello_world") 

36 'hello_world' 

37 """ 

38 # See pkg_resources.safe_name 

39 return _UNSAFE_NAME_CHARS.sub("-", component) 

40 

41 

42def safe_version(version: str) -> str: 

43 """Convert an arbitrary string into a valid version string. 

44 Can still raise an ``InvalidVersion`` exception. 

45 To avoid exceptions use ``best_effort_version``. 

46 >>> safe_version("1988 12 25") 

47 '1988.12.25' 

48 >>> safe_version("v0.2.1") 

49 '0.2.1' 

50 >>> safe_version("v0.2?beta") 

51 '0.2b0' 

52 >>> safe_version("v0.2 beta") 

53 '0.2b0' 

54 >>> safe_version("ubuntu lts") 

55 Traceback (most recent call last): 

56 ... 

57 packaging.version.InvalidVersion: Invalid version: 'ubuntu.lts' 

58 """ 

59 v = version.replace(' ', '.') 

60 try: 

61 return str(packaging.version.Version(v)) 

62 except packaging.version.InvalidVersion: 

63 attempt = _UNSAFE_NAME_CHARS.sub("-", v) 

64 return str(packaging.version.Version(attempt)) 

65 

66 

67def best_effort_version(version: str) -> str: 

68 """Convert an arbitrary string into a version-like string. 

69 Fallback when ``safe_version`` is not safe enough. 

70 >>> best_effort_version("v0.2 beta") 

71 '0.2b0' 

72 >>> best_effort_version("ubuntu lts") 

73 '0.dev0+sanitized.ubuntu.lts' 

74 >>> best_effort_version("0.23ubuntu1") 

75 '0.23.dev0+sanitized.ubuntu1' 

76 >>> best_effort_version("0.23-") 

77 '0.23.dev0+sanitized' 

78 >>> best_effort_version("0.-_") 

79 '0.dev0+sanitized' 

80 >>> best_effort_version("42.+?1") 

81 '42.dev0+sanitized.1' 

82 """ 

83 # See pkg_resources._forgiving_version 

84 try: 

85 return safe_version(version) 

86 except packaging.version.InvalidVersion: 

87 v = version.replace(' ', '.') 

88 match = _PEP440_FALLBACK.search(v) 

89 if match: 

90 safe = match["safe"] 

91 rest = v[len(safe) :] 

92 else: 

93 safe = "0" 

94 rest = version 

95 safe_rest = _NON_ALPHANUMERIC.sub(".", rest).strip(".") 

96 local = f"sanitized.{safe_rest}".strip(".") 

97 return safe_version(f"{safe}.dev0+{local}") 

98 

99 

100def safe_extra(extra: str) -> str: 

101 """Normalize extra name according to PEP 685 

102 >>> safe_extra("_FrIeNdLy-._.-bArD") 

103 'friendly-bard' 

104 >>> safe_extra("FrIeNdLy-._.-bArD__._-") 

105 'friendly-bard' 

106 """ 

107 return _NON_ALPHANUMERIC.sub("-", extra).strip("-").lower() 

108 

109 

110def filename_component(value: str) -> str: 

111 """Normalize each component of a filename (e.g. distribution/version part of wheel) 

112 Note: ``value`` needs to be already normalized. 

113 >>> filename_component("my-pkg") 

114 'my_pkg' 

115 """ 

116 return value.replace("-", "_").strip("_") 

117 

118 

119def filename_component_broken(value: str) -> str: 

120 """ 

121 Produce the incorrect filename component for compatibility. 

122 

123 See pypa/setuptools#4167 for detailed analysis. 

124 

125 TODO: replace this with filename_component after pip 24 is 

126 nearly-ubiquitous. 

127 

128 >>> filename_component_broken('foo_bar-baz') 

129 'foo-bar-baz' 

130 """ 

131 return value.replace('_', '-') 

132 

133 

134def safer_name(value: str) -> str: 

135 """Like ``safe_name`` but can be used as filename component for wheel""" 

136 # See bdist_wheel.safer_name 

137 return filename_component(safe_name(value)) 

138 

139 

140def safer_best_effort_version(value: str) -> str: 

141 """Like ``best_effort_version`` but can be used as filename component for wheel""" 

142 # See bdist_wheel.safer_verion 

143 # TODO: Replace with only safe_version in the future (no need for best effort) 

144 return filename_component(best_effort_version(value))