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.2.7, created at 2023-06-07 06:48 +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("{} has an invalid wheel, {}".format(name, 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 ".dist-info directory {!r} does not start with {!r}".format( 

64 info_dir, canonical_name 

65 ) 

66 ) 

67 

68 return info_dir 

69 

70 

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

72 try: 

73 return source.read(path) 

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

75 # and RuntimeError for password-protected files 

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

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

78 

79 

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

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

82 Otherwise, raise UnsupportedWheel. 

83 """ 

84 path = f"{dist_info_dir}/WHEEL" 

85 # Zip file path separators must be / 

86 wheel_contents = read_wheel_metadata_file(source, path) 

87 

88 try: 

89 wheel_text = wheel_contents.decode() 

90 except UnicodeDecodeError as e: 

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

92 

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

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

95 # currently ignore them. 

96 return Parser().parsestr(wheel_text) 

97 

98 

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

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

101 Otherwise, raise UnsupportedWheel. 

102 """ 

103 version_text = wheel_data["Wheel-Version"] 

104 if version_text is None: 

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

106 

107 version = version_text.strip() 

108 

109 try: 

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

111 except ValueError: 

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

113 

114 

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

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

117 

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

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

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

121 

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

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

124 

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

126 """ 

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

128 raise UnsupportedWheel( 

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

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

131 ) 

132 elif version > VERSION_COMPATIBLE: 

133 logger.warning( 

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

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

136 )