From a2961b68dd61db9faada9726ec034f00397e446e Mon Sep 17 00:00:00 2001 From: allen0099 Date: Wed, 14 Oct 2026 04:08:18 +0000 Subject: [PATCH] feat: support Python 3.15; mark unreleased features as 0.5.0 Declare Python 3.15 in the classifiers and run it in the Test matrix, the tox env_list and [gh-actions]; the suite passes on it unchanged. master now carries 0.5.0, so the docs that marked @cached, coalesce=True and HEAD-from-GET as 0.4.2 name 0.5.0 instead, and the comparison page's FastAPI-CacheX column states the 0.5.0 requirements. Closes #459 --- .github/workflows/test.yml | 4 ++-- changelog.d/459.added.md | 2 ++ docs/COMPARISON.md | 14 +++++++------- docs/DEVELOPMENT.md | 4 ++-- docs/HTTP_CACHING.md | 2 +- i18n/zh-TW/docs/COMPARISON.md | 12 ++++++------ i18n/zh-TW/docs/HTTP_CACHING.md | 2 +- pyproject.toml | 1 + tox.ini | 2 ++ 9 files changed, 24 insertions(+), 19 deletions(-) create mode 100644 changelog.d/459.added.md diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b79fc04..ec47364 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ permissions: contents: read jobs: - # The one CI run of the 3.10-3.14 matrix. tox runs the same suites against + # The one CI run of the 3.10-3.15 matrix. tox runs the same suites against # the same lockfile, so it is left to local use rather than run here twice. # A run against the lowest supported dependency versions would be a # separate job next to this one. @@ -38,7 +38,7 @@ jobs: strategy: matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/changelog.d/459.added.md b/changelog.d/459.added.md new file mode 100644 index 0000000..dfc79db --- /dev/null +++ b/changelog.d/459.added.md @@ -0,0 +1,2 @@ +**Python 3.15 is supported.** The package declares it, and CI runs the +full suite on it, against Redis and Memcached, alongside 3.10 to 3.14. diff --git a/docs/COMPARISON.md b/docs/COMPARISON.md index c46bccc..72bb518 100644 --- a/docs/COMPARISON.md +++ b/docs/COMPARISON.md @@ -14,22 +14,22 @@ If something here is out of date, please ## At a glance Facts about the other libraries are from their source code at the release -named in the last row. A feature marked 0.4.2 is not in a FastAPI-CacheX -release yet. +named in the last row (the latest release of each). A feature marked 0.5.0 needs FastAPI-CacheX 0.5.0 +or newer. | | FastAPI-CacheX | fastapi-cache2 | cashews | aiocache | |---|---|---|---|---| -| What it caches | FastAPI GET responses; JSON values; function results (`@cached`, 0.4.2) | Return values of endpoints and plain functions | Return values of any async function | Return values of any async function; a key-value API | +| What it caches | FastAPI GET responses; JSON values; function results (`@cached`, 0.5.0) | Return values of endpoints and plain functions | Return values of any async function | Return values of any async function; a key-value API | | Key built from | Method, host, path and query of the request | Function module, name and arguments | Function name and arguments, or a template | Function name and arguments, or a key builder | | `Cache-Control`, `ETag`, `304` | Written from the decorator's arguments; `304` on a matching `If-None-Match` | `max-age`, a weak `ETag` and `304` when the endpoint takes a `Response` | With its `CacheRequestControlMiddleware` and `CacheEtagMiddleware` | None | | A client's `Cache-Control` request header | Ignored, so clients cannot skip the cache | `no-store` and `no-cache` are honoured | Honoured by the middleware (`no-cache`, `no-store`, `max-age`) | — | | Requests with `Authorization` or a session | Bypass the shared cache unless you opt in | Cached like any other request | Your choice of key and middleware settings | — | | Serialization | JSON, no pickle | JSON by default, pickle optional | Pickle by default, optionally HMAC-signed; JSON optional | Pickle, JSON, msgpack, string or none | | Backends | Memory, Redis, Memcached | Memory, Redis, Memcached, DynamoDB | Memory, Redis (incl. cluster and client-side caching), diskcache | Memory, Redis, Memcached | -| Stampede protection | `get_or_set()` with a distributed lock; `coalesce=True` per process on `@cache` (0.4.2) | None | `locked`, `early`, `soft`, `thunder_protection` | `cached_stampede` (lock based) | +| Stampede protection | `get_or_set()` with a distributed lock; `coalesce=True` per process on `@cache` (0.5.0) | None | `locked`, `early`, `soft`, `thunder_protection` | `cached_stampede` (lock based) | | Tags, early refresh, metrics | No | No | Tags, early and soft refresh, Prometheus middleware, callbacks | Hit/miss and timing plugins | | Also included | `CacheLock`, atomic counters, monitoring routes | — | Rate limiting, circuit breaker, Bloom filters, locks | `RedLock`, `OptimisticLock`, `multi_cached` | -| Latest release | 0.4.1 (2026-10-03); Python 3.10+, FastAPI 0.133+, Starlette 1.0+ | 0.2.2 (2024-07-24); Python 3.8+ | 7.6.0 (2026-09-17); Python 3.10+ | 0.12.3 (2024-09-25) | +| Version compared | 0.5.0; Python 3.10–3.15, FastAPI 0.128.2+ | 0.2.2 (2024-07-24); Python 3.8+ | 7.6.0 (2026-09-17); Python 3.10+ | 0.12.3 (2024-09-25) | ## FastAPI-CacheX @@ -40,7 +40,7 @@ cache to behave like an HTTP cache: adds a weak `ETag` and answers a matching `If-None-Match` with `304`, also on a miss and on `no_cache` routes. It sends `Age` on a hit, honours `Vary` (`vary=` adds request headers to the key), and answers `HEAD` from - the cached `GET` (0.4.2). See [HTTP caching](HTTP_CACHING.md). + the cached `GET` (0.5.0). See [HTTP caching](HTTP_CACHING.md). - **Safe by default with credentials.** The default key carries no user identity, so a request with `Authorization` or a session bypasses the shared cache and is answered with `Cache-Control: private`, and a response that sets @@ -56,7 +56,7 @@ cache to behave like an HTTP cache: handler, so a cache outage does not become an API outage. - **Small extras on the same backend.** `CacheManager` / `AppCache` with `get_or_set()` behind a distributed lock, `@cached` for plain functions - (0.4.2), and `CacheLock` for one holder across workers. + (0.5.0), and `CacheLock` for one holder across workers. What it does not do, or does only simply: diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index ac8aab9..12ff2fb 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -133,7 +133,7 @@ Worth doing whenever a test is written for something security-relevant. ## Using tox -tox ensures the code works across different Python versions (3.10-3.14, the +tox ensures the code works across different Python versions (3.10-3.15, the `env_list` in `tox.ini`). Each environment installs the `redis` and `memcached` extras through `tox-uv` and passes the `CACHEX_TEST_*` and `CACHEX_REQUIRE_LIVE_SERVERS` variables through, so the opt-in rules above apply @@ -143,7 +143,7 @@ tox is for local runs. CI runs the same versions as a job matrix in the **Test** workflow (`.github/workflows/test.yml`), one job per version, so it does not run tox as well. -1. Install all Python versions (`uv python install 3.10 3.11 3.12 3.13 3.14`) +1. Install all Python versions (`uv python install 3.10 3.11 3.12 3.13 3.14 3.15`) 2. Run tox: ```bash diff --git a/docs/HTTP_CACHING.md b/docs/HTTP_CACHING.md index 1567eec..ad38ba0 100644 --- a/docs/HTTP_CACHING.md +++ b/docs/HTTP_CACHING.md @@ -268,7 +268,7 @@ empty body. The server drops the body of every HEAD response. `invalidate()` builds the GET key, so it clears what HEAD reads as well. A route that already accepted HEAD ran the handler on every HEAD request before -0.4.2 and added none of these headers; it now skips the handler on a hit. +0.5.0 and added none of these headers; it now skips the handler on a hit. ### Concurrent misses diff --git a/i18n/zh-TW/docs/COMPARISON.md b/i18n/zh-TW/docs/COMPARISON.md index fc5ddbe..1eef9e6 100644 --- a/i18n/zh-TW/docs/COMPARISON.md +++ b/i18n/zh-TW/docs/COMPARISON.md @@ -6,32 +6,32 @@ FastAPI-CacheX 在 FastAPI 應用程式內快取 HTTP 回應,並正確處理 ## 一覽 {#at-a-glance} -其他函式庫的資訊取自最後一列所列版本的原始碼。標示 0.4.2 的功能尚未包含在 FastAPI-CacheX 的任何發行版中。 +其他函式庫的資訊取自最後一列所列版本(各自的最新版本)的原始碼。標示 0.5.0 的功能需要 FastAPI-CacheX 0.5.0 以上。 | | FastAPI-CacheX | fastapi-cache2 | cashews | aiocache | |---|---|---|---|---| -| 快取的對象 | FastAPI 的 GET 回應;JSON 值;函式結果(`@cached`,0.4.2) | 端點與一般函式的回傳值 | 任何非同步函式的回傳值 | 任何非同步函式的回傳值;鍵值 API | +| 快取的對象 | FastAPI 的 GET 回應;JSON 值;函式結果(`@cached`,0.5.0) | 端點與一般函式的回傳值 | 任何非同步函式的回傳值 | 任何非同步函式的回傳值;鍵值 API | | 快取鍵的來源 | 請求的方法、主機、路徑與查詢字串 | 函式的模組、名稱與引數 | 函式名稱與引數,或樣板 | 函式名稱與引數,或 key builder | | `Cache-Control`、`ETag`、`304` | 依裝飾器的參數產生;`If-None-Match` 相符時回 `304` | 端點接收 `Response` 時,送出 `max-age`、弱 `ETag` 並回 `304` | 透過它的 `CacheRequestControlMiddleware` 與 `CacheEtagMiddleware` | 無 | | 用戶端請求的 `Cache-Control` 標頭 | 忽略,用戶端無法略過快取 | 遵循 `no-store` 與 `no-cache` | 由中介軟體遵循(`no-cache`、`no-store`、`max-age`) | — | | 帶 `Authorization` 或 Session 的請求 | 略過共用快取,除非明確選擇共用 | 和其他請求一樣被快取 | 取決於你的快取鍵和中介軟體設定 | — | | 序列化 | JSON,不用 pickle | 預設 JSON,可選 pickle | 預設 pickle,可選 HMAC 簽章;可選 JSON | pickle、JSON、msgpack、字串或不序列化 | | 後端 | 記憶體、Redis、Memcached | 記憶體、Redis、Memcached、DynamoDB | 記憶體、Redis(含 cluster 與 client-side caching)、diskcache | 記憶體、Redis、Memcached | -| Stampede 防護 | `get_or_set()` 搭配分散式鎖;`@cache` 的 `coalesce=True` 在單一行程內合併(0.4.2) | 無 | `locked`、`early`、`soft`、`thunder_protection` | `cached_stampede`(以鎖實作) | +| Stampede 防護 | `get_or_set()` 搭配分散式鎖;`@cache` 的 `coalesce=True` 在單一行程內合併(0.5.0) | 無 | `locked`、`early`、`soft`、`thunder_protection` | `cached_stampede`(以鎖實作) | | 標籤、提前更新、指標 | 無 | 無 | 標籤、early 與 soft 提前更新、Prometheus 中介軟體、回呼 | 命中率與計時外掛 | | 其他功能 | `CacheLock`、原子計數器、監控路由 | — | 速率限制、斷路器、Bloom filter、鎖 | `RedLock`、`OptimisticLock`、`multi_cached` | -| 最新版本 | 0.4.1(2026-10-03);Python 3.10+、FastAPI 0.133+、Starlette 1.0+ | 0.2.2(2024-07-24);Python 3.8+ | 7.6.0(2026-09-17);Python 3.10+ | 0.12.3(2024-09-25) | +| 比較的版本 | 0.5.0;Python 3.10–3.15、FastAPI 0.128.2+ | 0.2.2(2024-07-24);Python 3.8+ | 7.6.0(2026-09-17);Python 3.10+ | 0.12.3(2024-09-25) | ## FastAPI-CacheX {#fastapi-cachex} 當你要快取的是 **FastAPI 回應**,而且希望快取的行為和 HTTP 快取一致時,選擇它: -- **HTTP 語意。** `@cache` 依參數產生 `Cache-Control`,加上弱 `ETag`,並在 `If-None-Match` 相符時回 `304`,快取未命中和 `no_cache` 路由也一樣。命中時送出 `Age`,遵循 `Vary`(`vary=` 把請求標頭加進快取鍵),並以快取的 `GET` 回應 `HEAD`(0.4.2)。見 [HTTP 快取](HTTP_CACHING.md)。 +- **HTTP 語意。** `@cache` 依參數產生 `Cache-Control`,加上弱 `ETag`,並在 `If-None-Match` 相符時回 `304`,快取未命中和 `no_cache` 路由也一樣。命中時送出 `Age`,遵循 `Vary`(`vary=` 把請求標頭加進快取鍵),並以快取的 `GET` 回應 `HEAD`(0.5.0)。見 [HTTP 快取](HTTP_CACHING.md)。 - **預設對帶憑證的請求安全。** 預設的快取鍵不含使用者身分,所以帶 `Authorization` 或 Session 的請求會略過共用快取,並以 `Cache-Control: private` 回應;設定 cookie 的回應永遠不會存入快取。要共用時設定 `public=True`;要每個使用者各自一筆項目時,設定 `cache_authorized=True` 並搭配把使用者放進快取鍵的 key builder(見[帶有憑證的請求](HTTP_CACHING.md#requests-with-credentials))。 - **快取鍵來自請求。** 快取鍵是方法、主機、路徑與排序後的查詢字串,和下游 HTTP 快取看到的一致,也可以用 `invalidate(request)` 或 `clear_path()` 讓路由的快取失效。 - **不用 pickle。** 回應與 `CacheManager` 的值都以 JSON 儲存,從共用的 Redis 讀出的項目在反序列化時不會執行程式碼。 - **失敗時放行(fail open)。** 後端無法使用時,`@cache` 記錄日誌並執行 handler,快取故障不會變成 API 故障。 -- **同一個後端上的小工具。** `CacheManager`/`AppCache` 的 `get_or_set()` 以分散式鎖保護,`@cached` 快取一般函式(0.4.2),`CacheLock` 讓多個 worker 之間同時只有一個持有者。 +- **同一個後端上的小工具。** `CacheManager`/`AppCache` 的 `get_or_set()` 以分散式鎖保護,`@cached` 快取一般函式(0.5.0),`CacheLock` 讓多個 worker 之間同時只有一個持有者。 它不做、或只簡單做到的事: diff --git a/i18n/zh-TW/docs/HTTP_CACHING.md b/i18n/zh-TW/docs/HTTP_CACHING.md index e6e6908..4b899d4 100644 --- a/i18n/zh-TW/docs/HTTP_CACHING.md +++ b/i18n/zh-TW/docs/HTTP_CACHING.md @@ -131,7 +131,7 @@ HEAD 請求會讀取 GET 以同一個快取鍵儲存的項目,這是 RFC 9110 未命中時 handler 會執行,它的回應得到與 GET 相同的 `Cache-Control` 與 `Vary` 處理,`ETag` 與 `Content-Length` 則依它實際產生的內容計算:對 HEAD 省略本文的 handler,送出的是空本文的值。這個回應不會儲存,否則之後的 GET 會拿到空的本文。伺服器會丟棄每個 HEAD 回應的本文。`invalidate()` 建立的是 GET 的快取鍵,因此也會清除 HEAD 讀取的項目。 -0.4.2 之前,已接受 HEAD 的路由每次收到 HEAD 請求都會執行 handler,也不會加上這些標頭;現在命中時不會執行 handler。 +0.5.0 之前,已接受 HEAD 的路由每次收到 HEAD 請求都會執行 handler,也不會加上這些標頭;現在命中時不會執行 handler。 ### 同時發生的未命中 {#concurrent-misses} diff --git a/pyproject.toml b/pyproject.toml index c08f01f..aec7f02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Programming Language :: Python :: 3 :: Only", "Framework :: FastAPI", "Topic :: Software Development :: Libraries :: Python Modules", diff --git a/tox.ini b/tox.ini index 5e1b0d9..ecb46e3 100644 --- a/tox.ini +++ b/tox.ini @@ -6,6 +6,7 @@ env_list = py312 py313 py314 + py315 [testenv] runner = uv-venv-lock-runner @@ -52,3 +53,4 @@ python = 3.12: py312 3.13: py313 3.14: py314 + 3.15: py315