Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions changelog.d/459.added.md
Original file line number Diff line number Diff line change
@@ -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.
14 changes: 7 additions & 7 deletions docs/COMPARISON.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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:

Expand Down
4 changes: 2 additions & 2 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/HTTP_CACHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 6 additions & 6 deletions i18n/zh-TW/docs/COMPARISON.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 之間同時只有一個持有者。

它不做、或只簡單做到的事:

Expand Down
2 changes: 1 addition & 1 deletion i18n/zh-TW/docs/HTTP_CACHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ env_list =
py312
py313
py314
py315

[testenv]
runner = uv-venv-lock-runner
Expand Down Expand Up @@ -52,3 +53,4 @@ python =
3.12: py312
3.13: py313
3.14: py314
3.15: py315
Loading