From 038f184473c17656e960cdcbbb4dab3de8252257 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sat, 31 Oct 2026 22:21:52 +0800 Subject: [PATCH] build: replace httpx with httpx2 in website scripts httpx has had no release since December 2024. Co-Authored-By: Claude --- pyproject.toml | 2 +- uv.lock | 60 ++++++++++++--------- website/fetch_github_stars.py | 10 ++-- website/fetch_pypi_downloads_via_clickpy.py | 4 +- website/fetch_pypi_downloads_via_pepy.py | 4 +- website/tests/test_fetch_github_stars.py | 2 +- 6 files changed, 46 insertions(+), 36 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index acdaf82b..c7024360 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ Homepage = "https://awesome-python.com/" Repository = "https://github.com/vinta/awesome-python" [dependency-groups] -build = ["httpx>=0.28.1", "jinja2>=3.1.6", "markdown-it-py>=4.2.0"] +build = ["httpx2>=2.16.0", "jinja2>=3.1.6", "markdown-it-py>=4.2.0"] lint = ["ruff>=0.16.10", "ty>=0.0.85"] test = ["pytest>=9.1.1"] preview = ["watchfiles>=1.3.0"] diff --git a/uv.lock b/uv.lock index f10fc626..af950447 100644 --- a/uv.lock +++ b/uv.lock @@ -26,12 +26,12 @@ source = { virtual = "." } [package.dev-dependencies] build = [ - { name = "httpx" }, + { name = "httpx2" }, { name = "jinja2" }, { name = "markdown-it-py" }, ] dev = [ - { name = "httpx" }, + { name = "httpx2" }, { name = "jinja2" }, { name = "markdown-it-py" }, { name = "pytest" }, @@ -54,12 +54,12 @@ test = [ [package.metadata.requires-dev] build = [ - { name = "httpx", specifier = ">=0.28.1" }, + { name = "httpx2", specifier = ">=2.16.0" }, { name = "jinja2", specifier = ">=3.1.6" }, { name = "markdown-it-py", specifier = ">=4.2.0" }, ] dev = [ - { name = "httpx", specifier = ">=0.28.1" }, + { name = "httpx2", specifier = ">=2.16.0" }, { name = "jinja2", specifier = ">=3.1.6" }, { name = "markdown-it-py", specifier = ">=4.2.0" }, { name = "pytest", specifier = ">=9.1.1" }, @@ -74,15 +74,6 @@ lint = [ preview = [{ name = "watchfiles", specifier = ">=1.3.0" }] test = [{ name = "pytest", specifier = ">=9.1.1" }] -[[package]] -name = "certifi" -version = "2026.10.19" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -102,31 +93,41 @@ wheels = [ ] [[package]] -name = "httpcore" -version = "1.0.9" +name = "httpcore2" +version = "2.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "certifi" }, { name = "h11" }, + { name = "truststore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/61/afa9d6b7452f9eca73f73d45324eb0bb52f098fd5e6049bf6b8263be9b4e/httpcore2-2.16.0.tar.gz", hash = "sha256:c72cd0413563b17e1cd8354677dff887466f8ec2551679759e93126534428643", size = 69886, upload-time = "2026-10-21T07:09:45.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, + { url = "https://files.pythonhosted.org/packages/af/c2/ce417c397afa5181f0e3c6ff01a4d01639414e1adef08730d97571297151/httpcore2-2.16.0-py3-none-any.whl", hash = "sha256:5c4358319f2602d984ad93182c9fee5160f22e9da7babdda870a5e7c4e2dc34f", size = 85070, upload-time = "2026-10-21T07:09:42.802Z" }, ] [[package]] -name = "httpx" -version = "0.28.1" +name = "httpx2" +version = "2.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, + { name = "anyio", marker = "sys_platform != 'emscripten'" }, + { name = "httpcore2", marker = "sys_platform != 'emscripten'" }, + { name = "httpx2-jsfetch", marker = "sys_platform == 'emscripten'" }, { name = "idna" }, + { name = "truststore", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/73/5ca2cedf5e4e50423d1c258877865a6b62793633b561c58499f8aeb33b7e/httpx2-2.16.0.tar.gz", hash = "sha256:0d54c98128e8f2ff7fbbde0fca8c359e5f032edeaaaac443cdb7f31f685bcd7f", size = 103116, upload-time = "2026-10-21T07:09:46.348Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, + { url = "https://files.pythonhosted.org/packages/7b/3d/2e4bc4fd1ef9770871669adb348d612a6e4786b303a23f9935b81c48dd60/httpx2-2.16.0-py3-none-any.whl", hash = "sha256:fd2c5d7909e8d86fe92d53020c310ca0e4e431b838c207c5754b9bbf0308d49a", size = 97880, upload-time = "2026-10-21T07:09:44.01Z" }, +] + +[[package]] +name = "httpx2-jsfetch" +version = "1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" }, ] [[package]] @@ -326,6 +327,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fc/05/c16957eb287c3fc062e032619a25868d93d408a844b725bcf514f7a378ff/ruff-0.16.10-py3-none-win_arm64.whl", hash = "sha256:25a65fe998c4e6861ec079ada5826a2fc605e6cbccbe9dcd7fac1f54e791621b", size = 10366440, upload-time = "2026-10-01T18:03:19.04Z" }, ] +[[package]] +name = "truststore" +version = "0.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/9f/c5201d42a484c061e528825fc8e2d565f5abd50a4ced6fb7d29c4ec99b2b/truststore-0.10.5.tar.gz", hash = "sha256:30d36967ccaded5cbb38d602c433f53600036c79d502f4533a49b60a03bbefcd", size = 28091, upload-time = "2026-10-12T22:27:31.808Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e9/3a7820be2bb0fe53b6bc9c3be26d3d1158004e4c3ab953aa6840b955b1e9/truststore-0.10.5-py3-none-any.whl", hash = "sha256:9aaaedaefaf06d8b206278cf8b5012bc897f485a874503501e12d776df78951c", size = 19017, upload-time = "2026-10-12T22:27:30.377Z" }, +] + [[package]] name = "ty" version = "0.0.85" diff --git a/website/fetch_github_stars.py b/website/fetch_github_stars.py index 62815a17..1db96426 100755 --- a/website/fetch_github_stars.py +++ b/website/fetch_github_stars.py @@ -15,7 +15,7 @@ from datetime import UTC, datetime, timedelta from itertools import batched from pathlib import Path -import httpx +import httpx2 from build import extract_github_repo, load_stars @@ -84,7 +84,7 @@ def parse_graphql_response( return result -def fetch_batch(repos: Sequence[str], client: httpx.Client) -> dict[str, dict]: +def fetch_batch(repos: Sequence[str], client: httpx2.Client) -> dict[str, dict]: """Fetch star data for a batch of repos via GitHub GraphQL API.""" query = build_graphql_query(repos) if not query: @@ -142,9 +142,9 @@ def main() -> None: now_iso = now.isoformat() total_batches = (len(to_fetch) + BATCH_SIZE - 1) // BATCH_SIZE - with httpx.Client( + with httpx2.Client( headers={"Authorization": f"bearer {token}", "Content-Type": "application/json"}, - transport=httpx.HTTPTransport(retries=2, proxy=os.environ.get("HTTPS_PROXY") or None), + transport=httpx2.HTTPTransport(retries=2, proxy=os.environ.get("HTTPS_PROXY") or None), timeout=30, ) as client: for batch_num, batch in enumerate(batched(to_fetch, BATCH_SIZE), 1): @@ -152,7 +152,7 @@ def main() -> None: try: results = fetch_batch(batch, client) - except httpx.HTTPStatusError as e: + except httpx2.HTTPStatusError as e: print(f"HTTP error {e.response.status_code}", file=sys.stderr) if e.response.status_code == 401: print("Error: Invalid GITHUB_TOKEN.", file=sys.stderr) diff --git a/website/fetch_pypi_downloads_via_clickpy.py b/website/fetch_pypi_downloads_via_clickpy.py index dded71bc..4e21f350 100755 --- a/website/fetch_pypi_downloads_via_clickpy.py +++ b/website/fetch_pypi_downloads_via_clickpy.py @@ -36,7 +36,7 @@ import re from datetime import UTC, datetime from pathlib import Path -import httpx +import httpx2 from readme_parser import parse_readme @@ -86,7 +86,7 @@ def collect_names(readme_text: str) -> list[str]: def fetch_clickpy(names: list[str]) -> dict[str, int]: in_list = ", ".join(f"'{name}'" for name in names) query = f"SELECT project, sum(count) AS downloads FROM pypi.pypi_downloads_per_day WHERE project IN ({in_list}) AND date >= today() - 30 GROUP BY project FORMAT JSON" - resp = httpx.post(CLICKPY_URL, content=query, timeout=60) + resp = httpx2.post(CLICKPY_URL, content=query, timeout=60) resp.raise_for_status() return {row["project"]: int(row["downloads"]) for row in resp.json()["data"]} diff --git a/website/fetch_pypi_downloads_via_pepy.py b/website/fetch_pypi_downloads_via_pepy.py index 2a2c4dbe..b12ff327 100755 --- a/website/fetch_pypi_downloads_via_pepy.py +++ b/website/fetch_pypi_downloads_via_pepy.py @@ -22,7 +22,7 @@ import sys import time from pathlib import Path -import httpx +import httpx2 from fetch_pypi_downloads_via_clickpy import resolve @@ -60,7 +60,7 @@ def main() -> None: print(f"{arg}: not pip-installable per pypi_name_overrides.json, skipping", file=sys.stderr) else: names.append(pkg) - with httpx.Client(headers={"X-API-Key": load_api_key()}, timeout=30) as client: + with httpx2.Client(headers={"X-API-Key": load_api_key()}, timeout=30) as client: for i, name in enumerate(names): if i: time.sleep(SECONDS_BETWEEN_REQUESTS) diff --git a/website/tests/test_fetch_github_stars.py b/website/tests/test_fetch_github_stars.py index 14e888c6..33a862b3 100644 --- a/website/tests/test_fetch_github_stars.py +++ b/website/tests/test_fetch_github_stars.py @@ -254,7 +254,7 @@ class TestMainSkipsFreshCache: mock_client.__enter__ = MagicMock(return_value=mock_client) mock_client.__exit__ = MagicMock(return_value=False) mock_client.post.return_value = mock_response - monkeypatch.setattr("fetch_github_stars.httpx.Client", lambda **kwargs: mock_client) + monkeypatch.setattr("fetch_github_stars.httpx2.Client", lambda **kwargs: mock_client) main()