55import argparse
66import hashlib
77import json
8+ import time
9+ import urllib .error
810import urllib .request
911from pathlib import Path
1012from typing import Any
@@ -41,6 +43,28 @@ def verify_pypi_files(dist_dir: Path, payload: dict[str, Any]) -> list[dict[str,
4143 return verified
4244
4345
46+ def fetch_pypi_payload (
47+ version : str , * , attempts : int = 6 , delay_seconds : float = 10
48+ ) -> dict [str , Any ]:
49+ """Fetch a release after allowing bounded time for PyPI propagation."""
50+ if attempts < 1 :
51+ raise ValueError ("attempts must be at least 1" )
52+ url = f"https://pypi.org/pypi/specsmith/{ version } /json"
53+ for attempt in range (1 , attempts + 1 ):
54+ try :
55+ with urllib .request .urlopen (url , timeout = 30 ) as response :
56+ return json .load (response )
57+ except urllib .error .HTTPError as error :
58+ retryable = error .code == 404 or 500 <= error .code < 600
59+ if not retryable or attempt == attempts :
60+ raise
61+ except urllib .error .URLError :
62+ if attempt == attempts :
63+ raise
64+ time .sleep (delay_seconds )
65+ raise AssertionError ("unreachable" )
66+
67+
4468def main () -> int :
4569 parser = argparse .ArgumentParser (description = __doc__ )
4670 parser .add_argument ("--seal" , type = Path , required = True )
@@ -52,15 +76,12 @@ def main() -> int:
5276 parser .add_argument ("--output" , type = Path , required = True )
5377 args = parser .parse_args ()
5478
79+ seal = json .loads (args .seal .read_text (encoding = "utf-8" ))
5580 version = args .version .removeprefix ("v" )
56- with urllib .request .urlopen (
57- f"https://pypi.org/pypi/specsmith/{ version } /json" , timeout = 30
58- ) as response :
59- payload = json .load (response )
81+ payload = fetch_pypi_payload (version )
6082 if payload .get ("info" , {}).get ("version" ) != version :
6183 raise ValueError ("PyPI version response does not match the release" )
6284
63- seal = json .loads (args .seal .read_text (encoding = "utf-8" ))
6485 publication = {
6586 "version" : version ,
6687 "tag" : args .tag ,
0 commit comments