13
13
14
14
import jinja2
15
15
import jinja2 .sandbox
16
+ import requests
16
17
from conda_forge_feedstock_ops .container_utils import (
17
18
get_default_log_level_args ,
18
19
run_container_operation ,
@@ -140,6 +141,79 @@ def _render_jinja2(tmpl, context):
140
141
)
141
142
142
143
144
+ def _try_pypi_api (url_tmpl : str , context : MutableMapping , hash_type : str ):
145
+ if "name" not in context :
146
+ return None , None
147
+
148
+ if "version" not in context :
149
+ return None , None
150
+
151
+ if not any (
152
+ pypi_slug in url_tmpl
153
+ for pypi_slug in ["/pypi.org" , "/pypi.io" , "/files.pythonhosted.org" ]
154
+ ):
155
+ return None , None
156
+
157
+ r = requests .get (
158
+ f"https://pypi.org/simple/{ context ['name' ]} /" ,
159
+ headers = {"Accept" : "application/vnd.pypi.simple.v1+json" },
160
+ )
161
+ r .raise_for_status ()
162
+
163
+ data = r .json ()
164
+ logger .debug ("PyPI API data:\n %s" , pprint .pformat (data ))
165
+
166
+ valid_src_exts = {".tar.gz" , ".tar.bz2" , ".tar.xz" , ".zip" , ".tgz" }
167
+ finfo = None
168
+ ext = None
169
+ for _finfo in data ["files" ]:
170
+ for valid_ext in valid_src_exts :
171
+ if _finfo ["filename" ].endswith (context ["version" ] + valid_ext ):
172
+ ext = valid_ext
173
+ finfo = _finfo
174
+ break
175
+
176
+ if finfo is None or ext is None :
177
+ logger .debug (
178
+ "src dist for version %s not found in PyPI API" , context ["version" ]
179
+ )
180
+ return None , None
181
+
182
+ bn , _ = os .path .split (url_tmpl )
183
+ pypi_name = finfo ["filename" ].split (context ["version" ] + ext )[0 ]
184
+ logger .debug ("PyPI API file name: %s" , pypi_name )
185
+ name_tmpl = None
186
+ for tmpl in [
187
+ "{{ name }}" ,
188
+ "{{ name.lower() }}" ,
189
+ "{{ name.replace('-', '_') }}" ,
190
+ "{{ name.replace('_', '-') }}" ,
191
+ "{{ name.replace('-', '_').lower() }}" ,
192
+ "{{ name.replace('_', '-').lower() }}" ,
193
+ ]:
194
+ if pypi_name == _render_jinja2 (tmpl , context ) + "-" :
195
+ name_tmpl = tmpl
196
+ break
197
+
198
+ if name_tmpl is not None :
199
+ new_url_tmpl = os .path .join (bn , name_tmpl + "-" + "{{ version }}" + ext )
200
+ else :
201
+ new_url_tmpl = os .path .join (
202
+ bn , finfo ["filename" ].replace (context ["version" ], "{{ version }}" )
203
+ )
204
+
205
+ logger .debug ("new url template from PyPI API: %s" , new_url_tmpl )
206
+ url = _render_jinja2 (new_url_tmpl , context )
207
+ new_hash = _try_url_and_hash_it (url , hash_type )
208
+ if new_hash is not None :
209
+ return new_url_tmpl , new_hash
210
+
211
+ new_url_tmpl = finfo ["url" ]
212
+ new_hash = _try_url_and_hash_it (url , hash_type )
213
+ if new_hash is not None :
214
+ return new_url_tmpl , new_hash
215
+
216
+
143
217
def _get_new_url_tmpl_and_hash (url_tmpl : str , context : MutableMapping , hash_type : str ):
144
218
logger .info (
145
219
"hashing URL template: %s" ,
@@ -165,6 +239,13 @@ def _get_new_url_tmpl_and_hash(url_tmpl: str, context: MutableMapping, hash_type
165
239
new_url_tmpl = None
166
240
new_hash = None
167
241
242
+ try :
243
+ new_url_tmpl , new_hash = _try_pypi_api (url_tmpl , context , hash_type )
244
+ if new_hash is not None and new_url_tmpl is not None :
245
+ return new_url_tmpl , new_hash
246
+ except Exception as e :
247
+ logger .debug ("PyPI API url+hash update failed: %s" , repr (e ), exc_info = e )
248
+
168
249
for new_url_tmpl in gen_transformed_urls (url_tmpl ):
169
250
try :
170
251
url = _render_jinja2 (new_url_tmpl , context )
0 commit comments