Skip to content

Commit 9a540d3

Browse files
refactor: use httpx2 for API calls, drop unneeded future import
Address review feedback on the Hacktoberfest prep cron: - Switch the tracker script from urllib to httpx2, the repo's standard HTTP client, and add an install step to the workflow. - Drop 'from __future__ import annotations' (unnecessary on Python >= 3.14t).
1 parent 3d3627a commit 9a540d3

2 files changed

Lines changed: 26 additions & 30 deletions

File tree

.github/workflows/hacktoberfest_prep.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ jobs:
2424
with:
2525
python-version-file: .python-version
2626
allow-prereleases: true
27+
- name: Install dependencies
28+
run: python -m pip install --upgrade "httpx2>=2.0.1"
2729
- name: Update the tracker
2830
id: update
2931
env:

scripts/hacktoberfest_prep_update.py

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,13 @@
1919
Actions runner, so there is nothing to install.
2020
"""
2121

22-
from __future__ import annotations
23-
2422
import datetime as dt
25-
import json
2623
import os
2724
import re
2825
import sys
2926
import time
30-
import urllib.error
31-
import urllib.parse
32-
import urllib.request
27+
28+
import httpx2
3329

3430
REPO = os.environ.get("GITHUB_REPOSITORY", "TheAlgorithms/Python")
3531
TOKEN = os.environ.get("GITHUB_TOKEN", "")
@@ -45,7 +41,7 @@
4541
STATS_HEADER = "## Automated statistics"
4642

4743

48-
def _request(url: str) -> tuple[dict | list, dict]:
44+
def _request(url: str, params: dict | None = None) -> tuple[dict | list, dict]:
4945
"""GET ``url`` and return ``(json_body, headers)``, retrying on 403/rate limit."""
5046
headers = {
5147
"Accept": "application/vnd.github+json",
@@ -55,29 +51,26 @@ def _request(url: str) -> tuple[dict | list, dict]:
5551
if TOKEN:
5652
headers["Authorization"] = f"Bearer {TOKEN}"
5753
for attempt in range(4):
58-
req = urllib.request.Request(url, headers=headers) # noqa: S310
59-
try:
60-
with urllib.request.urlopen(req, timeout=30) as resp: # noqa: S310
61-
return json.load(resp), dict(resp.headers)
62-
except urllib.error.HTTPError as exc:
63-
remaining = exc.headers.get("X-RateLimit-Remaining")
64-
if exc.code in (403, 429) and remaining == "0":
65-
reset = int(exc.headers.get("X-RateLimit-Reset", "0"))
66-
wait = max(1, reset - int(time.time())) + 1
67-
print(f"Rate limited; sleeping {wait}s", file=sys.stderr)
68-
time.sleep(min(wait, 90))
69-
continue
70-
if exc.code >= 500 and attempt < 3:
71-
time.sleep(2 * (attempt + 1))
72-
continue
73-
raise
54+
resp = httpx2.get(url, params=params, headers=headers, timeout=30)
55+
if resp.is_success:
56+
return resp.json(), dict(resp.headers)
57+
remaining = resp.headers.get("X-RateLimit-Remaining")
58+
if resp.status_code in (403, 429) and remaining == "0":
59+
reset = int(resp.headers.get("X-RateLimit-Reset", "0"))
60+
wait = max(1, reset - int(time.time())) + 1
61+
print(f"Rate limited; sleeping {wait}s", file=sys.stderr)
62+
time.sleep(min(wait, 90))
63+
continue
64+
if resp.status_code >= 500 and attempt < 3:
65+
time.sleep(2 * (attempt + 1))
66+
continue
67+
resp.raise_for_status()
7468
msg = f"giving up on {url}"
7569
raise RuntimeError(msg)
7670

7771

7872
def _search_count(query: str) -> int:
79-
url = f"{API}/search/issues?q={urllib.parse.quote(query)}&per_page=1"
80-
body, _ = _request(url)
73+
body, _ = _request(f"{API}/search/issues", {"q": query, "per_page": 1})
8174
return int(body.get("total_count", 0)) # type: ignore[union-attr]
8275

8376

@@ -98,17 +91,18 @@ def top_awaiting_directories(
9891
page = 1
9992
scanned = 0
10093
while scanned < max_prs:
101-
url = (
102-
f"{API}/search/issues?q={urllib.parse.quote(query)}"
103-
f"&per_page=100&page={page}"
94+
body, _ = _request(
95+
f"{API}/search/issues",
96+
{"q": query, "per_page": 100, "page": page},
10497
)
105-
body, _ = _request(url)
10698
items = body.get("items", []) # type: ignore[union-attr]
10799
if not items:
108100
break
109101
for item in items:
110102
number = item["number"]
111-
files, _ = _request(f"{API}/repos/{REPO}/pulls/{number}/files?per_page=100")
103+
files, _ = _request(
104+
f"{API}/repos/{REPO}/pulls/{number}/files", {"per_page": 100}
105+
)
112106
dirs = set()
113107
for changed in files: # type: ignore[union-attr]
114108
parts = changed["filename"].split("/")

0 commit comments

Comments
 (0)