From e9377632cc2388d530731f9ce02d3f3d907f56c9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 07:21:20 +0000 Subject: [PATCH 1/6] Deploy the Python packages the tests run against Pywrangler 1.9.3 vendored `[project] dependencies` by resolving an unpinned `fastapi` afresh at every deploy, ignoring uv.lock. CI tested fastapi 0.136.1, starlette 1.0.0 and pydantic 2.13.3, while a deploy shipped whatever was newest that day (fastapi 0.141.1, starlette 1.7.0, pydantic 2.10.6). Pywrangler 1.17.4 resolves production packages into a committed, hash-pinned pylock.toml and reuses it on later syncs. uv.lock now pins every shared package to the pylock.toml version, so the suite runs against exactly what ships (starlette 1.7.0 also clears the five advisories against the previously tested 1.0.0). - tests/test_dependency_locks.py fails when the two locks disagree. - CI and `make deploy` fail if pywrangler's sync would change the committed pylock.toml. - `make upgrade-runtime-deps` refreshes both locks together via scripts/align_runtime_lock.py. Pywrangler 1.17 requires uv 0.12.3 or newer; the README says so. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0112H6AesYWoaS2XdDEHUJ4b --- .github/workflows/verify.yml | 2 + Makefile | 8 ++- README.md | 18 +++++- pylock.toml | 61 ++++++++++++++++++++ scripts/align_runtime_lock.py | 30 ++++++++++ tests/test_dependency_locks.py | 41 +++++++++++++ uv.lock | 101 ++++++++++++++++----------------- 7 files changed, 206 insertions(+), 55 deletions(-) create mode 100644 pylock.toml create mode 100755 scripts/align_runtime_lock.py create mode 100644 tests/test_dependency_locks.py diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 5d5129a..9af00c1 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -51,6 +51,8 @@ jobs: CHROME_PATH: /usr/bin/google-chrome run: | npm audit --audit-level=high + # pywrangler dev re-resolved pylock.toml above; production must match the commit. + git diff --exit-code -- pylock.toml make verify scripts/format_examples.py --check make verify-python-version VERSION=3.13 diff --git a/Makefile b/Makefile index 8c64815..5e592ef 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PY := uv run --python 3.13 NODE_DEPS_STAMP := node_modules/.package-lock.json -.PHONY: check-node-version node-deps test embed-examples embed-editorial-registry build-search-index build check-generated fingerprint prototypes browser-layout-test search-ranking-test social-cards check-social-cards seo-cache-lint verify-examples check-registry-integrity check-confusable-pairs check-broad-surface-tours check-footgun-coverage check-notes-supported check-program-covers-cells check-prose-duplication check-inline-links score-example-criteria check-quality-scores check-no-figure-rationales check-journey-outcomes audit-example-graph quality-checks rubric-audit format-examples verify-python-version verify smoke-deployment dev deploy lint +.PHONY: check-node-version node-deps test embed-examples embed-editorial-registry build-search-index build check-generated fingerprint prototypes browser-layout-test search-ranking-test social-cards check-social-cards seo-cache-lint verify-examples check-registry-integrity check-confusable-pairs check-broad-surface-tours check-footgun-coverage check-notes-supported check-program-covers-cells check-prose-duplication check-inline-links score-example-criteria check-quality-scores check-no-figure-rationales check-journey-outcomes audit-example-graph quality-checks rubric-audit format-examples verify-python-version verify smoke-deployment dev deploy upgrade-runtime-deps lint check-node-version: @major="$$(node -p 'process.versions.node.split(".")[0]')"; \ @@ -125,4 +125,10 @@ smoke-deployment: deploy: node-deps check-generated uv run --group workers pywrangler sync --force + git diff --exit-code -- pylock.toml uv run --group workers pywrangler deploy + +# Production vendors pylock.toml; tests run against uv.lock. Refresh both together. +upgrade-runtime-deps: node-deps + uv run --group workers pywrangler sync --force --upgrade + $(PY) scripts/align_runtime_lock.py diff --git a/README.md b/README.md index 2dd69bd..c05521e 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ Python documentation links point to the official [Python documentation](https:// - `src/examples.py` is a compatibility shim for the loaded catalog. - `src/example_sources_data.py` is generated embedded source data for Cloudflare Workers. - `public/` contains static assets served by Workers Assets. +- `pylock.toml` is the Pyodide-targeted lock Pywrangler vendors into production; `uv.lock` pins the same runtime versions for the test suite. - `python_modules/` is generated by the Workers tooling and intentionally ignored by Git. Example runs are executed by Dynamic Workers. The parent Worker creates a Dynamic Worker module from the submitted code, disables outbound network access, and keys Worker Loader reuse by Python version, example slug, and code hash. @@ -68,7 +69,7 @@ This project is developed with red-green-refactor TDD: 2. Implement the smallest change that makes it pass. 3. Refactor while keeping tests green. -Use Node 22 (Pywrangler's Pyodide runtime is not compatible with Node 26), then install the exact committed Python and Node dependency sets: +Use Node 22 (Pywrangler's Pyodide runtime is not compatible with Node 26) and uv 0.12.3 or newer (Pywrangler's minimum), then install the exact committed Python and Node dependency sets: ```bash node --version # v22.x @@ -173,7 +174,18 @@ scripts/format_examples.py --check make deploy ``` -`make deploy` first runs `make check-generated`, which rebuilds and rejects any generated output not committed to the branch. It then syncs the ignored Python Workers dependency bundle before Wrangler deploys. +`make deploy` first runs `make check-generated`, which rebuilds and rejects any generated output not committed to the branch. It then syncs the ignored Python Workers dependency bundle from the committed `pylock.toml`, refusing to deploy if the sync would change that lock, before Wrangler deploys. + +## Updating dependencies + +Production Python packages come from `pylock.toml`, which Pywrangler resolves against Pyodide wheels; the tests run against `uv.lock`, and `tests/test_dependency_locks.py` fails if the two disagree on any shared package. Refresh both together: + +```bash +make upgrade-runtime-deps +make verify +``` + +Dependabot proposes npm, GitHub Actions, and development-tool updates weekly. It skips runtime packages (their versions depend on which Pyodide wheels exist) and Ruff (upgrades are deliberate because they change lint rules). The browser libraries loaded from esm.sh are pinned in `public/editor.js` and `public/syntax-highlight.js` and are updated by hand. ## Contributing @@ -234,5 +246,5 @@ Use the active Cloudflare-supported Python version. - Dynamic Workers run with `globalOutbound: null` and tight CPU/subrequest limits. - POST example runs carry `Cache-Control: no-store` and are never cached. - Wrangler is an exact dev dependency in `package-lock.json`; `make dev` and `make deploy` require Node 22 and install/use that local version through Pywrangler. -- `make deploy` forces a fresh Pywrangler vendor sync so an empty or stale `python_modules/` directory cannot produce a dependency-free upload. +- `make deploy` forces a fresh Pywrangler vendor sync so an empty or stale `python_modules/` directory cannot produce a dependency-free upload, and fails if that sync would change the committed `pylock.toml`. - Production still requires the account-level WAF/rate-limit rule documented in `docs/turnstile-runner-protection-spec.md`; repository settings alone do not prove that external control is active. diff --git a/pylock.toml b/pylock.toml new file mode 100644 index 0000000..f66470c --- /dev/null +++ b/pylock.toml @@ -0,0 +1,61 @@ +lock-version = "1.0" +created-by = "uv" +requires-python = ">=3.13.2" + +[[packages]] +name = "annotated-doc" +version = "0.0.5" +wheels = [{ url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", upload-time = 2026-07-28T13:50:57Z, size = 5302, hashes = { sha256 = "117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101" } }] + +[[packages]] +name = "annotated-types" +version = "0.8.0" +wheels = [{ url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", upload-time = 2026-07-23T20:16:12Z, size = 13427, hashes = { sha256 = "f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0" } }] + +[[packages]] +name = "anyio" +version = "4.15.1" +wheels = [{ url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", upload-time = 2026-09-05T10:42:37Z, size = 132079, hashes = { sha256 = "6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101" } }] + +[[packages]] +name = "fastapi" +version = "0.141.1" +wheels = [{ url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", upload-time = 2026-07-29T17:18:04Z, size = 131954, hashes = { sha256 = "bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3" } }] + +[[packages]] +name = "idna" +version = "3.20" +wheels = [{ url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", upload-time = 2026-09-17T14:11:03Z, size = 69583, hashes = { sha256 = "ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c" } }] + +[[packages]] +name = "pydantic" +version = "2.10.6" +wheels = [ + { url = "https://cdn.jsdelivr.net/pyodide/v0.28.3/full/pydantic-2.10.6-py3-none-any.whl", hashes = { sha256 = "6773062f6635c42f052e4e836edd4ff433c0718b2dee6714d684786e59b756eb" } }, + { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hashes = { sha256 = "427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584" } }, +] + +[[packages]] +name = "pydantic-core" +version = "2.27.2" +wheels = [{ url = "https://cdn.jsdelivr.net/pyodide/v0.28.3/full/pydantic_core-2.27.2-cp313-cp313-pyodide_2025_0_wasm32.whl", hashes = { sha256 = "3695112ef99b222e48e0a2837b055ec818b43ff5e2e0802e99f5d6d2dd36af7b" } }] + +[[packages]] +name = "starlette" +version = "1.7.0" +wheels = [{ url = "https://files.pythonhosted.org/packages/4e/d6/1ec1b290f9e0fb067899b61e1d37a30c923068bad260b216dbe37a7d2967/starlette-1.7.0-py3-none-any.whl", upload-time = 2026-09-23T07:30:24Z, size = 78980, hashes = { sha256 = "67f8e99895493dd2911a03f11314af6ceebeae4e704bb9f43dfc6a9db151c93e" } }] + +[[packages]] +name = "typing-extensions" +version = "4.16.0" +wheels = [{ url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", upload-time = 2026-07-02T08:40:04Z, size = 45571, hashes = { sha256 = "481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8" } }] + +[[packages]] +name = "typing-inspection" +version = "0.4.4" +wheels = [{ url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", upload-time = 2026-08-12T12:37:24Z, size = 14750, hashes = { sha256 = "65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147" } }] + +[[packages]] +name = "workers-runtime-sdk" +version = "1.9.0" +wheels = [{ url = "https://files.pythonhosted.org/packages/94/3d/053deb135d4db2ef6f6c3ccdeb86d7829de3c271f7ae02cec73da8f65891/workers_runtime_sdk-1.9.0-py3-none-any.whl", upload-time = 2026-09-18T21:08:37Z, size = 38585, hashes = { sha256 = "36209f94984686264db4e1fd410d51ac3460f2a2ab3cc29869699b975e1e1331" } }] diff --git a/scripts/align_runtime_lock.py b/scripts/align_runtime_lock.py new file mode 100755 index 0000000..61ef44c --- /dev/null +++ b/scripts/align_runtime_lock.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +"""Pin uv.lock's runtime packages to the versions pylock.toml deploys. + +Production installs from `pylock.toml`, which pywrangler resolves against +Pyodide wheels. The test suite runs against `uv.lock`. This script moves +every package the two files share to the pylock.toml version so tests +exercise exactly what ships; `tests/test_dependency_locks.py` fails +whenever they drift apart. +""" +from __future__ import annotations + +import subprocess +import sys +import tomllib + +from _common import ROOT + + +def main() -> int: + pylock = tomllib.loads((ROOT / "pylock.toml").read_text()) + pins = [f"{package['name']}=={package['version']}" for package in pylock["packages"]] + command = ["uv", "lock"] + for pin in pins: + command += ["--upgrade-package", pin] + print("Pinning uv.lock to pylock.toml:", ", ".join(pins)) + return subprocess.run(command, cwd=ROOT, check=False).returncode + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/test_dependency_locks.py b/tests/test_dependency_locks.py new file mode 100644 index 0000000..90c80bd --- /dev/null +++ b/tests/test_dependency_locks.py @@ -0,0 +1,41 @@ +"""Contracts that keep the tested Python environment identical to production.""" + +import tomllib +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + + +def _locked_versions(path: Path) -> dict[str, str]: + data = tomllib.loads(path.read_text()) + return {package["name"]: package["version"] for package in data.get("package", [])} + + +class DependencyLockTests(unittest.TestCase): + def test_uv_lock_tests_exactly_what_pylock_deploys(self): + """Every package pywrangler vendors is tested at the vendored version.""" + deployed = tomllib.loads((ROOT / "pylock.toml").read_text())["packages"] + tested = _locked_versions(ROOT / "uv.lock") + self.assertTrue(deployed) + drift = { + package["name"]: (package["version"], tested.get(package["name"])) + for package in deployed + if tested.get(package["name"]) != package["version"] + } + self.assertEqual( + drift, + {}, + "uv.lock differs from pylock.toml as {name: (deployed, tested)}; " + "run scripts/align_runtime_lock.py", + ) + + def test_deploys_and_ci_use_the_committed_pylock(self): + makefile = (ROOT / "Makefile").read_text() + verify_workflow = (ROOT / ".github" / "workflows" / "verify.yml").read_text() + self.assertIn("git diff --exit-code -- pylock.toml", makefile) + self.assertIn("git diff --exit-code -- pylock.toml", verify_workflow) + + +if __name__ == "__main__": + unittest.main() diff --git a/uv.lock b/uv.lock index 29216a2..760e92e 100644 --- a/uv.lock +++ b/uv.lock @@ -4,32 +4,33 @@ requires-python = "==3.13.*" [[package]] name = "annotated-doc" -version = "0.0.4" +version = "0.0.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, + { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" }, ] [[package]] name = "annotated-types" -version = "0.7.0" +version = "0.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, ] [[package]] name = "anyio" -version = "4.13.0" +version = "4.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, + { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" }, ] [[package]] @@ -55,7 +56,7 @@ wheels = [ [[package]] name = "fastapi" -version = "0.136.1" +version = "0.141.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -64,9 +65,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/45/c130091c2dfa061bbfe3150f2a5091ef1adf149f2a8d2ae769ecaf6e99a2/fastapi-0.136.1.tar.gz", hash = "sha256:7af665ad7acfa0a3baf8983d393b6b471b9da10ede59c60045f49fbc89a0fa7f", size = 397448, upload-time = "2026-04-23T16:49:44.046Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/02/91e3416a8fdd715abb903a952a6bec7cdd8d14eed55d415fc8595524c319/fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1", size = 425799, upload-time = "2026-07-29T17:18:05.568Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/ff/2e4eca3ade2c22fe1dea7043b8ee9dabe47753349eb1b56a202de8af6349/fastapi-0.136.1-py3-none-any.whl", hash = "sha256:a6e9d7eeada96c93a4d69cb03836b44fa34e2854accb7244a1ece36cd4781c3f", size = 117683, upload-time = "2026-04-23T16:49:42.437Z" }, + { url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954, upload-time = "2026-07-29T17:18:04.364Z" }, ] [[package]] @@ -121,11 +122,11 @@ wheels = [ [[package]] name = "idna" -version = "3.13" +version = "3.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, + { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, ] [[package]] @@ -184,43 +185,41 @@ wheels = [ [[package]] name = "pydantic" -version = "2.13.3" +version = "2.10.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, - { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/e4/40d09941a2cebcb20609b86a559817d5b9291c49dd6f8c87e5feffbe703a/pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d", size = 844068, upload-time = "2026-04-20T14:46:43.632Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681, upload-time = "2025-01-24T01:42:12.693Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927", size = 471981, upload-time = "2026-04-20T14:46:41.402Z" }, + { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696, upload-time = "2025-01-24T01:42:10.371Z" }, ] [[package]] name = "pydantic-core" -version = "2.46.3" +version = "2.27.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2a/ef/f7abb56c49382a246fd2ce9c799691e3c3e7175ec74b14d99e798bcddb1a/pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c", size = 471412, upload-time = "2026-04-20T14:40:56.672Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443, upload-time = "2024-12-18T11:31:54.917Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/3c/9b5e8eb9821936d065439c3b0fb1490ffa64163bfe7e1595985a47896073/pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37", size = 2102109, upload-time = "2026-04-20T14:41:24.219Z" }, - { url = "https://files.pythonhosted.org/packages/91/97/1c41d1f5a19f241d8069f1e249853bcce378cdb76eec8ab636d7bc426280/pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f", size = 1951820, upload-time = "2026-04-20T14:42:14.236Z" }, - { url = "https://files.pythonhosted.org/packages/30/b4/d03a7ae14571bc2b6b3c7b122441154720619afe9a336fa3a95434df5e2f/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8", size = 1977785, upload-time = "2026-04-20T14:42:31.648Z" }, - { url = "https://files.pythonhosted.org/packages/ae/0c/4086f808834b59e3c8f1aa26df8f4b6d998cdcf354a143d18ef41529d1fe/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad", size = 2062761, upload-time = "2026-04-20T14:40:37.093Z" }, - { url = "https://files.pythonhosted.org/packages/fa/71/a649be5a5064c2df0db06e0a512c2281134ed2fcc981f52a657936a7527c/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c", size = 2232989, upload-time = "2026-04-20T14:42:59.254Z" }, - { url = "https://files.pythonhosted.org/packages/a2/84/7756e75763e810b3a710f4724441d1ecc5883b94aacb07ca71c5fb5cfb69/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6cdf19bf84128d5e7c37e8a73a0c5c10d51103a650ac585d42dd6ae233f2b7f", size = 2303975, upload-time = "2026-04-20T14:41:32.287Z" }, - { url = "https://files.pythonhosted.org/packages/6c/35/68a762e0c1e31f35fa0dac733cbd9f5b118042853698de9509c8e5bf128b/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031bb17f4885a43773c8c763089499f242aee2ea85cf17154168775dccdecf35", size = 2095325, upload-time = "2026-04-20T14:42:47.685Z" }, - { url = "https://files.pythonhosted.org/packages/77/bf/1bf8c9a8e91836c926eae5e3e51dce009bf495a60ca56060689d3df3f340/pydantic_core-2.46.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:bcf2a8b2982a6673693eae7348ef3d8cf3979c1d63b54fca7c397a635cc68687", size = 2133368, upload-time = "2026-04-20T14:41:22.766Z" }, - { url = "https://files.pythonhosted.org/packages/e5/50/87d818d6bab915984995157ceb2380f5aac4e563dddbed6b56f0ed057aba/pydantic_core-2.46.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28e8cf2f52d72ced402a137145923a762cbb5081e48b34312f7a0c8f55928ec3", size = 2173908, upload-time = "2026-04-20T14:42:52.044Z" }, - { url = "https://files.pythonhosted.org/packages/91/88/a311fb306d0bd6185db41fa14ae888fb81d0baf648a761ae760d30819d33/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022", size = 2186422, upload-time = "2026-04-20T14:43:29.55Z" }, - { url = "https://files.pythonhosted.org/packages/8f/79/28fd0d81508525ab2054fef7c77a638c8b5b0afcbbaeee493cf7c3fef7e1/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23", size = 2332709, upload-time = "2026-04-20T14:42:16.134Z" }, - { url = "https://files.pythonhosted.org/packages/b3/21/795bf5fe5c0f379308b8ef19c50dedab2e7711dbc8d0c2acf08f1c7daa05/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7", size = 2372428, upload-time = "2026-04-20T14:41:10.974Z" }, - { url = "https://files.pythonhosted.org/packages/45/b3/ed14c659cbe7605e3ef063077680a64680aec81eb1a04763a05190d49b7f/pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13", size = 1965601, upload-time = "2026-04-20T14:41:42.128Z" }, - { url = "https://files.pythonhosted.org/packages/ef/bb/adb70d9a762ddd002d723fbf1bd492244d37da41e3af7b74ad212609027e/pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0", size = 2071517, upload-time = "2026-04-20T14:43:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/52/eb/66faefabebfe68bd7788339c9c9127231e680b11906368c67ce112fdb47f/pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec", size = 2035802, upload-time = "2026-04-20T14:43:38.507Z" }, + { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709, upload-time = "2024-12-18T11:29:03.193Z" }, + { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273, upload-time = "2024-12-18T11:29:05.306Z" }, + { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027, upload-time = "2024-12-18T11:29:07.294Z" }, + { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888, upload-time = "2024-12-18T11:29:09.249Z" }, + { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738, upload-time = "2024-12-18T11:29:11.23Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138, upload-time = "2024-12-18T11:29:16.396Z" }, + { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025, upload-time = "2024-12-18T11:29:20.25Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633, upload-time = "2024-12-18T11:29:23.877Z" }, + { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404, upload-time = "2024-12-18T11:29:25.872Z" }, + { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130, upload-time = "2024-12-18T11:29:29.252Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946, upload-time = "2024-12-18T11:29:31.338Z" }, + { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387, upload-time = "2024-12-18T11:29:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453, upload-time = "2024-12-18T11:29:35.533Z" }, + { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186, upload-time = "2024-12-18T11:29:37.649Z" }, ] [[package]] @@ -364,40 +363,40 @@ wheels = [ [[package]] name = "starlette" -version = "1.0.0" +version = "1.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/2b/3850dc6bf7ef71b088962eba31dafc6cffd2f96e577ebb0bb316df96da3e/starlette-1.7.0.tar.gz", hash = "sha256:c79f74ea63cff761804fbbfb182f1e0b440c2d07b164d24700c5a1bab5d6ff5d", size = 2736246, upload-time = "2026-09-23T07:30:26.35Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d6/1ec1b290f9e0fb067899b61e1d37a30c923068bad260b216dbe37a7d2967/starlette-1.7.0-py3-none-any.whl", hash = "sha256:67f8e99895493dd2911a03f11314af6ceebeae4e704bb9f43dfc6a9db151c93e", size = 78980, upload-time = "2026-09-23T07:30:24.567Z" }, ] [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] [[package]] name = "typing-inspection" -version = "0.4.2" +version = "0.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, ] [[package]] name = "workers-py" -version = "1.9.3" +version = "1.17.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -407,16 +406,16 @@ dependencies = [ { name = "rich" }, { name = "workers-runtime-sdk" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/88/50/a4d4ad358e02439dad801deebdb4427160ce173a1c6146658def72719ddc/workers_py-1.9.3.tar.gz", hash = "sha256:92d4237d51a6199063f7792605fd39f97419f2a6cbbaf0a73782551b7e98333e", size = 66139, upload-time = "2026-04-22T17:28:24.65Z" } +sdist = { url = "https://files.pythonhosted.org/packages/99/f2/99aaf88e4a0ac5275109f4701d88b88b4067fe729bdd59e438cd03aac636/workers_py-1.17.4.tar.gz", hash = "sha256:a3ae853ba1c7d94ebba7caf0d26e64707e26cc24cc7e16eeef1b042ecee31bb9", size = 71780, upload-time = "2026-09-21T21:29:07.843Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/c4/d82a37ef525305f22eb279bae0d86781752658d8b8fa5ea56cc3fbb1bbdd/workers_py-1.9.3-py3-none-any.whl", hash = "sha256:33432107e0536fa43b36d4abe16ff888e0439a5624ed0c6a4eb1fb5fca38abae", size = 13618, upload-time = "2026-04-22T17:28:23.324Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4b/eac581b7a4dffa60a7af57e4f4d22a25d8af215531b2bcb71e12aababb41/workers_py-1.17.4-py3-none-any.whl", hash = "sha256:fd7707f9f01e502bd315c0f963abad705e74c890780f91721e08e1a29fde03a4", size = 17370, upload-time = "2026-09-21T21:29:06.51Z" }, ] [[package]] name = "workers-runtime-sdk" -version = "1.1.2" +version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e5/6b/c9f9e2e87fbe2c93f46c2a7d46c2d3584cbd5ed747dfe8c33183635e0965/workers_runtime_sdk-1.1.2.tar.gz", hash = "sha256:850c4f10bee0adc79632e6553a0c65d5ef1bc5003c3218d1fa46bc703541f859", size = 30019, upload-time = "2026-04-21T22:28:01.859Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/be/6a9677e293b632d2a7d0db97d0a4952d8787cebf12bda22ba21cb4324441/workers_runtime_sdk-1.9.0.tar.gz", hash = "sha256:0dd83ed6d6a51a312a01e66bee6e6af8e8bed42bc40caae65642f8d41ab8b7b7", size = 140950, upload-time = "2026-09-18T21:08:38.924Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/b2/31d42e5db8dfd59f3acd12872fb1718642b1049c667ca6bbd4cf2ec9da7d/workers_runtime_sdk-1.1.2-py3-none-any.whl", hash = "sha256:ce5b896cb4ebc07dd2dd1e07a90d85b188caada2ffa18ca0c0cf488b7fe05d9a", size = 17270, upload-time = "2026-04-21T22:28:00.272Z" }, + { url = "https://files.pythonhosted.org/packages/94/3d/053deb135d4db2ef6f6c3ccdeb86d7829de3c271f7ae02cec73da8f65891/workers_runtime_sdk-1.9.0-py3-none-any.whl", hash = "sha256:36209f94984686264db4e1fd410d51ac3460f2a2ab3cc29869699b975e1e1331", size = 38585, upload-time = "2026-09-18T21:08:37.773Z" }, ] From e39ff68b1848c518831d8bbe319e9a34833f0b4c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 07:21:35 +0000 Subject: [PATCH 2/6] Upgrade Pillow to 12.3 The <12 cap held the social-card tooling on Pillow 11.3.0, which has 17 published advisories (PSD, FITS, PDF, font and JPEG2000 parsing, among others), all fixed by 12.3.0. The script only opens its own generated JPEGs, so exposure was low, but the cap blocked every fix. `scripts/build_social_cards.py --check` decodes all 120 cards cleanly on 12.3.0. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0112H6AesYWoaS2XdDEHUJ4b --- pyproject.toml | 2 +- uv.lock | 43 +++++++++++++++---------------------------- 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9c7241b..863ee00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ workers = [ ] dev = [ "hypothesis>=6.0", - "pillow>=11.3,<12", + "pillow>=12.3,<13", "ruff==0.16.0", ] diff --git a/uv.lock b/uv.lock index 760e92e..5067f87 100644 --- a/uv.lock +++ b/uv.lock @@ -152,35 +152,22 @@ wheels = [ [[package]] name = "pillow" -version = "11.3.0" +version = "12.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, - { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, - { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, - { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, - { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, - { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, - { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, - { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, - { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, - { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, - { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, - { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, - { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, - { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, - { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, - { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, - { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, - { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, - { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, + { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, + { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, + { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, ] [[package]] @@ -306,7 +293,7 @@ requires-dist = [{ name = "fastapi" }] [package.metadata.requires-dev] dev = [ { name = "hypothesis", specifier = ">=6.0" }, - { name = "pillow", specifier = ">=11.3,<12" }, + { name = "pillow", specifier = ">=12.3,<13" }, { name = "ruff", specifier = "==0.16.0" }, ] workers = [ From d254fed1d0160e850da2c6d3862af0153462c7be Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 07:22:48 +0000 Subject: [PATCH 3/6] Move CI actions to their Node 24 releases Every Verify run warned that checkout@v4, setup-node@v4, setup-python@v5 and setup-uv@v5 target the deprecated Node.js 20 runtime and were being forced onto Node 24. Their current releases (checkout v7, setup-node v7, setup-python v7, setup-uv v10.2.0) run on Node 24 natively. - setup-uv no longer publishes floating major tags, so it is pinned to the v10.2.0 commit, as its README recommends. - setup-node v6+ enables npm caching automatically when package.json has a packageManager field; `package-manager-cache: false` keeps CI cache-free, matching setup-uv's `enable-cache: false`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0112H6AesYWoaS2XdDEHUJ4b --- .github/workflows/verify.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 9af00c1..a6fc19a 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -17,16 +17,17 @@ jobs: verify: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v5 + - uses: actions/checkout@v7 + - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 with: enable-cache: false - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v7 with: python-version: '3.13' - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: node-version: '22' + package-manager-cache: false - name: Install locked dependencies run: | uv sync --locked --all-groups From 438635e084348f08d6e5f3ff66fee64aae08ed1b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 07:23:40 +0000 Subject: [PATCH 4/6] Add Dependabot for npm, dev tooling, and CI actions Wrangler upgrades kept arriving as manual fixes once the high-severity npm audit gate went red (#12, and twice in #13). Dependabot now proposes weekly updates for: - npm (Wrangler), after a 3-day cooldown; - uv, limited to hypothesis, pillow and workers-py. Runtime packages stay on `make upgrade-runtime-deps`, since production vendors pylock.toml from Pyodide wheels, and Ruff upgrades stay deliberate because they add lint rules and must match `required-version`; - GitHub Actions, grouped into one PR. The toolchain contract test now asserts that Wrangler is exactly pinned and consistent across package.json and package-lock.json instead of hard-coding a version, so Dependabot's bumps can pass CI. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0112H6AesYWoaS2XdDEHUJ4b --- .github/dependabot.yml | 35 +++++++++++++++++++++++++++++++++++ tests/test_app.py | 6 ++++-- 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..35bc1e8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,35 @@ +version: 2 + +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + cooldown: + default-days: 3 + + # Development tools only. Runtime packages are refreshed with + # `make upgrade-runtime-deps`, because production vendors pylock.toml and + # their versions depend on which Pyodide wheels exist. Ruff upgrades stay + # deliberate: new releases add lint rules and must match `required-version`. + - package-ecosystem: uv + directory: / + schedule: + interval: weekly + cooldown: + default-days: 3 + allow: + - dependency-name: hypothesis + - dependency-name: pillow + - dependency-name: workers-py + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + cooldown: + default-days: 3 + groups: + github-actions: + patterns: + - "*" diff --git a/tests/test_app.py b/tests/test_app.py index ac61cfc..83762dc 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -447,9 +447,11 @@ def test_generated_drift_is_blocked_before_commit_and_merge(self): self.assertIn('"preview_urls": false', wrangler_config) package = json.loads((ROOT / "package.json").read_text()) lock = json.loads((ROOT / "package-lock.json").read_text()) - self.assertEqual(package["devDependencies"]["wrangler"], "4.137.0") + wrangler = package["devDependencies"]["wrangler"] + self.assertRegex(wrangler, r"^\d+\.\d+\.\d+$") self.assertEqual(package["engines"]["node"], "22.x") - self.assertEqual(lock["packages"][""]["devDependencies"]["wrangler"], "4.137.0") + self.assertEqual(lock["packages"][""]["devDependencies"]["wrangler"], wrangler) + self.assertEqual(lock["packages"]["node_modules/wrangler"]["version"], wrangler) makefile = (ROOT / "Makefile").read_text() self.assertIn("check-node-version", makefile) self.assertIn("pywrangler sync --force", makefile) From bde4e6caa1c42f7629314b062c0729da28efc4d3 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 07:27:35 +0000 Subject: [PATCH 5/6] Upgrade Shiki to 4.4.3 and CodeMirror to current releases The browser libraries loaded from esm.sh had fallen behind: Shiki by three major versions (1.29.2 -> 4.4.3) and CodeMirror by several minor releases (state 6.5.2 -> 6.7.6, view 6.41.1 -> 6.43.13, language 6.12.3 -> 6.12.4; view 6.43 requires state 6.7). The site uses only Shiki's `codeToHtml(source, { lang: 'python', theme })` with the GitHub light and dark themes. Rendering all 440 Python blocks from the example sources in both themes produces byte-identical HTML on 1.29.2 and 4.4.3, so highlighting and dark mode are unchanged. The screenshot tool's `--legacy-shiki` mode follows the production pin. Rebuilt fingerprinted assets and the HTML cache version. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0112H6AesYWoaS2XdDEHUJ4b --- .../{editor.3827f6497b54.js => editor.3659b44c8480.js} | 10 +++++----- public/editor.js | 10 +++++----- ...405e604fc10.js => syntax-highlight.743e2535f133.js} | 2 +- public/syntax-highlight.js | 2 +- scripts/capture_browser_screenshot.mjs | 2 +- src/asset_manifest.py | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) rename public/{editor.3827f6497b54.js => editor.3659b44c8480.js} (86%) rename public/{syntax-highlight.c405e604fc10.js => syntax-highlight.743e2535f133.js} (97%) diff --git a/public/editor.3827f6497b54.js b/public/editor.3659b44c8480.js similarity index 86% rename from public/editor.3827f6497b54.js rename to public/editor.3659b44c8480.js index 9296650..067c444 100644 --- a/public/editor.3827f6497b54.js +++ b/public/editor.3659b44c8480.js @@ -1,8 +1,8 @@ -import { Compartment, EditorState } from 'https://esm.sh/@codemirror/state@6.5.2'; -import { EditorView, lineNumbers } from 'https://esm.sh/@codemirror/view@6.41.1?deps=@codemirror/state@6.5.2'; -import { defaultHighlightStyle, syntaxHighlighting } from 'https://esm.sh/@codemirror/language@6.12.3?deps=@codemirror/state@6.5.2,@codemirror/view@6.41.1'; -import { python } from 'https://esm.sh/@codemirror/lang-python@6.2.1?deps=@codemirror/state@6.5.2,@codemirror/view@6.41.1,@codemirror/language@6.12.3'; -import { oneDarkHighlightStyle } from 'https://esm.sh/@codemirror/theme-one-dark@6.1.3?deps=@codemirror/state@6.5.2,@codemirror/view@6.41.1,@codemirror/language@6.12.3'; +import { Compartment, EditorState } from 'https://esm.sh/@codemirror/state@6.7.6'; +import { EditorView, lineNumbers } from 'https://esm.sh/@codemirror/view@6.43.13?deps=@codemirror/state@6.7.6'; +import { defaultHighlightStyle, syntaxHighlighting } from 'https://esm.sh/@codemirror/language@6.12.4?deps=@codemirror/state@6.7.6,@codemirror/view@6.43.13'; +import { python } from 'https://esm.sh/@codemirror/lang-python@6.2.1?deps=@codemirror/state@6.7.6,@codemirror/view@6.43.13,@codemirror/language@6.12.4'; +import { oneDarkHighlightStyle } from 'https://esm.sh/@codemirror/theme-one-dark@6.1.3?deps=@codemirror/state@6.7.6,@codemirror/view@6.43.13,@codemirror/language@6.12.4'; const themePreference = window.matchMedia('(prefers-color-scheme: dark)'); const highlightStyle = () => themePreference.matches ? oneDarkHighlightStyle : defaultHighlightStyle; diff --git a/public/editor.js b/public/editor.js index 9296650..067c444 100644 --- a/public/editor.js +++ b/public/editor.js @@ -1,8 +1,8 @@ -import { Compartment, EditorState } from 'https://esm.sh/@codemirror/state@6.5.2'; -import { EditorView, lineNumbers } from 'https://esm.sh/@codemirror/view@6.41.1?deps=@codemirror/state@6.5.2'; -import { defaultHighlightStyle, syntaxHighlighting } from 'https://esm.sh/@codemirror/language@6.12.3?deps=@codemirror/state@6.5.2,@codemirror/view@6.41.1'; -import { python } from 'https://esm.sh/@codemirror/lang-python@6.2.1?deps=@codemirror/state@6.5.2,@codemirror/view@6.41.1,@codemirror/language@6.12.3'; -import { oneDarkHighlightStyle } from 'https://esm.sh/@codemirror/theme-one-dark@6.1.3?deps=@codemirror/state@6.5.2,@codemirror/view@6.41.1,@codemirror/language@6.12.3'; +import { Compartment, EditorState } from 'https://esm.sh/@codemirror/state@6.7.6'; +import { EditorView, lineNumbers } from 'https://esm.sh/@codemirror/view@6.43.13?deps=@codemirror/state@6.7.6'; +import { defaultHighlightStyle, syntaxHighlighting } from 'https://esm.sh/@codemirror/language@6.12.4?deps=@codemirror/state@6.7.6,@codemirror/view@6.43.13'; +import { python } from 'https://esm.sh/@codemirror/lang-python@6.2.1?deps=@codemirror/state@6.7.6,@codemirror/view@6.43.13,@codemirror/language@6.12.4'; +import { oneDarkHighlightStyle } from 'https://esm.sh/@codemirror/theme-one-dark@6.1.3?deps=@codemirror/state@6.7.6,@codemirror/view@6.43.13,@codemirror/language@6.12.4'; const themePreference = window.matchMedia('(prefers-color-scheme: dark)'); const highlightStyle = () => themePreference.matches ? oneDarkHighlightStyle : defaultHighlightStyle; diff --git a/public/syntax-highlight.c405e604fc10.js b/public/syntax-highlight.743e2535f133.js similarity index 97% rename from public/syntax-highlight.c405e604fc10.js rename to public/syntax-highlight.743e2535f133.js index 4f9b866..9395950 100644 --- a/public/syntax-highlight.c405e604fc10.js +++ b/public/syntax-highlight.743e2535f133.js @@ -64,7 +64,7 @@ const sourceBlocks = [...document.querySelectorAll('pre code.language-python')]. if (sourceBlocks.length) { let codeToHtml = null; try { - ({ codeToHtml } = await import('https://esm.sh/shiki@1.29.2')); + ({ codeToHtml } = await import('https://esm.sh/shiki@4.4.3')); } catch (_) { // Plain server-rendered code remains readable when the optional CDN fails. } diff --git a/public/syntax-highlight.js b/public/syntax-highlight.js index 4f9b866..9395950 100644 --- a/public/syntax-highlight.js +++ b/public/syntax-highlight.js @@ -64,7 +64,7 @@ const sourceBlocks = [...document.querySelectorAll('pre code.language-python')]. if (sourceBlocks.length) { let codeToHtml = null; try { - ({ codeToHtml } = await import('https://esm.sh/shiki@1.29.2')); + ({ codeToHtml } = await import('https://esm.sh/shiki@4.4.3')); } catch (_) { // Plain server-rendered code remains readable when the optional CDN fails. } diff --git a/scripts/capture_browser_screenshot.mjs b/scripts/capture_browser_screenshot.mjs index 78dc3a5..cf7d0a7 100755 --- a/scripts/capture_browser_screenshot.mjs +++ b/scripts/capture_browser_screenshot.mjs @@ -112,7 +112,7 @@ try { await client.send('Runtime.evaluate', { awaitPromise: true, expression: `(async () => { - const { codeToHtml } = await import('https://esm.sh/shiki@1.29.2'); + const { codeToHtml } = await import('https://esm.sh/shiki@4.4.3'); for (const block of document.querySelectorAll('pre code.language-python')) { const highlighted = await codeToHtml(block.textContent, { lang: 'python', theme: 'github-light' }); const wrapper = document.createElement('div'); diff --git a/src/asset_manifest.py b/src/asset_manifest.py index 95b62f4..6db62b1 100644 --- a/src/asset_manifest.py +++ b/src/asset_manifest.py @@ -1,3 +1,3 @@ # Generated by scripts/fingerprint_assets.py. Do not edit by hand. -ASSET_PATHS = {'SITE_CSS': '/site.07c59ae07f87.css', 'SYNTAX_JS': '/syntax-highlight.c405e604fc10.js', 'EDITOR_JS': '/editor.3827f6497b54.js', 'RUNNER_JS': '/runner.96b105eef218.js', 'SEARCH_JS': '/search.e5a5822d9917.js', 'SEARCH_INDEX': '/search-index.332712b2e502.json'} -HTML_CACHE_VERSION = 'dbf5ec68f559' +ASSET_PATHS = {'SITE_CSS': '/site.07c59ae07f87.css', 'SYNTAX_JS': '/syntax-highlight.743e2535f133.js', 'EDITOR_JS': '/editor.3659b44c8480.js', 'RUNNER_JS': '/runner.96b105eef218.js', 'SEARCH_JS': '/search.e5a5822d9917.js', 'SEARCH_INDEX': '/search-index.332712b2e502.json'} +HTML_CACHE_VERSION = 'ce207aefedb8' From 467b405d32b225f989a2e28ca2f84cbe36b8e7d3 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 07:31:12 +0000 Subject: [PATCH 6/6] Record the dependency refresh in the changelog and lessons Documents the pylock.toml lock, the upgraded packages and actions, and the Dependabot policy, and records why a lockfile protects only what installs from it. The TLS lesson now names UV_SYSTEM_CERTS, since uv 0.12 (Pywrangler 1.17's minimum) deprecates UV_NATIVE_TLS. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0112H6AesYWoaS2XdDEHUJ4b --- CHANGELOG.md | 4 ++++ docs/lessons-learned.md | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6976cd..e9c2dcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/en/1.1.0 ### Fixed +- Production Python packages are locked. Pywrangler 1.17.4 vendors a committed, hash-pinned `pylock.toml` instead of resolving an unpinned `fastapi` at deploy time, and `uv.lock` pins the same versions (FastAPI 0.141.1, Starlette 1.7.0, Pydantic 2.10.6) so the test suite runs against exactly what ships. `tests/test_dependency_locks.py` fails on drift, and CI and `make deploy` fail if a sync would change `pylock.toml`. Starlette 1.7.0 also clears the five advisories against the previously tested 1.0.0. - Example-page runner wiring (Run interception, Reset, the share button, and keyboard navigation) no longer waits for the CDN-backed highlighter and editor modules: `runner.js` loads `async`, so a slow or unreachable esm.sh cannot stall it — ordered module scripts otherwise execute strictly after every preceding module settles, including their top-level awaits. ### Added @@ -32,6 +33,9 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/en/1.1.0 ### Changed +- Dependency refresh: Pillow 12.3.0 (from 11.3.0; 17 advisories), Pywrangler 1.17.4 (from 1.9.3; requires uv 0.12.3+), Shiki 4.4.3 (from 1.29.2; byte-identical output for every example block in both themes), and CodeMirror state 6.7.6, view 6.43.13, and language 6.12.4. +- CI actions moved to their Node 24 releases (checkout, setup-node, and setup-python v7; setup-uv v10.2.0 pinned by commit), clearing the Node.js 20 deprecation warning; npm caching stays off. +- Dependabot proposes weekly npm, GitHub Actions, and development-tool (Hypothesis, Pillow, Pywrangler) updates. Runtime packages refresh through `make upgrade-runtime-deps`. - The home header stays visible on landing: the scroll-driven entrance now animates the brand and a compositor-friendly veil layer instead of hiding the whole header (which left the page with no visible nav at first paint). - Body type is `100%/1.6` instead of a fixed 16px, so browser text-size settings are respected. The never-rendering brand faces (FT Kunst Grotesk, Apercu Mono Pro — installed nowhere, shipped never) are removed from the stacks, which now declare the real design: `system-ui` prose and `ui-monospace`-first code. The `/about` type specimen says so. - `prefers-reduced-transparency` and `prefers-contrast: more` get solid-chrome and defined-border fallbacks; cards press down on tap; header links carry full-height (~43px) tap targets via padding with cancelling negative margins. diff --git a/docs/lessons-learned.md b/docs/lessons-learned.md index 7b77dc3..beaa9ec 100644 --- a/docs/lessons-learned.md +++ b/docs/lessons-learned.md @@ -11,7 +11,8 @@ This document records project lessons that should guide future changes to Python - POST runs must never go through the rendered-page cache path. - For Cloudflare request geography, prefer `request.cf.colo` / `request.cf.country` when available. Do not assume the `cf-ray` header includes a colo suffix; `wrangler tail` has shown Ray IDs without `-SJC`-style suffixes. - Structured application logs can be privacy-safe while the Cloudflare log envelope is not. `wrangler tail` wraps `console.log` payloads with the raw request URL and headers, including `cf-connecting-ip`, even when invocation logs are disabled. Validate sink-level privacy separately from custom-payload privacy. -- If `pywrangler deploy` fails fetching Pyodide packages with `invalid peer certificate: UnknownIssuer`, rerun with `UV_NATIVE_TLS=true` so `uv` uses system certificate roots. +- If `pywrangler deploy` fails fetching Pyodide packages with `invalid peer certificate: UnknownIssuer`, rerun with `UV_SYSTEM_CERTS=true` (formerly `UV_NATIVE_TLS`, which uv 0.12 deprecates) so `uv` uses system certificate roots. +- A lockfile only protects what installs from it. Pywrangler 1.9.3 vendored `[project] dependencies` by resolving an unpinned `fastapi` afresh at every deploy, so CI tested one FastAPI/Starlette/Pydantic set (`uv.lock`) while production shipped whatever was newest that day. Pywrangler 1.17 vendors a committed, Pyodide-targeted `pylock.toml`; keep `uv.lock` pinned to the same versions (`scripts/align_runtime_lock.py`) and let `tests/test_dependency_locks.py` fail on drift. Pyodide wheels lag PyPI, so production can legitimately run an older package (Pydantic 2.10.6) than a host-only resolve would pick. ## Cache busting and Worker Cache API