Skip to content

Commit 2ba4ed0

Browse files
authored
toxgen: Retry & fail if we fail to fetch PyPI data (#4251)
- try to refetch data if PyPI returns an error - if we fail after 3 tries, fail the whole script (it doesn't make sense to run it without access to up-to-date PyPI data)
1 parent 8016aab commit 2ba4ed0

File tree

2 files changed

+48
-26
lines changed

2 files changed

+48
-26
lines changed

scripts/populate_tox/populate_tox.py

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
lstrip_blocks=True,
3737
)
3838

39+
PYPI_COOLDOWN = 0.15 # seconds to wait between requests to PyPI
40+
3941
PYPI_PROJECT_URL = "https://pypi.python.org/pypi/{project}/json"
4042
PYPI_VERSION_URL = "https://pypi.python.org/pypi/{project}/{version}/json"
4143
CLASSIFIER_PREFIX = "Programming Language :: Python :: "
@@ -88,27 +90,34 @@
8890
}
8991

9092

91-
@functools.cache
92-
def fetch_package(package: str) -> dict:
93-
"""Fetch package metadata from PyPI."""
94-
url = PYPI_PROJECT_URL.format(project=package)
95-
pypi_data = requests.get(url)
93+
def fetch_url(url: str) -> Optional[dict]:
94+
for attempt in range(3):
95+
pypi_data = requests.get(url)
9696

97-
if pypi_data.status_code != 200:
98-
print(f"{package} not found")
97+
if pypi_data.status_code == 200:
98+
return pypi_data.json()
9999

100-
return pypi_data.json()
100+
backoff = PYPI_COOLDOWN * 2**attempt
101+
print(
102+
f"{url} returned an error: {pypi_data.status_code}. Attempt {attempt + 1}/3. Waiting {backoff}s"
103+
)
104+
time.sleep(backoff)
105+
106+
return None
101107

102108

103109
@functools.cache
104-
def fetch_release(package: str, version: Version) -> dict:
105-
url = PYPI_VERSION_URL.format(project=package, version=version)
106-
pypi_data = requests.get(url)
110+
def fetch_package(package: str) -> Optional[dict]:
111+
"""Fetch package metadata from PyPI."""
112+
url = PYPI_PROJECT_URL.format(project=package)
113+
return fetch_url(url)
107114

108-
if pypi_data.status_code != 200:
109-
print(f"{package} not found")
110115

111-
return pypi_data.json()
116+
@functools.cache
117+
def fetch_release(package: str, version: Version) -> Optional[dict]:
118+
"""Fetch release metadata from PyPI."""
119+
url = PYPI_VERSION_URL.format(project=package, version=version)
120+
return fetch_url(url)
112121

113122

114123
def _prefilter_releases(
@@ -229,8 +238,14 @@ def get_supported_releases(
229238
expected_python_versions = SpecifierSet(f">={MIN_PYTHON_VERSION}")
230239

231240
def _supports_lowest(release: Version) -> bool:
232-
time.sleep(0.1) # don't DoS PYPI
233-
py_versions = determine_python_versions(fetch_release(package, release))
241+
time.sleep(PYPI_COOLDOWN) # don't DoS PYPI
242+
243+
pypi_data = fetch_release(package, release)
244+
if pypi_data is None:
245+
print("Failed to fetch necessary data from PyPI. Aborting.")
246+
sys.exit(1)
247+
248+
py_versions = determine_python_versions(pypi_data)
234249
target_python_versions = TEST_SUITE_CONFIG[integration].get("python")
235250
if target_python_versions:
236251
target_python_versions = SpecifierSet(target_python_versions)
@@ -499,7 +514,11 @@ def _add_python_versions_to_release(
499514
integration: str, package: str, release: Version
500515
) -> None:
501516
release_pypi_data = fetch_release(package, release)
502-
time.sleep(0.1) # give PYPI some breathing room
517+
if release_pypi_data is None:
518+
print("Failed to fetch necessary data from PyPI. Aborting.")
519+
sys.exit(1)
520+
521+
time.sleep(PYPI_COOLDOWN) # give PYPI some breathing room
503522

504523
target_python_versions = TEST_SUITE_CONFIG[integration].get("python")
505524
if target_python_versions:
@@ -592,6 +611,9 @@ def main(fail_on_changes: bool = False) -> None:
592611

593612
# Fetch data for the main package
594613
pypi_data = fetch_package(package)
614+
if pypi_data is None:
615+
print("Failed to fetch necessary data from PyPI. Aborting.")
616+
sys.exit(1)
595617

596618
# Get the list of all supported releases
597619

tox.ini

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# The file (and all resulting CI YAMLs) then need to be regenerated via
1111
# "scripts/generate-test-files.sh".
1212
#
13-
# Last generated: 2025-04-03T11:46:44.595900+00:00
13+
# Last generated: 2025-04-08T10:33:11.499210+00:00
1414

1515
[tox]
1616
requires =
@@ -179,7 +179,7 @@ envlist =
179179

180180
{py3.7,py3.12,py3.13}-statsig-v0.55.3
181181
{py3.7,py3.12,py3.13}-statsig-v0.56.0
182-
{py3.7,py3.12,py3.13}-statsig-v0.57.1
182+
{py3.7,py3.12,py3.13}-statsig-v0.57.2
183183

184184
{py3.8,py3.12,py3.13}-unleash-v6.0.1
185185
{py3.8,py3.12,py3.13}-unleash-v6.1.0
@@ -202,7 +202,7 @@ envlist =
202202
{py3.8,py3.10,py3.11}-strawberry-v0.209.8
203203
{py3.8,py3.11,py3.12}-strawberry-v0.227.7
204204
{py3.8,py3.11,py3.12}-strawberry-v0.245.0
205-
{py3.9,py3.12,py3.13}-strawberry-v0.263.0
205+
{py3.9,py3.12,py3.13}-strawberry-v0.263.2
206206

207207

208208
# ~~~ Network ~~~
@@ -215,7 +215,7 @@ envlist =
215215
# ~~~ Tasks ~~~
216216
{py3.6,py3.7,py3.8}-celery-v4.4.7
217217
{py3.6,py3.7,py3.8}-celery-v5.0.5
218-
{py3.8,py3.12,py3.13}-celery-v5.5.0
218+
{py3.8,py3.12,py3.13}-celery-v5.5.1
219219

220220
{py3.6,py3.7}-dramatiq-v1.9.0
221221
{py3.6,py3.8,py3.9}-dramatiq-v1.12.3
@@ -260,7 +260,7 @@ envlist =
260260
{py3.8,py3.10,py3.11}-litestar-v2.0.1
261261
{py3.8,py3.11,py3.12}-litestar-v2.5.5
262262
{py3.8,py3.11,py3.12}-litestar-v2.10.0
263-
{py3.8,py3.12,py3.13}-litestar-v2.15.1
263+
{py3.8,py3.12,py3.13}-litestar-v2.15.2
264264

265265
{py3.6}-pyramid-v1.8.6
266266
{py3.6,py3.8,py3.9}-pyramid-v1.10.8
@@ -542,7 +542,7 @@ deps =
542542

543543
statsig-v0.55.3: statsig==0.55.3
544544
statsig-v0.56.0: statsig==0.56.0
545-
statsig-v0.57.1: statsig==0.57.1
545+
statsig-v0.57.2: statsig==0.57.2
546546
statsig: typing_extensions
547547

548548
unleash-v6.0.1: UnleashClient==6.0.1
@@ -574,7 +574,7 @@ deps =
574574
strawberry-v0.209.8: strawberry-graphql[fastapi,flask]==0.209.8
575575
strawberry-v0.227.7: strawberry-graphql[fastapi,flask]==0.227.7
576576
strawberry-v0.245.0: strawberry-graphql[fastapi,flask]==0.245.0
577-
strawberry-v0.263.0: strawberry-graphql[fastapi,flask]==0.263.0
577+
strawberry-v0.263.2: strawberry-graphql[fastapi,flask]==0.263.2
578578
strawberry: httpx
579579
strawberry-v0.209.8: pydantic<2.11
580580
strawberry-v0.227.7: pydantic<2.11
@@ -595,7 +595,7 @@ deps =
595595
# ~~~ Tasks ~~~
596596
celery-v4.4.7: celery==4.4.7
597597
celery-v5.0.5: celery==5.0.5
598-
celery-v5.5.0: celery==5.5.0
598+
celery-v5.5.1: celery==5.5.1
599599
celery: newrelic
600600
celery: redis
601601
py3.7-celery: importlib-metadata<5.0
@@ -683,7 +683,7 @@ deps =
683683
litestar-v2.0.1: litestar==2.0.1
684684
litestar-v2.5.5: litestar==2.5.5
685685
litestar-v2.10.0: litestar==2.10.0
686-
litestar-v2.15.1: litestar==2.15.1
686+
litestar-v2.15.2: litestar==2.15.2
687687
litestar: pytest-asyncio
688688
litestar: python-multipart
689689
litestar: requests

0 commit comments

Comments
 (0)