@@ -79,16 +79,54 @@ def get_pip_env(platform: str, python_version: Version) -> dict[str, str]:
79
79
class PyPILink :
80
80
"""Link returned by PyPI simple API."""
81
81
82
- def __init__ (self , url : str , yanked : str | None , has_metadata : bool ) -> None :
82
+ def __init__ (
83
+ self ,
84
+ identifier : str ,
85
+ url : str ,
86
+ yanked : str | None ,
87
+ has_metadata : bool ,
88
+ require_python : str | None = None ,
89
+ ) -> None :
83
90
"""Initialize a PyPI link.
84
91
92
+ :param identifier: the project identifier
85
93
:param url: url of the resource
86
94
:param yanked: yanker data
87
95
:param has_metadata: True if metadata is directly available from PyPI
96
+ :param require_python: require python data
88
97
"""
98
+ self .identifier = identifier
89
99
self .url = url
90
100
self .yanked = yanked
101
+ self .require_python = require_python
91
102
self .has_metadata = has_metadata
103
+ self ._urlparse = urlparse (url )
104
+ self .checksum = self ._urlparse .fragment .replace ("sha256=" , "" , 1 )
105
+ self .filename = self ._urlparse .path .rpartition ("/" )[- 1 ]
106
+
107
+ py_tags = ""
108
+ abi_tags = ""
109
+ platform_tags = ""
110
+ # Retreive a package version.
111
+ if self .filename .endswith (".whl" ):
112
+ # Wheel filenames contain compatibility information
113
+ _ , version , py_tags , abi_tags , platform_tags = self .filename [:- 4 ].rsplit (
114
+ "-" , 4
115
+ )
116
+ else :
117
+ package_filename = self .filename
118
+ if any (package_filename .endswith (ext ) for ext in (".tar.gz" , ".tar.bz2" )):
119
+ # Remove .gz or .bz2
120
+ package_filename , _ = os .path .splitext (package_filename )
121
+ # Remove remaining extension
122
+ basename , ext = os .path .splitext (package_filename )
123
+ # Retrieve version
124
+ _ , version , * _ = basename .rsplit ("-" , 1 )
125
+
126
+ self .pkg_version = Version (version )
127
+ self .pkg_py_tags = py_tags .split ("." )
128
+ self .pkg_abi_tags = abi_tags .split ("." )
129
+ self .pkg_platform_tags = platform_tags .split ("." )
92
130
93
131
@property
94
132
def is_yanked (self ) -> bool :
@@ -102,11 +140,17 @@ def metadata_url(self) -> str:
102
140
103
141
def as_dict (self ) -> dict [str , None | bool | str ]:
104
142
"""Serialize the a PyPILink into a Python dict that can be dump as json."""
105
- return {
143
+ res = {
144
+ "identifier" : self .identifier ,
106
145
"url" : self .url ,
146
+ "filename" : self .filename ,
147
+ "checksum" : self .checksum ,
107
148
"yanked" : self .yanked ,
108
149
"has_metadata" : self .has_metadata ,
109
150
}
151
+ if self .require_python :
152
+ res ["require_python" ] = self .require_python
153
+ return res
110
154
111
155
@classmethod
112
156
def from_dict (cls , data : dict ) -> PyPILink :
@@ -115,30 +159,40 @@ def from_dict(cls, data: dict) -> PyPILink:
115
159
:param data: the dict to read
116
160
"""
117
161
return PyPILink (
118
- url = data ["url" ], yanked = data ["yanked" ], has_metadata = data ["has_metadata" ]
162
+ identifier = data ["identifier" ],
163
+ url = data ["url" ],
164
+ yanked = data .get ("yanked" ),
165
+ has_metadata = data ["has_metadata" ],
166
+ require_python = data .get ("require-python" ),
119
167
)
120
168
121
169
122
170
class PyPILinksParser (HTMLParser ):
123
171
"""HTML parser to parse links from the PyPI simple API."""
124
172
125
- def __init__ (self ) -> None :
173
+ def __init__ (self , identifier : str ) -> None :
126
174
"""Initialize the parser."""
127
175
super ().__init__ ()
176
+ self .identifier = identifier
128
177
self .links : list [PyPILink ] = []
129
178
130
179
def handle_starttag (self , tag : str , attrs : list [tuple [str , str | None ]]) -> None :
131
180
"""See HTMLParser doc."""
132
181
if tag == "a" :
133
182
attr_dict = dict (attrs )
134
183
assert attr_dict ["href" ] is not None
135
- self .links .append (
136
- PyPILink (
137
- url = attr_dict ["href" ],
138
- yanked = attr_dict .get ("data-yanked" ),
139
- has_metadata = "data-dist-info-metadata" in attr_dict ,
184
+ try :
185
+ self .links .append (
186
+ PyPILink (
187
+ url = attr_dict ["href" ],
188
+ yanked = attr_dict .get ("data-yanked" ),
189
+ require_python = attr_dict .get ("data-requires-python" ),
190
+ has_metadata = "data-dist-info-metadata" in attr_dict ,
191
+ identifier = self .identifier ,
192
+ )
140
193
)
141
- )
194
+ except InvalidVersion :
195
+ pass
142
196
143
197
144
198
class PyPI :
@@ -194,7 +248,7 @@ def fetch_project_links(self, name: str) -> list[PyPILink]:
194
248
logger .debug (f"fetch { identifier } links from { self .pypi_url } " )
195
249
pypi_request = requests .get (self .pypi_url + "simple/" + identifier + "/" )
196
250
pypi_request .raise_for_status ()
197
- pypi_links_parser = PyPILinksParser ()
251
+ pypi_links_parser = PyPILinksParser (identifier )
198
252
pypi_links_parser .feed (pypi_request .text )
199
253
200
254
# Update cache
@@ -282,31 +336,12 @@ def __init__(
282
336
self .cache_dir = os .path .abspath (cache_dir )
283
337
284
338
# Compute filename and extract compatibility information
285
- path = urlparse (self .url ).path
286
- self .filename = path .rpartition ("/" )[- 1 ]
287
-
288
- py_tags = ""
289
- abi_tags = ""
290
- platform_tags = ""
291
-
292
- if self .filename .endswith (".whl" ):
293
- # Wheel filenames contain compatibility information
294
- tmp , py_tags , abi_tags , platform_tags = self .filename [:- 4 ].rsplit ("-" , 3 )
295
- _ , version = tmp .split ("-" , 1 )
296
-
297
- elif self .filename .endswith (".tar.gz" ):
298
- # For sources, packages naming scheme has changed other time
299
- # (mainly handling of - and _) but we expect the format of
300
- # the package to be: <STR_WITH_SAME_LENGTH_AS_NAME>-<VERSION>.tar.gz
301
- version = self .filename [len (self .name ) + 1 : - 7 ]
302
- else :
303
- basename , ext = os .path .splitext (self .filename )
304
- name , version = basename .split ("-" )[:2 ]
339
+ self .filename = link .filename
305
340
306
- self .version = Version ( version )
307
- self .py_tags = py_tags . split ( "." )
308
- self .abi_tags = abi_tags . split ( "." )
309
- self .platform_tags = platform_tags . split ( "." )
341
+ self .version = link . pkg_version
342
+ self .py_tags = link . pkg_py_tags
343
+ self .abi_tags = link . pkg_abi_tags
344
+ self .platform_tags = link . pkg_platform_tags
310
345
311
346
# Requirements cache
312
347
self ._reqs : None | set [Requirement ] = None
@@ -632,6 +667,7 @@ def add_wheel(self, filename: str) -> None:
632
667
name = os .path .basename (filename )[:- 4 ].split ("-" )[0 ]
633
668
self .pypi .cache [canonicalize_name (name )] = [
634
669
PyPILink (
670
+ identifier = name ,
635
671
url = f"file://{ os .path .abspath (filename )} " .replace ("\\ " , "/" ),
636
672
yanked = None ,
637
673
has_metadata = False ,
0 commit comments