3
3
import posixpath
4
4
import re
5
5
import urllib .parse as urlparse
6
+ import warnings
7
+
8
+ from functools import cached_property
9
+ from typing import TYPE_CHECKING
6
10
7
11
from poetry .core .packages .utils .utils import path_to_url
8
12
from poetry .core .packages .utils .utils import splitext
9
13
10
14
15
+ if TYPE_CHECKING :
16
+ from collections .abc import Mapping
17
+
18
+
11
19
class Link :
12
20
def __init__ (
13
21
self ,
14
22
url : str ,
23
+ * ,
15
24
requires_python : str | None = None ,
16
- metadata : str | bool | None = None ,
25
+ hashes : Mapping [str , str ] | None = None ,
26
+ metadata : str | bool | dict [str , str ] | None = None ,
17
27
yanked : str | bool = False ,
18
28
) -> None :
19
29
"""
@@ -25,11 +35,16 @@ def __init__(
25
35
String containing the `Requires-Python` metadata field, specified
26
36
in PEP 345. This may be specified by a data-requires-python
27
37
attribute in the HTML link tag, as described in PEP 503.
38
+ hashes:
39
+ A dictionary of hash names and associated hashes of the file.
40
+ Only relevant for JSON-API (PEP 691).
28
41
metadata:
29
- String of the syntax `<hashname>=<hashvalue>` representing the hash
30
- of the Core Metadata file. This may be specified by a
31
- data-dist-info-metadata attribute in the HTML link tag, as described
32
- in PEP 658.
42
+ One of:
43
+ - bool indicating that metadata is available
44
+ - string of the syntax `<hashname>=<hashvalue>` representing the hash
45
+ of the Core Metadata file according to PEP 658 (HTML).
46
+ - dict with hash names and associated hashes of the Core Metadata file
47
+ according to PEP 691 (JSON).
33
48
yanked:
34
49
False, if the data-yanked attribute is not present.
35
50
A string, if the data-yanked attribute has a string value.
@@ -43,6 +58,7 @@ def __init__(
43
58
44
59
self .url = url
45
60
self .requires_python = requires_python if requires_python else None
61
+ self ._hashes = hashes
46
62
47
63
if isinstance (metadata , str ):
48
64
metadata = {"true" : True , "" : False , "false" : False }.get (
@@ -96,41 +112,41 @@ def __ge__(self, other: object) -> bool:
96
112
def __hash__ (self ) -> int :
97
113
return hash (self .url )
98
114
99
- @property
115
+ @cached_property
100
116
def filename (self ) -> str :
101
117
_ , netloc , path , _ , _ = urlparse .urlsplit (self .url )
102
118
name = posixpath .basename (path .rstrip ("/" )) or netloc
103
119
name = urlparse .unquote (name )
104
120
105
121
return name
106
122
107
- @property
123
+ @cached_property
108
124
def scheme (self ) -> str :
109
125
return urlparse .urlsplit (self .url )[0 ]
110
126
111
- @property
127
+ @cached_property
112
128
def netloc (self ) -> str :
113
129
return urlparse .urlsplit (self .url )[1 ]
114
130
115
- @property
131
+ @cached_property
116
132
def path (self ) -> str :
117
133
return urlparse .unquote (urlparse .urlsplit (self .url )[2 ])
118
134
119
135
def splitext (self ) -> tuple [str , str ]:
120
136
return splitext (posixpath .basename (self .path .rstrip ("/" )))
121
137
122
- @property
138
+ @cached_property
123
139
def ext (self ) -> str :
124
140
return self .splitext ()[1 ]
125
141
126
- @property
142
+ @cached_property
127
143
def url_without_fragment (self ) -> str :
128
144
scheme , netloc , path , query , fragment = urlparse .urlsplit (self .url )
129
145
return urlparse .urlunsplit ((scheme , netloc , path , query , None ))
130
146
131
147
_egg_fragment_re = re .compile (r"[#&]egg=([^&]*)" )
132
148
133
- @property
149
+ @cached_property
134
150
def egg_fragment (self ) -> str | None :
135
151
match = self ._egg_fragment_re .search (self .url )
136
152
if not match :
@@ -139,7 +155,7 @@ def egg_fragment(self) -> str | None:
139
155
140
156
_subdirectory_fragment_re = re .compile (r"[#&]subdirectory=([^&]*)" )
141
157
142
- @property
158
+ @cached_property
143
159
def subdirectory_fragment (self ) -> str | None :
144
160
match = self ._subdirectory_fragment_re .search (self .url )
145
161
if not match :
@@ -148,20 +164,36 @@ def subdirectory_fragment(self) -> str | None:
148
164
149
165
_hash_re = re .compile (r"(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)" )
150
166
151
- @property
167
+ @cached_property
152
168
def has_metadata (self ) -> bool :
153
169
if self ._metadata is None :
154
170
return False
155
171
return bool (self ._metadata ) and (self .is_wheel or self .is_sdist )
156
172
157
- @property
173
+ @cached_property
158
174
def metadata_url (self ) -> str | None :
159
175
if self .has_metadata :
160
176
return f"{ self .url_without_fragment .split ('?' , 1 )[0 ]} .metadata"
161
177
return None
162
178
179
+ @cached_property
180
+ def metadata_hashes (self ) -> Mapping [str , str ]:
181
+ if self .has_metadata :
182
+ if isinstance (self ._metadata , dict ):
183
+ return self ._metadata
184
+ if isinstance (self ._metadata , str ):
185
+ match = self ._hash_re .search (self ._metadata )
186
+ if match :
187
+ return {match .group (1 ): match .group (2 )}
188
+ return {}
189
+
163
190
@property
164
191
def metadata_hash (self ) -> str | None :
192
+ warnings .warn (
193
+ "metadata_hash is deprecated. Use metadata_hashes instead." ,
194
+ DeprecationWarning ,
195
+ stacklevel = 2 ,
196
+ )
165
197
if self .has_metadata and isinstance (self ._metadata , str ):
166
198
match = self ._hash_re .search (self ._metadata )
167
199
if match :
@@ -170,62 +202,86 @@ def metadata_hash(self) -> str | None:
170
202
171
203
@property
172
204
def metadata_hash_name (self ) -> str | None :
205
+ warnings .warn (
206
+ "metadata_hash_name is deprecated. Use metadata_hashes instead." ,
207
+ DeprecationWarning ,
208
+ stacklevel = 2 ,
209
+ )
173
210
if self .has_metadata and isinstance (self ._metadata , str ):
174
211
match = self ._hash_re .search (self ._metadata )
175
212
if match :
176
213
return match .group (1 )
177
214
return None
178
215
216
+ @cached_property
217
+ def hashes (self ) -> Mapping [str , str ]:
218
+ if self ._hashes :
219
+ return self ._hashes
220
+ match = self ._hash_re .search (self .url )
221
+ if match :
222
+ return {match .group (1 ): match .group (2 )}
223
+ return {}
224
+
179
225
@property
180
226
def hash (self ) -> str | None :
227
+ warnings .warn (
228
+ "hash is deprecated. Use hashes instead." ,
229
+ DeprecationWarning ,
230
+ stacklevel = 2 ,
231
+ )
181
232
match = self ._hash_re .search (self .url )
182
233
if match :
183
234
return match .group (2 )
184
235
return None
185
236
186
237
@property
187
238
def hash_name (self ) -> str | None :
239
+ warnings .warn (
240
+ "hash_name is deprecated. Use hashes instead." ,
241
+ DeprecationWarning ,
242
+ stacklevel = 2 ,
243
+ )
188
244
match = self ._hash_re .search (self .url )
189
245
if match :
190
246
return match .group (1 )
191
247
return None
192
248
193
- @property
249
+ @cached_property
194
250
def show_url (self ) -> str :
195
251
return posixpath .basename (self .url .split ("#" , 1 )[0 ].split ("?" , 1 )[0 ])
196
252
197
- @property
253
+ @cached_property
198
254
def is_wheel (self ) -> bool :
199
255
return self .ext == ".whl"
200
256
201
- @property
257
+ @cached_property
202
258
def is_wininst (self ) -> bool :
203
259
return self .ext == ".exe"
204
260
205
- @property
261
+ @cached_property
206
262
def is_egg (self ) -> bool :
207
263
return self .ext == ".egg"
208
264
209
- @property
265
+ @cached_property
210
266
def is_sdist (self ) -> bool :
211
267
return self .ext in {".tar.bz2" , ".tar.gz" , ".zip" }
212
268
213
- @property
269
+ @cached_property
214
270
def is_artifact (self ) -> bool :
215
271
"""
216
272
Determines if this points to an actual artifact (e.g. a tarball) or if
217
273
it points to an "abstract" thing like a path or a VCS location.
218
274
"""
219
- if self .scheme in ( "ssh" , "git" , "hg" , "bzr" , "sftp" , "svn" ) :
275
+ if self .scheme in { "ssh" , "git" , "hg" , "bzr" , "sftp" , "svn" } :
220
276
return False
221
277
222
278
return True
223
279
224
- @property
280
+ @cached_property
225
281
def yanked (self ) -> bool :
226
282
return isinstance (self ._yanked , str ) or bool (self ._yanked )
227
283
228
- @property
284
+ @cached_property
229
285
def yanked_reason (self ) -> str :
230
286
if isinstance (self ._yanked , str ):
231
287
return self ._yanked
0 commit comments