5
5
import stat
6
6
import zipfile
7
7
from contextlib import contextmanager
8
- from typing import BinaryIO , ClassVar , Iterator , List , Tuple , Type , cast
8
+ from typing import BinaryIO , ClassVar , Iterator , List , Optional , Tuple , Type , cast
9
9
10
+ from installer .exceptions import InstallerError
10
11
from installer .records import RecordEntry , parse_record_file
11
12
from installer .utils import canonicalize_name , parse_wheel_filename
12
13
@@ -101,17 +102,32 @@ def get_contents(self) -> Iterator[WheelContentElement]:
101
102
raise NotImplementedError
102
103
103
104
104
- class _WheelFileValidationError (ValueError ):
105
+ class _WheelFileValidationError (ValueError , InstallerError ):
105
106
"""Raised when a wheel file fails validation."""
106
107
107
- def __init__ (self , issues : List [str ]) -> None : # noqa: D107
108
+ def __init__ (self , issues : List [str ]) -> None :
108
109
super ().__init__ (repr (issues ))
109
110
self .issues = issues
110
111
111
112
def __repr__ (self ) -> str :
112
113
return f"WheelFileValidationError(issues={ self .issues !r} )"
113
114
114
115
116
+ class _WheelFileBadDistInfo (ValueError , InstallerError ):
117
+ """Raised when a wheel file has issues around `.dist-info`."""
118
+
119
+ def __init__ (self , * , reason : str , filename : Optional [str ], dist_info : str ) -> None :
120
+ super ().__init__ (reason )
121
+ self .reason = reason
122
+ self .filename = filename
123
+ self .dist_info = dist_info
124
+
125
+ def __str__ (self ) -> str :
126
+ return (
127
+ f"{ self .reason } (filename={ self .filename !r} , dist_info={ self .dist_info !r} )"
128
+ )
129
+
130
+
115
131
class WheelFile (WheelSource ):
116
132
"""Implements `WheelSource`, for an existing file from the filesystem.
117
133
@@ -137,6 +153,7 @@ def __init__(self, f: zipfile.ZipFile) -> None:
137
153
version = parsed_name .version ,
138
154
distribution = parsed_name .distribution ,
139
155
)
156
+ self ._dist_info_dir : Optional [str ] = None
140
157
141
158
@classmethod
142
159
@contextmanager
@@ -148,29 +165,39 @@ def open(cls, path: "os.PathLike[str]") -> Iterator["WheelFile"]:
148
165
@property
149
166
def dist_info_dir (self ) -> str :
150
167
"""Name of the dist-info directory."""
151
- if not hasattr (self , "_dist_info_dir" ):
152
- top_level_directories = {
153
- path .split ("/" , 1 )[0 ] for path in self ._zipfile .namelist ()
154
- }
155
- dist_infos = [
156
- name for name in top_level_directories if name .endswith (".dist-info" )
157
- ]
158
-
159
- assert (
160
- len (dist_infos ) == 1
161
- ), "Wheel doesn't contain exactly one .dist-info directory"
162
- dist_info_dir = dist_infos [0 ]
163
-
164
- # NAME-VER.dist-info
165
- di_dname = dist_info_dir .rsplit ("-" , 2 )[0 ]
166
- norm_di_dname = canonicalize_name (di_dname )
167
- norm_file_dname = canonicalize_name (self .distribution )
168
- assert (
169
- norm_di_dname == norm_file_dname
170
- ), "Wheel .dist-info directory doesn't match wheel filename"
171
-
172
- self ._dist_info_dir = dist_info_dir
173
- return self ._dist_info_dir
168
+ if self ._dist_info_dir is not None :
169
+ return self ._dist_info_dir
170
+
171
+ top_level_directories = {
172
+ path .split ("/" , 1 )[0 ] for path in self ._zipfile .namelist ()
173
+ }
174
+ dist_infos = [
175
+ name for name in top_level_directories if name .endswith (".dist-info" )
176
+ ]
177
+
178
+ try :
179
+ (dist_info_dir ,) = dist_infos
180
+ except ValueError :
181
+ raise _WheelFileBadDistInfo (
182
+ reason = "Wheel doesn't contain exactly one .dist-info directory" ,
183
+ filename = self ._zipfile .filename ,
184
+ dist_info = str (sorted (dist_infos )),
185
+ ) from None
186
+
187
+ # NAME-VER.dist-info
188
+ di_dname = dist_info_dir .rsplit ("-" , 2 )[0 ]
189
+ norm_di_dname = canonicalize_name (di_dname )
190
+ norm_file_dname = canonicalize_name (self .distribution )
191
+
192
+ if norm_di_dname != norm_file_dname :
193
+ raise _WheelFileBadDistInfo (
194
+ reason = "Wheel .dist-info directory doesn't match wheel filename" ,
195
+ filename = self ._zipfile .filename ,
196
+ dist_info = dist_info_dir ,
197
+ )
198
+
199
+ self ._dist_info_dir = dist_info_dir
200
+ return dist_info_dir
174
201
175
202
@property
176
203
def dist_info_filenames (self ) -> List [str ]:
0 commit comments