3
3
import functools
4
4
import hashlib
5
5
6
- from collections import defaultdict
7
6
from contextlib import contextmanager
8
7
from pathlib import Path
9
8
from typing import TYPE_CHECKING
13
12
import requests
14
13
import requests .adapters
15
14
15
+ from packaging .metadata import parse_email
16
16
from poetry .core .constraints .version import parse_constraint
17
17
from poetry .core .packages .dependency import Dependency
18
- from poetry .core .packages .utils .link import Link
19
18
from poetry .core .utils .helpers import temporary_directory
20
19
from poetry .core .version .markers import parse_marker
21
20
22
21
from poetry .config .config import Config
22
+ from poetry .inspection .info import PackageInfo
23
23
from poetry .inspection .lazy_wheel import HTTPRangeRequestUnsupported
24
24
from poetry .inspection .lazy_wheel import metadata_from_wheel_url
25
25
from poetry .repositories .cached_repository import CachedRepository
36
36
37
37
if TYPE_CHECKING :
38
38
from packaging .utils import NormalizedName
39
+ from poetry .core .packages .utils .link import Link
39
40
40
- from poetry .inspection .info import PackageInfo
41
41
from poetry .repositories .link_sources .base import LinkSource
42
42
from poetry .utils .authenticator import RepositoryCertificateConfig
43
43
@@ -109,10 +109,9 @@ def _cached_or_downloaded_file(
109
109
)
110
110
yield filepath
111
111
112
- def _get_info_from_wheel (self , url : str ) -> PackageInfo :
112
+ def _get_info_from_wheel (self , link : Link ) -> PackageInfo :
113
113
from poetry .inspection .info import PackageInfo
114
114
115
- link = Link (url )
116
115
netloc = link .netloc
117
116
118
117
# If "lazy-wheel" is enabled and the domain supports range requests
@@ -147,17 +146,68 @@ def _get_info_from_wheel(self, url: str) -> PackageInfo:
147
146
level = "debug" ,
148
147
)
149
148
self ._supports_range_requests [netloc ] = True
150
- return self ._get_info_from_wheel (link . url )
149
+ return self ._get_info_from_wheel (link )
151
150
152
- def _get_info_from_sdist (self , url : str ) -> PackageInfo :
151
+ def _get_info_from_sdist (self , link : Link ) -> PackageInfo :
153
152
from poetry .inspection .info import PackageInfo
154
153
155
- with self ._cached_or_downloaded_file (Link ( url ) ) as filepath :
154
+ with self ._cached_or_downloaded_file (link ) as filepath :
156
155
return PackageInfo .from_sdist (filepath )
157
156
158
- def _get_info_from_urls (self , urls : dict [str , list [str ]]) -> PackageInfo :
157
+ def _get_info_from_metadata (self , link : Link ) -> PackageInfo | None :
158
+ if link .has_metadata :
159
+ try :
160
+ assert link .metadata_url is not None
161
+ response = self .session .get (link .metadata_url )
162
+ if link .metadata_hashes and (
163
+ hash_name := get_highest_priority_hash_type (
164
+ set (link .metadata_hashes .keys ()), f"{ link .filename } .metadata"
165
+ )
166
+ ):
167
+ metadata_hash = getattr (hashlib , hash_name )(
168
+ response .text .encode ()
169
+ ).hexdigest ()
170
+ if metadata_hash != link .metadata_hashes [hash_name ]:
171
+ self ._log (
172
+ f"Metadata file hash ({ metadata_hash } ) does not match"
173
+ f" expected hash ({ link .metadata_hashes [hash_name ]} )."
174
+ f" Metadata file for { link .filename } will be ignored." ,
175
+ level = "warning" ,
176
+ )
177
+ return None
178
+
179
+ metadata , _ = parse_email (response .content )
180
+ return PackageInfo .from_metadata (metadata )
181
+
182
+ except requests .HTTPError :
183
+ self ._log (
184
+ f"Failed to retrieve metadata at { link .metadata_url } " ,
185
+ level = "warning" ,
186
+ )
187
+
188
+ return None
189
+
190
+ def _get_info_from_links (
191
+ self ,
192
+ links : list [Link ],
193
+ * ,
194
+ ignore_yanked : bool = True ,
195
+ ) -> PackageInfo :
196
+ # Sort links by distribution type
197
+ wheels : list [Link ] = []
198
+ sdists : list [Link ] = []
199
+ for link in links :
200
+ if link .yanked and ignore_yanked :
201
+ # drop yanked files unless the entire release is yanked
202
+ continue
203
+ if link .is_wheel :
204
+ wheels .append (link )
205
+ elif link .filename .endswith (
206
+ (".tar.gz" , ".zip" , ".bz2" , ".xz" , ".Z" , ".tar" )
207
+ ):
208
+ sdists .append (link )
209
+
159
210
# Prefer to read data from wheels: this is faster and more reliable
160
- wheels = urls .get ("bdist_wheel" )
161
211
if wheels :
162
212
# We ought just to be able to look at any of the available wheels to read
163
213
# metadata, they all should give the same answer.
@@ -173,8 +223,7 @@ def _get_info_from_urls(self, urls: dict[str, list[str]]) -> PackageInfo:
173
223
universal_python3_wheel = None
174
224
platform_specific_wheels = []
175
225
for wheel in wheels :
176
- link = Link (wheel )
177
- m = wheel_file_re .match (link .filename )
226
+ m = wheel_file_re .match (wheel .filename )
178
227
if not m :
179
228
continue
180
229
@@ -194,13 +243,19 @@ def _get_info_from_urls(self, urls: dict[str, list[str]]) -> PackageInfo:
194
243
platform_specific_wheels .append (wheel )
195
244
196
245
if universal_wheel is not None :
197
- return self ._get_info_from_wheel (universal_wheel )
246
+ return self ._get_info_from_metadata (
247
+ universal_wheel
248
+ ) or self ._get_info_from_wheel (universal_wheel )
198
249
199
250
info = None
200
251
if universal_python2_wheel and universal_python3_wheel :
201
- info = self ._get_info_from_wheel (universal_python2_wheel )
252
+ info = self ._get_info_from_metadata (
253
+ universal_python2_wheel
254
+ ) or self ._get_info_from_wheel (universal_python2_wheel )
202
255
203
- py3_info = self ._get_info_from_wheel (universal_python3_wheel )
256
+ py3_info = self ._get_info_from_metadata (
257
+ universal_python3_wheel
258
+ ) or self ._get_info_from_wheel (universal_python3_wheel )
204
259
205
260
if info .requires_python or py3_info .requires_python :
206
261
info .requires_python = str (
@@ -250,35 +305,37 @@ def _get_info_from_urls(self, urls: dict[str, list[str]]) -> PackageInfo:
250
305
251
306
# Prefer non platform specific wheels
252
307
if universal_python3_wheel :
253
- return self ._get_info_from_wheel (universal_python3_wheel )
308
+ return self ._get_info_from_metadata (
309
+ universal_python3_wheel
310
+ ) or self ._get_info_from_wheel (universal_python3_wheel )
254
311
255
312
if universal_python2_wheel :
256
- return self ._get_info_from_wheel (universal_python2_wheel )
313
+ return self ._get_info_from_metadata (
314
+ universal_python2_wheel
315
+ ) or self ._get_info_from_wheel (universal_python2_wheel )
257
316
258
317
if platform_specific_wheels :
259
318
first_wheel = platform_specific_wheels [0 ]
260
- return self ._get_info_from_wheel (first_wheel )
319
+ return self ._get_info_from_metadata (
320
+ first_wheel
321
+ ) or self ._get_info_from_wheel (first_wheel )
261
322
262
- return self ._get_info_from_sdist (urls ["sdist" ][0 ])
323
+ return self ._get_info_from_metadata (sdists [0 ]) or self ._get_info_from_sdist (
324
+ sdists [0 ]
325
+ )
263
326
264
327
def _links_to_data (self , links : list [Link ], data : PackageInfo ) -> dict [str , Any ]:
265
328
if not links :
266
329
raise PackageNotFound (
267
330
f'No valid distribution links found for package: "{ data .name } " version:'
268
331
f' "{ data .version } "'
269
332
)
270
- urls = defaultdict ( list )
333
+
271
334
files : list [dict [str , Any ]] = []
272
335
for link in links :
273
336
if link .yanked and not data .yanked :
274
337
# drop yanked files unless the entire release is yanked
275
338
continue
276
- if link .is_wheel :
277
- urls ["bdist_wheel" ].append (link .url )
278
- elif link .filename .endswith (
279
- (".tar.gz" , ".zip" , ".bz2" , ".xz" , ".Z" , ".tar" )
280
- ):
281
- urls ["sdist" ].append (link .url )
282
339
283
340
file_hash : str | None
284
341
for hash_name in ("sha512" , "sha384" , "sha256" ):
@@ -299,7 +356,8 @@ def _links_to_data(self, links: list[Link], data: PackageInfo) -> dict[str, Any]
299
356
300
357
data .files = files
301
358
302
- info = self ._get_info_from_urls (urls )
359
+ # drop yanked files unless the entire release is yanked
360
+ info = self ._get_info_from_links (links , ignore_yanked = not data .yanked )
303
361
304
362
data .summary = info .summary
305
363
data .requires_dist = info .requires_dist
0 commit comments