Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pip/_internal/utils/wheel.py: 26%

58 statements  

« prev     ^ index     » next       coverage.py v7.4.3, created at 2024-02-26 06:33 +0000

1"""Support functions for working with wheel files. 

2""" 

3 

4import logging 

5from email.message import Message 

6from email.parser import Parser 

7from typing import Tuple 

8from zipfile import BadZipFile, ZipFile 

9 

10from pip._vendor.packaging.utils import canonicalize_name 

11 

12from pip._internal.exceptions import UnsupportedWheel 

13 

14VERSION_COMPATIBLE = (1, 0) 

15 

16 

17logger = logging.getLogger(__name__) 

18 

19 

20def parse_wheel(wheel_zip: ZipFile, name: str) -> Tuple[str, Message]: 

21 """Extract information from the provided wheel, ensuring it meets basic 

22 standards. 

23 

24 Returns the name of the .dist-info directory and the parsed WHEEL metadata. 

25 """ 

26 try: 

27 info_dir = wheel_dist_info_dir(wheel_zip, name) 

28 metadata = wheel_metadata(wheel_zip, info_dir) 

29 version = wheel_version(metadata) 

30 except UnsupportedWheel as e: 

31 raise UnsupportedWheel(f"{name} has an invalid wheel, {str(e)}") 

32 

33 check_compatibility(version, name) 

34 

35 return info_dir, metadata 

36 

37 

38def wheel_dist_info_dir(source: ZipFile, name: str) -> str: 

39 """Returns the name of the contained .dist-info directory. 

40 

41 Raises AssertionError or UnsupportedWheel if not found, >1 found, or 

42 it doesn't match the provided name. 

43 """ 

44 # Zip file path separators must be / 

45 subdirs = {p.split("/", 1)[0] for p in source.namelist()} 

46 

47 info_dirs = [s for s in subdirs if s.endswith(".dist-info")] 

48 

49 if not info_dirs: 

50 raise UnsupportedWheel(".dist-info directory not found") 

51 

52 if len(info_dirs) > 1: 

53 raise UnsupportedWheel( 

54 "multiple .dist-info directories found: {}".format(", ".join(info_dirs)) 

55 ) 

56 

57 info_dir = info_dirs[0] 

58 

59 info_dir_name = canonicalize_name(info_dir) 

60 canonical_name = canonicalize_name(name) 

61 if not info_dir_name.startswith(canonical_name): 

62 raise UnsupportedWheel( 

63 f".dist-info directory {info_dir!r} does not start with {canonical_name!r}" 

64 ) 

65 

66 return info_dir 

67 

68 

69def read_wheel_metadata_file(source: ZipFile, path: str) -> bytes: 

70 try: 

71 return source.read(path) 

72 # BadZipFile for general corruption, KeyError for missing entry, 

73 # and RuntimeError for password-protected files 

74 except (BadZipFile, KeyError, RuntimeError) as e: 

75 raise UnsupportedWheel(f"could not read {path!r} file: {e!r}") 

76 

77 

78def wheel_metadata(source: ZipFile, dist_info_dir: str) -> Message: 

79 """Return the WHEEL metadata of an extracted wheel, if possible. 

80 Otherwise, raise UnsupportedWheel. 

81 """ 

82 path = f"{dist_info_dir}/WHEEL" 

83 # Zip file path separators must be / 

84 wheel_contents = read_wheel_metadata_file(source, path) 

85 

86 try: 

87 wheel_text = wheel_contents.decode() 

88 except UnicodeDecodeError as e: 

89 raise UnsupportedWheel(f"error decoding {path!r}: {e!r}") 

90 

91 # FeedParser (used by Parser) does not raise any exceptions. The returned 

92 # message may have .defects populated, but for backwards-compatibility we 

93 # currently ignore them. 

94 return Parser().parsestr(wheel_text) 

95 

96 

97def wheel_version(wheel_data: Message) -> Tuple[int, ...]: 

98 """Given WHEEL metadata, return the parsed Wheel-Version. 

99 Otherwise, raise UnsupportedWheel. 

100 """ 

101 version_text = wheel_data["Wheel-Version"] 

102 if version_text is None: 

103 raise UnsupportedWheel("WHEEL is missing Wheel-Version") 

104 

105 version = version_text.strip() 

106 

107 try: 

108 return tuple(map(int, version.split("."))) 

109 except ValueError: 

110 raise UnsupportedWheel(f"invalid Wheel-Version: {version!r}") 

111 

112 

113def check_compatibility(version: Tuple[int, ...], name: str) -> None: 

114 """Raises errors or warns if called with an incompatible Wheel-Version. 

115 

116 pip should refuse to install a Wheel-Version that's a major series 

117 ahead of what it's compatible with (e.g 2.0 > 1.1); and warn when 

118 installing a version only minor version ahead (e.g 1.2 > 1.1). 

119 

120 version: a 2-tuple representing a Wheel-Version (Major, Minor) 

121 name: name of wheel or package to raise exception about 

122 

123 :raises UnsupportedWheel: when an incompatible Wheel-Version is given 

124 """ 

125 if version[0] > VERSION_COMPATIBLE[0]: 

126 raise UnsupportedWheel( 

127 "{}'s Wheel-Version ({}) is not compatible with this version " 

128 "of pip".format(name, ".".join(map(str, version))) 

129 ) 

130 elif version > VERSION_COMPATIBLE: 

131 logger.warning( 

132 "Installing from a newer Wheel-Version (%s)", 

133 ".".join(map(str, version)), 

134 )