From 55bc61dce45b694eddccbe8715536e68f02ae514 Mon Sep 17 00:00:00 2001 From: allen0099 Date: Fri, 2 Oct 2026 14:40:46 +0000 Subject: [PATCH 1/3] docs: fix stale session/JWT statements and zh-TW wording before 0.4.0 --- CLAUDE.md | 2 +- docs/JWT_CLAIMS.md | 2 +- docs/SESSION.md | 6 +++--- i18n/zh-TW/docs/BACKENDS.md | 6 +++--- i18n/zh-TW/docs/CACHE_FLOW.md | 6 +++--- i18n/zh-TW/docs/CONTRIBUTING.md | 2 +- i18n/zh-TW/docs/HTTP_CACHING.md | 10 +++++----- i18n/zh-TW/docs/JWT_CLAIMS.md | 2 +- i18n/zh-TW/docs/MIGRATING_0_4.md | 6 +++--- i18n/zh-TW/docs/SESSION.md | 10 +++++----- 10 files changed, 26 insertions(+), 26 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7862380..b2beb52 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,7 +18,7 @@ Use `uv` for everything (`uv sync --group dev --all-extras`, `uv run ...`). ## Invariants that span modules -- HTTP cache keys are `method|||host|||path|||query` (`CACHE_KEY_SEPARATOR`). Host and path go through `escape_key_component` so client input cannot inject the separator. Anything that builds or parses keys (`clear_path`, `routes.py`, backends) must stay consistent with it. +- HTTP cache keys are `http:v2|method|host|path|query` (`HTTP_KEY_FORMAT_TAG`, `CACHE_KEY_SEPARATOR = "|"`), built and parsed by `CacheKey` in `cache_key.py`. Method, host, path and extra components go through `escape_key_component` so client input cannot inject the separator; a query over 200 bytes becomes `sha256:`. A format change bumps the tag. Anything that builds or parses keys (`clear_path`, `routes.py`, backends) must go through `CacheKey`. - `@cache` fails open by default: a backend error is logged and treated as a miss, or the response is served unstored. Only GET is cached. - Backend lookup: `@cache` and the `CacheBackend` / `AppCache` dependencies fall back to a `MemoryBackend` when none is set. `CacheLock`, `StateManager` and a directly built `CacheManager(...)` call `BackendProxy.get()` and raise; the monitoring routes and `invalidate()` treat a missing backend as empty. The proxies' lazy creation goes through `ProxyBase.get_or_create` (per-class lock; sync dependencies run in threads). - Memcached cannot enumerate keys. `clear_pattern`, `get_all_keys` and every `CacheManager.clear*` are no-ops with a `RuntimeWarning`, while `MemcachedBackend.clear()` issues `flush_all` and wipes the whole server. diff --git a/docs/JWT_CLAIMS.md b/docs/JWT_CLAIMS.md index 1d2d565..b6cdef1 100644 --- a/docs/JWT_CLAIMS.md +++ b/docs/JWT_CLAIMS.md @@ -186,7 +186,7 @@ The base class below does what the built-in `JWTTokenSerializer` does and leaves ``` -PyJWT verifies the signature, `exp`, `iat` and (when present) `nbf` by default, and `iss`/`aud` when `issuer`/`audience` are given. Two checks of the built-in serializer are not repeated here: it rejects an asymmetric `jwt_algorithm` and warns about a `secret_key` shorter than the HMAC output. This class signs with `secret_key` too, so keep an `HS*` algorithm; for an asymmetric one, hold the private and public keys in the class and use them in `jwt.encode()` and `jwt.decode()`. +PyJWT verifies the signature, `exp`, `iat` and (when present) `nbf` by default, and `iss`/`aud` when `issuer`/`audience` are given. Two checks of the built-in serializer are not repeated here: it rejects an asymmetric `jwt_algorithm` and rejects (`ValueError`) a `secret_key` shorter than the HMAC output. This class signs with `secret_key` too, so keep an `HS*` algorithm; for an asymmetric one, hold the private and public keys in the class and use them in `jwt.encode()` and `jwt.decode()`. ### Example 1: Adding `jti` and `nbf` diff --git a/docs/SESSION.md b/docs/SESSION.md index 72a2609..41e25dc 100644 --- a/docs/SESSION.md +++ b/docs/SESSION.md @@ -11,7 +11,7 @@ the cache backend; the client only holds a single signed token. | Token source | Response side | |--------------|---------------| -| Custom header (default `X-Session-Token`) / `Authorization: Bearer` / **cookie** (default name `session`) | Routed by source: a request that sent a header or bearer token (even one that no longer resolves) gets its token in the response header; otherwise (a cookie, or no token at all) it gets `Set-Cookie` | +| Custom header (default `X-Session-Token`) / `Authorization: Bearer` / **cookie** (default name `__Host-session`) | Routed by source: a request that sent a header or bearer token (even one that no longer resolves) gets its token in the response header; otherwise (a cookie, or no token at all) it gets `Set-Cookie` | The header-only `SessionMiddleware`, deprecated since 0.3.1, was **removed in 0.4.0**; see [Migration](#migration-sessionmiddleware-fastapicachexsessionmiddleware). @@ -149,7 +149,7 @@ Both methods delete what they find with a single `backend.delete_many()` call. where the client manages the token. Cookie transport was not supported. - **`FastAPICacheXSessionMiddleware`** (a pure ASGI middleware): compatible with Starlette's built-in `SessionMiddleware`, exposing the same dict-like `request.session`. It passes the signed - session token in a cookie (default cookie name `session`), while the session contents are stored + session token in a cookie (default cookie name `__Host-session`), while the session contents are stored in the backend (the cache backend of the `SessionManager`) rather than encoded into the cookie itself as Starlette's own implementation does. Token resolution is "header first, cookie second": it reads the custom header (default `X-Session-Token`) and/or `Authorization: Bearer` @@ -562,7 +562,7 @@ async def sudo(request: Request, session: AuthenticatedSession): `rotate_session_id()` calls `SessionManager.regenerate_session_id()` on the request's session, which deletes the backend record under the old ID and saves the session under a -new ID, keeping its data, user, `created_at` and expiry. Either middleware sees the new ID +new ID, keeping its data, user, `created_at` and expiry. The middleware sees the new ID and sends a token for it through the transport the request used: `Set-Cookie` for a cookie, the response header for a header token. After that the old token no longer resolves to a session. For a new visitor there is no session to rotate, so it returns diff --git a/i18n/zh-TW/docs/BACKENDS.md b/i18n/zh-TW/docs/BACKENDS.md index 114c42d..3a77497 100644 --- a/i18n/zh-TW/docs/BACKENDS.md +++ b/i18n/zh-TW/docs/BACKENDS.md @@ -113,7 +113,7 @@ BackendProxy.set(backend) ## Memcached {#memcached} -以 `uv add "fastapi-cachex[memcached]"` 安裝此 extra。0.3.8 以前這個 extra 名為 `memcache`,0.4.0 已移除這個名稱。安裝時遇到不存在的 extra 只會顯示警告,因此 `fastapi-cachex[memcache]` 會裝好套件但不含 `pymemcache`,建構 `MemcachedBackend` 時才會拋出錯誤。請參閱[遷移至 0.4.0](MIGRATING_0_4.md#memcache-extra)。 +以 `uv add "fastapi-cachex[memcached]"` 安裝此 extra。0.3.8 之前這個 extra 名為 `memcache`,0.4.0 已移除這個名稱。安裝時遇到不存在的 extra 只會顯示警告,因此 `fastapi-cachex[memcache]` 會裝好套件但不含 `pymemcache`,建構 `MemcachedBackend` 時才會拋出錯誤。請參閱[遷移至 0.4.0](MIGRATING_0_4.md#memcache-extra)。 ```python from fastapi_cachex.backends import MemcachedBackend @@ -128,7 +128,7 @@ BackendProxy.set(backend) - Memcached 協定不支援依模式清除鍵(`clear_pattern`):它會回傳 0 並發出 `RuntimeWarning` - 無法列舉鍵:`get_all_keys()`/`get_cache_data()` 會回傳空結果(並發出 `RuntimeWarning`),因此監控路由不會顯示任何內容 - `clear_path()` 找不到 HTTP 快取項目:它只會刪除名稱與路徑完全相同的鍵,忽略 `include_params`,而且每次呼叫都會發出 `RuntimeWarning`。資料變更後要刪除某個快取路由的項目,請呼叫 [`invalidate(request)`](HTTP_CACHING.md#invalidating-a-single-cached-route),它會重建完全相同的鍵 -- `backend.clear()`(`MemcachedBackend.clear()`)會發出 `flush_all`,清空整台 Memcached 伺服器,而不只是這個命名空間。`CacheManager.clear()` 則不同:它需要列舉鍵,因此在 Memcached 上不會刪除任何東西(見[應用程式快取](APP_CACHE.md)) +- `backend.clear()`(`MemcachedBackend.clear()`)會發出 `flush_all`,清空整台 Memcached 伺服器,而不只是這個命名空間。`CacheManager.clear()` 則不同:它需要列舉鍵,因此在 Memcached 上不會刪除任何東西(見[應用層快取](APP_CACHE.md)) - Memcached 會拒絕的鍵(超過 250 位元組、含空白字元或非 ASCII 字元)會改以其 SHA-256 摘要儲存 - 過期時間落在 2038-01-19 之後的 `ttl` 會拋出 `ValueError`(見 [TTL 值](#ttl-values)) - 超過伺服器項目大小上限(預設 1 MB,可用 `memcached -I` 調整)的值會被拒絕並拋出錯誤。`@cache` 會記錄該錯誤,並照常送出不儲存的回應(見[後端發生錯誤時](HTTP_CACHING.md#when-the-backend-fails));其他呼叫端則會收到該錯誤 @@ -136,7 +136,7 @@ BackendProxy.set(backend) 同步的 pymemcache 用戶端在工作執行緒中執行,並使用連線池,因此並行的請求絕不會共用同一個 socket。寫入會等待伺服器確認(`default_noreply=False`),因此只要 `set()` 返回,就能從連線池中的任何連線讀到該值。每次呼叫只切換到工作執行緒一次,多步驟的[原子操作](#atomic-backend-primitives)也是如此。`delete_many()` 在這一次呼叫中逐一對每個鍵送出 `DELETE`,並回傳實際存在的鍵數;0.3.8 之前它每個鍵都要切換一次執行緒,回傳的是傳入的鍵數。 -伺服器無法連線時,所有要送往它的呼叫都會拋出錯誤,一秒後會再嘗試連線。0.3.8 以前,失敗後一秒內的呼叫會回傳虛構的結果:`get()` 當成未命中、`set()` 沒有任何反應(寫入遺失)、`increment()` 當成新的計數器並回傳 0。設定多台伺服器時,失敗的那台會立即移出輪替,它的鍵會改由其餘伺服器處理,直到它恢復回應。 +伺服器無法連線時,所有要送往它的呼叫都會拋出錯誤,一秒後會再嘗試連線。0.3.8 之前,失敗後一秒內的呼叫會回傳虛構的結果:`get()` 當成未命中、`set()` 沒有任何反應(寫入遺失)、`increment()` 當成新的計數器並回傳 0。設定多台伺服器時,失敗的那台會立即移出輪替,它的鍵會改由其餘伺服器處理,直到它恢復回應。 ## 關閉後端 {#closing-a-backend} diff --git a/i18n/zh-TW/docs/CACHE_FLOW.md b/i18n/zh-TW/docs/CACHE_FLOW.md index d064dbf..a898141 100644 --- a/i18n/zh-TW/docs/CACHE_FLOW.md +++ b/i18n/zh-TW/docs/CACHE_FLOW.md @@ -161,7 +161,7 @@ entry = CacheEntry( ) ``` -TTL 不儲存在 `CacheEntry` 中:過期由後端負責(`MemoryBackend` 將它存在 `CacheItem.expiry`,Redis 使用 `SET ... EX`,Memcached 使用 exptime)。`stored_at` 是系統時鐘時間(`time.time()`),因為送出項目的行程不一定是儲存它的行程;0.3.9 以前的版本寫入的項目為 `None`。 +TTL 不儲存在 `CacheEntry` 中:過期由後端負責(`MemoryBackend` 將它存在 `CacheItem.expiry`,Redis 使用 `SET ... EX`,Memcached 使用 exptime)。`stored_at` 是系統時鐘時間(`time.time()`),因為送出項目的行程不一定是儲存它的行程;0.3.9 之前的版本寫入的項目為 `None`。 若尚未以 `BackendProxy.set()` 設定後端,裝飾器會在第一個請求時建立 `MemoryBackend`、註冊它,並記錄一則警告,說明這個快取是每個行程各自一份。 @@ -223,7 +223,7 @@ return response > 「非 2xx 不寫入」是刻意的設計:暫時性的錯誤不應抹除最後一次正常的快取回應,也不應在之後被當成 200 重播。`206 Partial Content` 同樣不會快取,因為它的內容只對產生它的那個 `Range` 請求有意義。非 2xx 回應也永遠不會以 `304` 回應,且回傳時不帶裝飾器的 `Cache-Control` 標頭(只有 `no_store=True` 會在每個回應加上 `no-store`)。 > [!NOTE] -> **屬於單一呼叫者的回應永遠不會被儲存。** 依照 RFC 9111 §3.5,帶有 `Authorization` 標頭的請求會繞過後端(不讀取也不寫入),帶有 Session 的請求(Session 中介軟體從任何權杖來源載入了 Session,或 `request.session` 不是空的)也一樣,除非路由設定了 `public=True`,或以 `cache_authorized=True` 明確選擇啟用(用於包含已驗證身分的 `key_builder`)。產生回應時,若回應自己的 `Cache-Control` 含有 `private` 或 `no-store`(完整指令,不分大小寫),或回應設定了 cookie,則照常回傳但不寫入。handler 送出的 `private`/`no-store` 標頭會原樣送出,不會被裝飾器的標頭取代。設定 cookie 的回應,以及任何 `Authorization` 或 Session 請求的回應(無論繞過後端,或設定 `cache_authorized` 而由後端回應;200 或 304),會以 `private` 取代 `public` 送出並保留裝飾器的其他指令(`no_cache` 路由則為 `private, no-cache`),讓下游的共用快取也不會儲存它們。`must_revalidate=True` 不會解除 `Authorization` 的繞過;雖然 RFC 9111 允許在 `must-revalidate` 下重複使用,本函式庫仍要求明確選擇啟用。該鍵下已儲存的項目保持不變,而在 handler 執行前就命中該項目的請求仍照常由它回應。每次略過都會以 `DEBUG` 等級記錄。0.3.9 以前這類回應會被儲存並重播給每位呼叫者(#296);帶有 Session 的請求在 0.3.9 以前也不會繞過(#319)。 +> **屬於單一呼叫者的回應永遠不會被儲存。** 依照 RFC 9111 §3.5,帶有 `Authorization` 標頭的請求會繞過後端(不讀取也不寫入),帶有 Session 的請求(Session 中介軟體從任何權杖來源載入了 Session,或 `request.session` 不是空的)也一樣,除非路由設定了 `public=True`,或以 `cache_authorized=True` 明確選擇啟用(用於包含已驗證身分的 `key_builder`)。產生回應時,若回應自己的 `Cache-Control` 含有 `private` 或 `no-store`(完整指令,不分大小寫),或回應設定了 cookie,則照常回傳但不寫入。handler 送出的 `private`/`no-store` 標頭會原樣送出,不會被裝飾器的標頭取代。設定 cookie 的回應,以及任何 `Authorization` 或 Session 請求的回應(無論繞過後端,或設定 `cache_authorized` 而由後端回應;200 或 304),會以 `private` 取代 `public` 送出並保留裝飾器的其他指令(`no_cache` 路由則為 `private, no-cache`),讓下游的共用快取也不會儲存它們。`must_revalidate=True` 不會解除 `Authorization` 的繞過;雖然 RFC 9111 允許在 `must-revalidate` 下重複使用,本函式庫仍要求明確選擇啟用。該鍵下已儲存的項目保持不變,而在 handler 執行前就命中該項目的請求仍照常由它回應。每次略過都會以 `DEBUG` 等級記錄。0.3.9 之前這類回應會被儲存並重播給每位呼叫者(#296);帶有 Session 的請求在 0.3.9 之前也不會繞過(#319)。 ### 4. ETag 產生與驗證 {#4-etag-generation-and-validation} @@ -292,7 +292,7 @@ Redis 與 Memcached 共用同一套 JSON 編解碼器;若已安裝 `orjson` ``` - `content` 使用 **latin-1 來回轉換**,而不是 base64:latin-1 與位元組一一對應,因此任何位元組序列都能放進 JSON 文字中,並原封不動地還原。 -- 舊版本寫入、沒有 `status_code`/`headers` 欄位的項目仍可讀取,解碼後為 `200` 且沒有額外標頭。沒有 `stored_at` 的項目(0.3.9 以前)解碼後為 `stored_at=None`,送出時不帶 `Age` 標頭。 +- 舊版本寫入、沒有 `status_code`/`headers` 欄位的項目仍可讀取,解碼後為 `200` 且沒有額外標頭。沒有 `stored_at` 的項目(0.3.9 之前)解碼後為 `stored_at=None`,送出時不帶 `Age` 標頭。 - `headers` 是 `[name, value]` 行組成的清單,因此重複送出的標頭會保留每一行。0.3.x 寫入的物件(每個名稱一個值)仍可解碼。其他任何形式都會讓整個項目視為未命中。 - 任何解碼失敗(損壞的 JSON、缺少欄位、型別錯誤)都視為**快取未命中**,回傳 `None` 而不是拋出例外。 - `increment()` 會留下一個**單純的整數**(由 Redis/Memcached 的 INCR 系列指令寫入);它會解碼成 fingerprint 為 `counter` 的 `CacheEntry`。 diff --git a/i18n/zh-TW/docs/CONTRIBUTING.md b/i18n/zh-TW/docs/CONTRIBUTING.md index ef4663c..87b4d7c 100644 --- a/i18n/zh-TW/docs/CONTRIBUTING.md +++ b/i18n/zh-TW/docs/CONTRIBUTING.md @@ -43,7 +43,7 @@ 1. 變更介面時,請更新 `docs/` 底下對應的指南(若變更應出現在首頁,也請更新 README)。只有英文頁面需要更新:[繁體中文翻譯](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/#traditional-chinese-translation)(英文)允許落後於英文版。 2. 若你的變更改變了行為、新增了公開 API,或修正了使用者可能遇到的問題,請新增一個 changelog 片段:檔案 `changelog.d/.
.md`(section 為 `added`、`changed`、`deprecated`、`removed`、`fixed` 或 `security`),內容為該筆項目,以粗體的一行摘要開頭,寫成 `**What changed.** The details...`,不要加上開頭的 `- ` 或 issue 連結——發行時會自動補上。請不要直接編輯 [CHANGELOG.md](https://github.com/allen0099/FastAPI-CacheX/blob/master/CHANGELOG.md),詳見 [Changelog 片段](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/#changelog-fragments)(英文)。發行說明只會列出這些摘要,格式錯誤的片段會讓 CI 失敗。沒有任何可發行的項目時,發行流程也會拒絕執行,因此遺漏終究會被發現,但要到發行時才會發現,而且只會知道「有人忘了寫」,無法得知是哪個 PR。 -3. 為任何新的依賴、功能或變更更新文件。新的公開 API 需要 docstring;若它位於尚未涵蓋的模組中,還需要在 `docs/api/` 底下新增項目。請以 `uv run zensical build --strict` 檢查網站(見[文件網站](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/#documentation-site)(英文))。 +3. 為任何新的依賴項、功能或變更更新文件。新的公開 API 需要 docstring;若它位於尚未涵蓋的模組中,還需要在 `docs/api/` 底下新增項目。請以 `uv run zensical build --strict` 檢查網站(見[文件網站](https://fastapi-cachex.readthedocs.io/en/latest/DEVELOPMENT/#documentation-site)(英文))。 4. 取得至少一位其他開發者的同意後,PR 即可合併。 ## 有任何問題? {#any-questions} diff --git a/i18n/zh-TW/docs/HTTP_CACHING.md b/i18n/zh-TW/docs/HTTP_CACHING.md index a9d2870..5621cb2 100644 --- a/i18n/zh-TW/docs/HTTP_CACHING.md +++ b/i18n/zh-TW/docs/HTTP_CACHING.md @@ -81,13 +81,13 @@ GET /items → 200, Cache-Control: max-age=60,沒有 Age(handler 有執行 GET /items → 200, Cache-Control: max-age=60, Age: 42(儲存後 42 秒送出) ``` -項目的儲存時間取自儲存它的行程的系統時鐘,而由送出它的行程讀取,因此 `Age` 會限制在 `0`–`ttl` 之間,以防兩台主機的時鐘不一致。handler 有執行時(未命中、`no_cache=True`、繞過後端的請求)不會送出 `Age`;0.3.9 以前的版本儲存的項目沒有記錄時間,也不會送出。handler 自己設定的 `Age` 標頭不會被儲存。 +項目的儲存時間取自儲存它的行程的系統時鐘,而由送出它的行程讀取,因此 `Age` 會限制在 `0`–`ttl` 之間,以防兩台主機的時鐘不一致。handler 有執行時(未命中、`no_cache=True`、繞過後端的請求)不會送出 `Age`;0.3.9 之前的版本儲存的項目沒有記錄時間,也不會送出。handler 自己設定的 `Age` 標頭不會被儲存。 只有成功的回應會被儲存。handler *回傳* 非 2xx 狀態的回應(例如 `Response(..., status_code=404)`)會原樣傳出、永不快取,因此暫時性的錯誤不會取代或污染上一筆正常的項目。`206 Partial Content` 同樣排除在外,因為它的本文只對產生它的那個 `Range` 請求有意義。 屬於單一呼叫者的回應同樣不會被儲存(#296): -- **請求帶有 `Authorization` 或 Session。** 依照 RFC 9111 §3.5 對共用快取的要求,這類請求會像 `private=True` 一樣繞過後端:不讀取也不寫入,handler 照常執行,`If-None-Match` 與新產生的回應比對。回應(以及 304)會以 `private` 取代 `public` 送出,並保留裝飾器的其他指令(`no_cache` 路由則為 `private, no-cache`),讓 CDN 或代理也不會儲存它。`public=True` 的路由不受此限。設定 `cache_authorized=True`(給包含呼叫者身分的 key builder 使用的明確選項,見[需驗證身分的端點](#authenticated-endpoints))的路由會為這類請求讀寫後端,但回應仍帶有 `private`:項目只在後端依呼叫者區分,CDN 則只以 URL 為鍵(0.3.9 以前會原樣送出裝飾器的標頭,#372)。`must_revalidate=True` 不會解除繞過:RFC 9111 允許共用快取在 `must-revalidate` 下重複使用這類回應,但本函式庫要求明確選擇啟用。沒有正數 `ttl` 的路由本來就不經過後端,但它對這類請求的回應仍會加上 `private`(0.3.9 以前不會加,#362);`private=True` 的路由本來就會送出 `private`。請求「帶有 Session」是指 `FastAPICacheXSessionMiddleware` 為它載入了 Session(權杖來自標頭、Bearer 權杖或 Session Cookie 皆可,有沒有使用者都算),或在任何 Session 中介軟體(包括 Starlette 的)下 `request.session` 不是空的。解析不出 Session 的權杖(偽造、過期)不算,因此無法用來略過快取。0.3.9 以前只有 `Authorization` 會觸發繞過,讀取 Session 的路由只加上 `@cache` 時,會把一位訪客的回應提供給下一位(#319)。會讀取後端的路由第一次繞過時,會以 `WARNING` 等級記錄(見[帶有憑證的請求](#requests-with-credentials))。 +- **請求帶有 `Authorization` 或 Session。** 依照 RFC 9111 §3.5 對共用快取的要求,這類請求會像 `private=True` 一樣繞過後端:不讀取也不寫入,handler 照常執行,`If-None-Match` 與新產生的回應比對。回應(以及 304)會以 `private` 取代 `public` 送出,並保留裝飾器的其他指令(`no_cache` 路由則為 `private, no-cache`),讓 CDN 或代理也不會儲存它。`public=True` 的路由不受此限。設定 `cache_authorized=True`(給包含呼叫者身分的 key builder 使用的明確選項,見[需驗證身分的端點](#authenticated-endpoints))的路由會為這類請求讀寫後端,但回應仍帶有 `private`:項目只在後端依呼叫者區分,CDN 則只以 URL 為鍵(0.3.9 之前會原樣送出裝飾器的標頭,#372)。`must_revalidate=True` 不會解除繞過:RFC 9111 允許共用快取在 `must-revalidate` 下重複使用這類回應,但本函式庫要求明確選擇啟用。沒有正數 `ttl` 的路由本來就不經過後端,但它對這類請求的回應仍會加上 `private`(0.3.9 之前不會加,#362);`private=True` 的路由本來就會送出 `private`。請求「帶有 Session」是指 `FastAPICacheXSessionMiddleware` 為它載入了 Session(權杖來自標頭、Bearer 權杖或 Session Cookie 皆可,有沒有使用者都算),或在任何 Session 中介軟體(包括 Starlette 的)下 `request.session` 不是空的。解析不出 Session 的權杖(偽造、過期)不算,因此無法用來略過快取。0.3.9 之前只有 `Authorization` 會觸發繞過,讀取 Session 的路由只加上 `@cache` 時,會把一位訪客的回應提供給下一位(#319)。會讀取後端的路由第一次繞過時,會以 `WARNING` 等級記錄(見[帶有憑證的請求](#requests-with-credentials))。 - **handler 自己的 `Cache-Control` 含有 `private` 或 `no-store`**(完整指令,不分大小寫)。回應照常送出但不儲存,而且 handler 的標頭會原樣送出,不會被裝飾器的標頭取代。 - **回應設定了 cookie。** 回應照常送出(包含 `Set-Cookie`),但不儲存;它(以及 304)會以 `private` 取代 `public` 送出並保留其他指令,讓下游的共用快取也不會儲存它。 @@ -173,9 +173,9 @@ http:v2|{method}|{host}|{path}|{query_params} - 不同的查詢參數各有獨立的快取項目 - 同一個端點搭配不同參數時可以各自快取 -查詢參數會先依名稱排序,再建立快取鍵,因此 `?a=1&b=2` 與 `?b=2&a=1` 共用同一筆項目。排序是穩定的:同名參數的多個值保留用戶端送出的順序,因為以 `tag: list[str]` 讀取的處理函式看到的正是這個順序,所以 `?tag=b&tag=a` 與 `?tag=a&tag=b` 仍是兩筆項目。名稱以解碼後的值比較(`%61` 視為 `a` 排序,快取鍵本來就這樣寫它),每個名稱與值的編碼都與未排序的快取鍵完全相同,只有順序改變:已經依序排列的查詢,不論是否排序都得到相同的鍵。快取鍵原本就視為相同的仍然相同(`?a` 與 `?a=`、`&&` 產生的空段),其餘一律不會合併。0.4.0 起預設會排序([#72](https://github.com/allen0099/FastAPI-CacheX/issues/72),見[遷移至 0.4.0](MIGRATING_0_4.md#cache-keys))。 +查詢參數會先依名稱排序,再建立快取鍵,因此 `?a=1&b=2` 與 `?b=2&a=1` 共用同一筆項目。排序是穩定的:同名參數的多個值保留用戶端送出的順序,因為以 `tag: list[str]` 讀取的 handler看到的正是這個順序,所以 `?tag=b&tag=a` 與 `?tag=a&tag=b` 仍是兩筆項目。名稱以解碼後的值比較(`%61` 視為 `a` 排序,快取鍵本來就這樣寫它),每個名稱與值的編碼都與未排序的快取鍵完全相同,只有順序改變:已經依序排列的查詢,不論是否排序都得到相同的鍵。快取鍵原本就視為相同的仍然相同(`?a` 與 `?a=`、`&&` 產生的空段),其餘一律不會合併。0.4.0 起預設會排序([#72](https://github.com/allen0099/FastAPI-CacheX/issues/72),見[遷移至 0.4.0](MIGRATING_0_4.md#cache-keys))。 -回應取決於用戶端送出之查詢順序的處理函式(例如從 `request.url` 複製的自身連結或分頁連結)應關閉排序,否則第一位呼叫者的順序會被快取,並提供給送出其他順序的呼叫者: +回應取決於用戶端送出之查詢順序的 handler(例如從 `request.url` 複製的自身連結或分頁連結)應關閉排序,否則第一位呼叫者的順序會被快取,並提供給送出其他順序的呼叫者: ```python @app.get("/search") @@ -367,7 +367,7 @@ async def my_dashboard(user: CurrentUser): > > 以原始請求標頭組成的鍵等同於水平權限提升:送出 `X-User-Id: ` 就會拿到該使用者的快取回應。 -key builder 只在 `@cache` 讀取或寫入後端時執行,因此 `no_store=True`、`private=True`、沒有 `ttl` 的路由,以及路由未設定 `public=True` 或 `cache_authorized=True` 時帶有 `Authorization` 或 Session 的請求,都不會呼叫它。0.3.8 以前它仍會被呼叫,但只用於除錯日誌。請讓它不帶副作用。 +key builder 只在 `@cache` 讀取或寫入後端時執行,因此 `no_store=True`、`private=True`、沒有 `ttl` 的路由,以及路由未設定 `public=True` 或 `cache_authorized=True` 時帶有 `Authorization` 或 Session 的請求,都不會呼叫它。0.3.8 之前它仍會被呼叫,但只用於除錯日誌。請讓它不帶副作用。 key builder 必須是回傳 `str` 的同步函式,呼叫時不會被 await。`async def` 函式、具有 `async def __call__` 的物件,或包裝上述兩者的 `functools.partial`,都會在套用 `@cache` 時以 `CacheXError` 拒絕;`invalidate()` 也會在存取後端之前拒絕它們。仍然回傳非 `str` 的 builder(例如回傳協程的同步包裝函式)會在請求時拋出 `CacheXError`。`fail_open` 不涵蓋這種情況:這是路由的錯誤,不是後端故障。需要非同步讀取的資料(例如從資料庫取得使用者),請在依賴項或中介軟體中讀取,放到 `request.state` 供 builder 使用。 diff --git a/i18n/zh-TW/docs/JWT_CLAIMS.md b/i18n/zh-TW/docs/JWT_CLAIMS.md index 18311c5..0eedc1e 100644 --- a/i18n/zh-TW/docs/JWT_CLAIMS.md +++ b/i18n/zh-TW/docs/JWT_CLAIMS.md @@ -186,7 +186,7 @@ await session_manager.delete_session("session-abc123") ``` -PyJWT 預設會驗證簽章、`exp`、`iat` 與(存在時的)`nbf`,並在傳入 `issuer`/`audience` 時驗證 `iss`/`aud`。內建序列化器的兩項檢查在這裡沒有重複:它會拒絕非對稱的 `jwt_algorithm`,並在 `secret_key` 短於 HMAC 輸出長度時發出警告。這個類別同樣以 `secret_key` 簽署,因此請使用 `HS*` 演算法;若要使用非對稱演算法,請在類別中保存私鑰與公鑰,並在 `jwt.encode()` 與 `jwt.decode()` 中使用它們。 +PyJWT 預設會驗證簽章、`exp`、`iat` 與(存在時的)`nbf`,並在傳入 `issuer`/`audience` 時驗證 `iss`/`aud`。內建序列化器的兩項檢查在這裡沒有重複:它會拒絕非對稱的 `jwt_algorithm`,並在 `secret_key` 短於 HMAC 輸出長度時拋出 `ValueError`。這個類別同樣以 `secret_key` 簽署,因此請使用 `HS*` 演算法;若要使用非對稱演算法,請在類別中保存私鑰與公鑰,並在 `jwt.encode()` 與 `jwt.decode()` 中使用它們。 ### 範例 1:加入 `jti` 與 `nbf` {#example-1-adding-jti-and-nbf} diff --git a/i18n/zh-TW/docs/MIGRATING_0_4.md b/i18n/zh-TW/docs/MIGRATING_0_4.md index 2589329..e6e1162 100644 --- a/i18n/zh-TW/docs/MIGRATING_0_4.md +++ b/i18n/zh-TW/docs/MIGRATING_0_4.md @@ -219,10 +219,10 @@ SessionConfig( - **0.4.0 不會移除 `use_bearer_token`([#377](https://github.com/allen0099/FastAPI-CacheX/issues/377))。** 它維持已棄用,傳入時仍會發出 `DeprecationWarning`,並在 0.5.0 隨 `fastapi_cachex.session` 一起移除。是否讀取 Bearer 權杖只由清單決定: ```python -# 之前 +# 修改前 SessionConfig(secret_key=SECRET, use_bearer_token=False) -# 之後 +# 修改後 SessionConfig(secret_key=SECRET, token_source_priority=["header", "cookie"]) ``` @@ -278,7 +278,7 @@ CacheManagerProxy.set(CacheManager(lock=True)) - 直接寫出分隔符號的 `clear_pattern()` 模式(`"GET|||*|||/users/*"`)需要改寫,以大寫或帶預設連接埠寫出 host 的模式也一樣。`clear_path()` 與 `invalidate()` 會自行組出鍵,不需要修改。 - 呼叫 `build_cache_key()` 的自訂 `key_builder` 會自動跟上,包括排序;若要保留送出的順序,請對 `build_cache_key()` 傳入 `sort_query=False`。`@cache` 與 `invalidate()` 現在會拒絕與自訂 `key_builder` 一起傳入的 `sort_query`,`False` 也包括在內(0.3.9 接受在此傳入 `False`,但它沒有作用):請移除它,由 builder 決定。自行組出鍵的(以 `CACHE_KEY_SEPARATOR` 串接或直接寫死 `|||`)仍可以快取,也仍能搭配 `invalidate()` 與 `clear_pattern()`,但其鍵沒有 `http:v2` 標籤,因此 `clear_path()` 不再找得到它們,監控路由也不再列出它們。改用 `build_cache_key(request, *components)` 即可兩者都保留。 -- 回應取決於用戶端送出的查詢字串順序的處理函式(例如從 `request.url` 複製的自身連結或分頁連結、對原始查詢字串計算的簽章),請設定 `@cache(sort_query=False)`,並對該路由的 `invalidate()` 傳入 `sort_query=False`。否則第一位呼叫者的順序會被快取,並提供給送出其他順序的呼叫者。`sort_query=False` 在 0.3.9 就能使用。 +- 回應取決於用戶端送出的查詢字串順序的 handler(例如從 `request.url` 複製的自身連結或分頁連結、對原始查詢字串計算的簽章),請設定 `@cache(sort_query=False)`,並對該路由的 `invalidate()` 傳入 `sort_query=False`。否則第一位呼叫者的順序會被快取,並提供給送出其他順序的呼叫者。`sort_query=False` 在 0.3.9 就能使用。 - 0.4.0 不會讀取 0.3.x 寫入的項目。這些項目會在 TTL 到期後過期;在 Redis 與記憶體後端上,可以在升級後立即以 `await backend.clear_pattern("*|||*")` 移除。這個模式會比對任何含有 `|||` 的鍵,因此請先確認你自己的鍵(例如 `CacheManager` 的鍵)都不含它。Memcached 無法列舉鍵,只能等它們過期。 0.3.9 不會警告:0.3.x 無從判斷某個模式或 key builder 是否符合新格式,而執行期唯一的代價只是一次未命中。與自訂 `key_builder` 一起傳入的 `sort_query` 同樣不會警告,但它會在套用裝飾器時(通常是匯入時)拋出 `CacheXError`,而不是在請求時。 diff --git a/i18n/zh-TW/docs/SESSION.md b/i18n/zh-TW/docs/SESSION.md index 23c7722..e25b92b 100644 --- a/i18n/zh-TW/docs/SESSION.md +++ b/i18n/zh-TW/docs/SESSION.md @@ -9,7 +9,7 @@ FastAPI-CacheX 的 Session 管理提供完整的使用者 Session 處理,包 | 權杖來源 | 回應端 | |--------------|---------------| -| 自訂標頭(預設 `X-Session-Token`)/`Authorization: Bearer`/**Cookie**(預設名稱 `session`) | 依來源決定:送出標頭或 Bearer 權杖的請求(即使該權杖已無法解析)會在回應標頭中收到權杖;其他情況(Cookie,或完全沒有權杖)則使用 `Set-Cookie` | +| 自訂標頭(預設 `X-Session-Token`)/`Authorization: Bearer`/**Cookie**(預設名稱 `__Host-session`) | 依來源決定:送出標頭或 Bearer 權杖的請求(即使該權杖已無法解析)會在回應標頭中收到權杖;其他情況(Cookie,或完全沒有權杖)則使用 `Set-Cookie` | 只支援標頭的 `SessionMiddleware` 自 0.3.1 起已棄用,**已於 0.4.0 移除**;見[遷移](#migration-sessionmiddleware-fastapicachexsessionmiddleware)。 @@ -95,7 +95,7 @@ app.add_middleware(FastAPICacheXSessionMiddleware) # 從 proxy 取得 `SessionMiddleware` 自 0.3.1 起已棄用,已於 0.4.0 移除。請改用 `FastAPICacheXSessionMiddleware`: - **`SessionMiddleware`**(一個 `BaseHTTPMiddleware`,已移除):以自訂標頭(預設 `X-Session-Token`)和/或 `Authorization: Bearer` 傳遞權杖,適合由用戶端管理權杖的 API 優先架構。不支援以 Cookie 傳輸。 -- **`FastAPICacheXSessionMiddleware`**(一個純 ASGI 中介軟體):與 Starlette 內建的 `SessionMiddleware` 相容,提供相同的類 dict `request.session`。它以 Cookie(預設 Cookie 名稱 `session`)傳遞簽署過的 Session 權杖,而 Session 內容則存放在後端(`SessionManager` 的快取後端),而不是像 Starlette 自己的實作那樣編碼進 Cookie 本身。權杖解析採「標頭優先、Cookie 其次」:它會先讀取自訂標頭(預設 `X-Session-Token`)和/或 `Authorization: Bearer`,只有兩者都不存在時才退回使用 Cookie,因此原本搭配 `SessionMiddleware` 使用 `X-Session-Token` 的用戶端不需修改即可繼續運作。回應端同樣依來源決定:請求送出標頭或 Bearer 權杖時(即使該權杖已無法解析),新的或更新後的權杖會在 `header_name` 回應標頭中傳回,且不會發出 `Set-Cookie`;從 Cookie 傳入的權杖(或沒有權杖的請求所建立的全新匿名 Session)則使用 `Set-Cookie`。 +- **`FastAPICacheXSessionMiddleware`**(一個純 ASGI 中介軟體):與 Starlette 內建的 `SessionMiddleware` 相容,提供相同的類 dict `request.session`。它以 Cookie(預設 Cookie 名稱 `__Host-session`)傳遞簽署過的 Session 權杖,而 Session 內容則存放在後端(`SessionManager` 的快取後端),而不是像 Starlette 自己的實作那樣編碼進 Cookie 本身。權杖解析採「標頭優先、Cookie 其次」:它會先讀取自訂標頭(預設 `X-Session-Token`)和/或 `Authorization: Bearer`,只有兩者都不存在時才退回使用 Cookie,因此原本搭配 `SessionMiddleware` 使用 `X-Session-Token` 的用戶端不需修改即可繼續運作。回應端同樣依來源決定:請求送出標頭或 Bearer 權杖時(即使該權杖已無法解析),新的或更新後的權杖會在 `header_name` 回應標頭中傳回,且不會發出 `Set-Cookie`;從 Cookie 傳入的權杖(或沒有權杖的請求所建立的全新匿名 Session)則使用 `Set-Cookie`。 `FastAPICacheXSessionMiddleware` 和 `SessionMiddleware` 一樣會將載入的 `Session` 物件放進 `request.state`,因此 Session 依賴項 `get_session`、`get_optional_session`、`require_session` 與 `require_user_session` 都能直接運作,不需任何修改: @@ -123,7 +123,7 @@ async def me(session=Depends(require_user_session)): - 以 `del` 或 `pop()` 移除最後一個鍵並不是登出。帶有使用者的 Session 會以空資料儲存;匿名 Session 已無任何內容,會和 `clear()` 一樣被刪除。 - 以寫入 `request.session` 的方式登入時,會沿用請求帶來的 Session ID。Starlette 的中介軟體中 Cookie *就是* Session,因此登入回應會取代任何被植入的 Cookie;這裡的 Cookie 只是指向伺服器端紀錄的名稱,被植入的 Cookie 會跟著受害者一起登入。請以 `await login(request, user)` 登入,它會為 Session 換一個新 ID 並附加使用者(見[登入後重新產生 Session ID](#5-regenerate-the-session-id-after-login))。 - 只要存取 `request.session`,或透過 Session 依賴項(`get_session`、`get_optional_session`,以及建立在它們之上的依賴項,例如 `AuthenticatedSession`)讀取 Session,就會為了尋找權杖而讀取過的每個請求標頭加入 `Vary`:依 `token_source_priority` 順序檢查的標頭(`header_name`,以及啟用 Bearer 權杖時的 `Authorization`),直到攜帶權杖的那一個為止。只有在沒有任何標頭攜帶權杖時才會讀取 Cookie,因此也只有這時才會加入 `Cookie`。 -- 帶有 Session 權杖的回應(新建立的 Session、滑動續期、重新產生的 ID),或帶有讓 Session Cookie 失效之 `Set-Cookie` 的回應,一律不可快取。中介軟體會設定 `Cache-Control: private, no-store`,取代路由原本設定的值(包括 `@cache(public=True)` 的路由),並且即使處理函式沒有碰過 `request.session`,也會加入與上一項相同的 `Vary` 名稱。否則 CDN 或反向 proxy 可能存下權杖,再交給下一位訪客。不帶權杖的回應則維持原本的標頭。 +- 帶有 Session 權杖的回應(新建立的 Session、滑動續期、重新產生的 ID),或帶有讓 Session Cookie 失效之 `Set-Cookie` 的回應,一律不可快取。中介軟體會設定 `Cache-Control: private, no-store`,取代路由原本設定的值(包括 `@cache(public=True)` 的路由),並且即使 handler 沒有碰過 `request.session`,也會加入與上一項相同的 `Vary` 名稱。否則 CDN 或反向 proxy 可能存下權杖,再交給下一位訪客。不帶權杖的回應則維持原本的標頭。 - 對帶有 Session 的請求(中介軟體從任何來源載入的 Session,有沒有使用者都算,或不是空的 `request.session`),`@cache` 不會讀寫後端,並像 `Authorization` 一樣以 `private` 回應。`public=True` 讓路由在各 Session 間共用;`cache_authorized=True` 搭配包含 Session 使用者的 `key_builder` 則依使用者快取,回應仍帶有 `private`。見[需驗證身分的端點](HTTP_CACHING.md#authenticated-endpoints)。 Cookie 一律為 `HttpOnly`;`Secure`、`SameSite`、`Domain`、`Path` 與 `Max-Age` 則依 `cookie_*` 設定(`cookie_max_age=None` 或 `0` 時不設 `Max-Age`)。 @@ -345,7 +345,7 @@ async def log_in(credentials: Credentials, request: Request): 無論哪種情況,舊的權杖都無法再解析出 Session。接著中介軟體會儲存該 Session(包括呼叫之後寫入 `request.session` 的鍵;除非已載入的 Session 屬於其他使用者,也包括呼叫之前寫入的鍵),並透過該請求使用的傳輸方式送出權杖:以標頭或 `Authorization: Bearer` 權杖送來的請求使用回應標頭,否則使用帶有所有 `cookie_*` 屬性的 HttpOnly `Set-Cookie`。和每個帶有權杖的回應一樣,它會加上 `Cache-Control: private, no-store`。之後帶著該權杖的請求會通過 `require_user_session` 與 `AuthenticatedSession`。`login()` 會回傳該 Session,在該請求剩下的處理中,`get_session` 也會回傳它。 -完全沒有帶權杖的請求只會收到 Cookie,頁面上的指令碼讀不到它。不要把權杖複製到瀏覽器登入回應的標頭或本文中。沒有權杖就登入的 API 用戶端需要從本文取得權杖:對 `login()` 回傳的 Session 回傳 `manager.issue_token(session)`,或由另一個端點發出權杖,如 [`examples/session_jwt.py`](https://github.com/allen0099/FastAPI-CacheX/blob/master/examples/session_jwt.py) 所示。完整的瀏覽器版本請見 [`examples/session_login.py`](https://github.com/allen0099/FastAPI-CacheX/blob/master/examples/session_login.py)。 +完全沒有帶權杖的請求只會收到 Cookie,頁面上的腳本讀不到它。不要把權杖複製到瀏覽器登入回應的標頭或本文中。沒有權杖就登入的 API 用戶端需要從本文取得權杖:對 `login()` 回傳的 Session 回傳 `manager.issue_token(session)`,或由另一個端點發出權杖,如 [`examples/session_jwt.py`](https://github.com/allen0099/FastAPI-CacheX/blob/master/examples/session_jwt.py) 所示。完整的瀏覽器版本請見 [`examples/session_login.py`](https://github.com/allen0099/FastAPI-CacheX/blob/master/examples/session_login.py)。 登出請呼叫 `await logout(request)`。它會立即從後端刪除 Session,因此權杖在回應送出之前就已失效,Cookie 用戶端也會收到讓 Cookie 過期的回應。它回傳 `True`;若該請求中沒有載入或建立任何 Session(包括權杖無法解析的情況),則回傳 `False`。之後寫入 `request.session` 的鍵會存進新的匿名 Session,之後呼叫的 `login()` 則會建立新的 Session。 @@ -368,7 +368,7 @@ async def sudo(request: Request, session: AuthenticatedSession): return {"ok": True} ``` -`rotate_session_id()` 會對請求的 Session 呼叫 `SessionManager.regenerate_session_id()`,刪除舊 ID 底下的後端紀錄,並以新 ID 儲存該 Session,保留其資料、使用者、`created_at` 與過期時間。任一個中介軟體都會看到新 ID,並透過該請求使用的傳輸方式送出對應的權杖:Cookie 使用 `Set-Cookie`,標頭權杖則使用回應標頭。之後舊的權杖就無法再解析出 Session。新訪客沒有可換 ID 的 Session,因此它會回傳 `False`。若這個請求執行期間,另一個請求刪除、使其失效或輪替了這個 Session,則不會儲存或送出任何東西,並回應 `401`;`login()` 則改為替使用者建立新的 Session(見 [Session 寫入](MIGRATING_0_4.md#session-writes))。 +`rotate_session_id()` 會對請求的 Session 呼叫 `SessionManager.regenerate_session_id()`,刪除舊 ID 底下的後端紀錄,並以新 ID 儲存該 Session,保留其資料、使用者、`created_at` 與過期時間。中介軟體會看到新 ID,並透過該請求使用的傳輸方式送出對應的權杖:Cookie 使用 `Set-Cookie`,標頭權杖則使用回應標頭。之後舊的權杖就無法再解析出 Session。新訪客沒有可換 ID 的 Session,因此它會回傳 `False`。若這個請求執行期間,另一個請求刪除、使其失效或輪替了這個 Session,則不會儲存或送出任何東西,並回應 `401`;`login()` 則改為替使用者建立新的 Session(見 [Session 寫入](MIGRATING_0_4.md#session-writes))。 已經取得請求 Session 物件的 handler,也可以直接呼叫 `await manager.regenerate_session_id(session)`。請從 `get_optional_session` 取得 Session,並在它為 `None` 時略過呼叫;`SessionDep` 會對還沒有 Session 的訪客回應 `401`。與 `rotate_session_id()` 不同,若期間另一個請求結束了這個 Session,直接呼叫會拋出 `SessionNotFoundError` 或 `SessionInvalidError`,因此請捕捉 `SessionError`,並比照沒有 Session 的情況回應。 From a499664e5ee1b051f673121d9c20380d999cbced Mon Sep 17 00:00:00 2001 From: allen0099 Date: Fri, 2 Oct 2026 14:46:03 +0000 Subject: [PATCH 2/3] docs: review the docs and changelog for 0.4.0 - migration guide: drop pre-release wording, fix the use_bearer_token and get_or_set lock snippets, date the CacheError and UserSessionDep notes - changelog: release notice, entries for the session changes 0.3.9 announced but 0.4.0 does not make (#75, #131, #377), migration links - SECURITY.md: 0.4.x is the supported line - HTTP caching, backends, lock and app cache pages: deprecation context for sessions, ttl in the normal-caching examples, smaller corrections - zh-TW follows, plus glossary fixes --- CHANGELOG.md | 6 ++++++ README.md | 2 +- SECURITY.md | 18 +++++++++--------- changelog.d/131.changed.md | 5 +++++ changelog.d/377.deprecated.md | 5 +++++ changelog.d/420.deprecated.md | 3 +-- changelog.d/69.removed.md | 2 +- changelog.d/75.changed.md | 6 ++++++ docs/APP_CACHE.md | 3 ++- docs/BACKENDS.md | 5 +++-- docs/CACHE_FLOW.md | 6 +++--- docs/DEVELOPMENT.md | 4 ++-- docs/HTTP_CACHING.md | 22 +++++++++++----------- docs/LOCK.md | 2 +- docs/MIGRATING_0_4.md | 21 ++++++++++----------- i18n/zh-TW/docs/APP_CACHE.md | 3 ++- i18n/zh-TW/docs/BACKENDS.md | 4 ++-- i18n/zh-TW/docs/CACHE_FLOW.md | 6 +++--- i18n/zh-TW/docs/HTTP_CACHING.md | 12 ++++++------ i18n/zh-TW/docs/LOCK.md | 2 +- i18n/zh-TW/docs/MIGRATING_0_4.md | 21 ++++++++++----------- i18n/zh-TW/docs/index.md | 2 +- 22 files changed, 91 insertions(+), 69 deletions(-) create mode 100644 changelog.d/131.changed.md create mode 100644 changelog.d/377.deprecated.md create mode 100644 changelog.d/75.changed.md diff --git a/CHANGELOG.md b/CHANGELOG.md index c5cea5c..d62c008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,12 @@ Note that 0.3.3 was never released; 0.3.4 follows 0.3.2. ## [Unreleased] +0.4.0 contains breaking changes; read +[Migrating to 0.4.0](https://fastapi-cachex.readthedocs.io/en/stable/MIGRATING_0_4/) +before upgrading. Sessions and OAuth state are deprecated and will be removed +in 0.5.0. Three session changes announced in 0.3.9 are not made as announced: +see the entries for #75, #131 and #377. + ## [0.3.9] - 2026-09-29 0.3.9 is the last 0.3.x release. 0.4.0 contains breaking changes; see [Migrating to 0.4.0](https://fastapi-cachex.readthedocs.io/en/stable/MIGRATING_0_4/). diff --git a/README.md b/README.md index 6ed1a1c..4a4075c 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ async def report(cache: AppCache): ## Documentation -- [Migrating to 0.4.0](https://fastapi-cachex.readthedocs.io/en/latest/MIGRATING_0_4/) — what 0.4.0 changes and how to act on the 0.3.9 warnings now +- [Migrating to 0.4.0](https://fastapi-cachex.readthedocs.io/en/latest/MIGRATING_0_4/) — what 0.4.0 changes and how to upgrade from 0.3.x - [HTTP caching](https://fastapi-cachex.readthedocs.io/en/latest/HTTP_CACHING/) — the `@cache` decorator, Cache-Control directives, cache keys, invalidation and monitoring routes - [Application cache](https://fastapi-cachex.readthedocs.io/en/latest/APP_CACHE/) — `CacheManager` - [Backends](https://fastapi-cachex.readthedocs.io/en/latest/BACKENDS/) — choosing and configuring a backend, atomic primitives diff --git a/SECURITY.md b/SECURITY.md index 4c0fbdf..ae4358d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,16 +2,16 @@ ## Supported versions -Security fixes are released for the latest 0.3.x release line only. Upgrade -to the newest 0.3.x release before reporting, and check whether the problem is -still there. +Security fixes are released for the latest release line only, currently +0.4.x. Upgrade to the newest 0.4.x release before reporting, and check whether +the problem is still there. -| Version | Supported | -|--------------------------|-----------| -| Latest 0.3.x (now 0.3.8) | Yes | -| Any older release | No | +| Version | Supported | +|-------------------|-----------| +| Latest 0.4.x | Yes | +| 0.3.9 and older | No | -A fix ships as a new 0.3.x patch release; earlier releases are not patched. +A fix ships as a new 0.4.x patch release; earlier releases are not patched. `fastapi_cachex.session` and `fastapi_cachex.state` are deprecated in 0.4.0. They get security fixes during 0.4.x and none after 0.5.0 removes them. @@ -53,7 +53,7 @@ guaranteed response times. What you can expect: - An acknowledgement once a maintainer has read the report, and a follow-up after it has been looked into, saying whether it is being treated as a vulnerability. -- If it is, a fix in a new 0.3.x patch release, followed by a published GitHub +- If it is, a fix in a new 0.4.x patch release, followed by a published GitHub security advisory that credits you, unless you prefer not to be named. - Questions in the advisory thread if the report needs more detail. diff --git a/changelog.d/131.changed.md b/changelog.d/131.changed.md new file mode 100644 index 0000000..5f1f215 --- /dev/null +++ b/changelog.d/131.changed.md @@ -0,0 +1,5 @@ +**`get_session_manager` keeps returning the middleware's manager, and the 0.3.9 +`FutureWarning` is gone.** 0.3.9 announced that 0.4.0 would resolve it through +`SessionManagerProxy`. With sessions deprecated, that change is not made. See +the +[migration guide](https://fastapi-cachex.readthedocs.io/en/stable/MIGRATING_0_4/#get-session-manager). diff --git a/changelog.d/377.deprecated.md b/changelog.d/377.deprecated.md new file mode 100644 index 0000000..9800302 --- /dev/null +++ b/changelog.d/377.deprecated.md @@ -0,0 +1,5 @@ +**`SessionConfig.use_bearer_token` is removed in 0.5.0, not 0.4.0.** It keeps +working and keeps its `DeprecationWarning`, and goes with +`fastapi_cachex.session`; list the token sources in `token_source_priority` +instead. See the +[migration guide](https://fastapi-cachex.readthedocs.io/en/stable/MIGRATING_0_4/#token-source-priority). diff --git a/changelog.d/420.deprecated.md b/changelog.d/420.deprecated.md index 85aa7cd..7aefacd 100644 --- a/changelog.d/420.deprecated.md +++ b/changelog.d/420.deprecated.md @@ -4,5 +4,4 @@ FastAPI-CacheX is narrowing to HTTP and application caching. Importing names from the `fastapi_cachex` package, emits a `FutureWarning`; a plain `import fastapi_cachex` does not. The names are no longer in `fastapi_cachex.__all__`. Both packages get security fixes only until 0.5.0; -"Sessions and OAuth state are deprecated" in the 0.4.0 migration guide says -where to move. +the [migration guide](https://fastapi-cachex.readthedocs.io/en/stable/MIGRATING_0_4/#session-state-deprecated) says where to move. diff --git a/changelog.d/69.removed.md b/changelog.d/69.removed.md index 22fb585..662926c 100644 --- a/changelog.d/69.removed.md +++ b/changelog.d/69.removed.md @@ -4,4 +4,4 @@ reads the same custom header and `Authorization: Bearer` token and adds `request.session` and the session cookie. Replace `app.add_middleware(SessionMiddleware, ...)` with `app.add_middleware(FastAPICacheXSessionMiddleware, ...)` and set the cookie -options (see the 0.4.0 migration guide). +options (see the [migration guide](https://fastapi-cachex.readthedocs.io/en/stable/MIGRATING_0_4/#session-middleware)). diff --git a/changelog.d/75.changed.md b/changelog.d/75.changed.md new file mode 100644 index 0000000..7ab1694 --- /dev/null +++ b/changelog.d/75.changed.md @@ -0,0 +1,6 @@ +**`token_source_priority` does not have to name `"cookie"`, and the 0.3.9 +`FutureWarning` is gone.** 0.3.9 announced that 0.4.0 would read the session +cookie only when the list names it. With sessions deprecated, that change is +not made: the cookie is still read after the header sources, and `"cookie"` is +still accepted only as the last entry. See the +[migration guide](https://fastapi-cachex.readthedocs.io/en/stable/MIGRATING_0_4/#token-source-priority). diff --git a/docs/APP_CACHE.md b/docs/APP_CACHE.md index 0cf9a7e..a54488f 100644 --- a/docs/APP_CACHE.md +++ b/docs/APP_CACHE.md @@ -17,7 +17,8 @@ async def expensive_operation(cache: AppCache): return result -# Or instantiate directly, e.g. outside of a request. `lock` defaults to True +# Or instantiate directly, e.g. outside of a request, once a backend is set with +# BackendProxy.set(...). `lock` defaults to True # (see "Stampede protection"). manager = CacheManager(key_prefix="myapp:", default_ttl=60) await manager.set("user:42", {"name": "Alice"}) diff --git a/docs/BACKENDS.md b/docs/BACKENDS.md index 2d1c0b3..850b87a 100644 --- a/docs/BACKENDS.md +++ b/docs/BACKENDS.md @@ -82,7 +82,7 @@ BackendProxy.set(backend) the prefix (see [Migrating to 0.4.0](MIGRATING_0_4.md#redis-clear-pattern)) **Configuring from a model**: `RedisConfig` is a pydantic model with the same -settings and validation, which is handy when they come from environment +settings, validated (port range, `db >= 0`), which is handy when they come from environment variables or a settings file: ```python @@ -187,7 +187,8 @@ BackendProxy.set(backend) [`invalidate(request)`](HTTP_CACHING.md#invalidating-a-single-cached-route), which rebuilds the exact key - `backend.clear()` (`MemcachedBackend.clear()`) issues `flush_all`, which wipes the whole - Memcached server, not just this namespace. `CacheManager.clear()` is different: it + Memcached server, not just this namespace, and emits a `RuntimeWarning` each time. + `CacheManager.clear()` is different: it enumerates keys, so on Memcached it deletes nothing (see [Application cache](APP_CACHE.md)) - A key Memcached would reject (over 250 bytes, whitespace, non-ASCII) is stored diff --git a/docs/CACHE_FLOW.md b/docs/CACHE_FLOW.md index 555209f..b43e0dd 100644 --- a/docs/CACHE_FLOW.md +++ b/docs/CACHE_FLOW.md @@ -139,10 +139,10 @@ The decorator arguments control both the server-side behaviour and the # Normal caching behaviour @cache(ttl=3600) # Cache for 1 hour (also used as the max-age value) -@cache(public=True) # Allow shared caches, also for Authorization/session requests +@cache(ttl=3600, public=True) # Allow shared caches, also for Authorization/session requests @cache(private=True) # Private only; never touches the shared backend @cache(ttl=60, key_builder=per_user_key, cache_authorized=True) # Authorization/session requests use the backend, answered private -@cache(immutable=True) # Content never changes +@cache(ttl=3600, immutable=True) # Content never changes # Header-only directives (they do not change server-side behaviour) @cache(ttl=60, must_revalidate=True) # must-revalidate @@ -463,7 +463,7 @@ async def cleanup_task(): # The task is only started lazily on the first get/set/set_if_absent/increment/ # get_and_delete call (it needs a running event loop), so write-only usage (for -# example StateManager.create_state) starts it too. +# example CacheManager.add()) starts it too. # Redis/Memcached: TTL mechanism # Use the backend's built-in TTL (SET ... EX, exptime) diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 6e92cce..5df9262 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -58,8 +58,8 @@ so do not point these at anything you care about. When a port is set but nothing is listening, the suites skip and say so. A run with nothing opted in still clears the coverage gate (`fail_under = 90`) -at about 94.5%, but only because the rest of the suite carries it — `redis.py` -alone drops to roughly 42%. The margin is thin, so the first place an untested +at about 95%, but only because the rest of the suite carries it — `redis.py` +alone drops below 40%. The margin is thin, so the first place an untested line shows up as a failure is a local opted-out run, not CI, which sets both variables against its own service containers and sees 99.95%. diff --git a/docs/HTTP_CACHING.md b/docs/HTTP_CACHING.md index 94c452e..ffb44aa 100644 --- a/docs/HTTP_CACHING.md +++ b/docs/HTTP_CACHING.md @@ -146,8 +146,8 @@ A response that belongs to one caller is never stored either (#296): backend anyway, but its response to such a request still gets `private` (before 0.3.9 it was sent without it, #362); `private=True` routes send it already. - A request has a session when `FastAPICacheXSessionMiddleware` loaded one - for it, from the token header, a + A request has a session when `FastAPICacheXSessionMiddleware` (deprecated, + removed in 0.5.0) loaded one for it, from the token header, a bearer token or the session cookie, with or without a user, or when `request.session` is non-empty under any session middleware, Starlette's included. A token that resolves to no session (forged, expired) does not @@ -278,8 +278,8 @@ async def report(): return await build_report() ``` -This only covers `@cache`. `invalidate()`, `CacheManager`, `StateManager`, -`CacheLock` and sessions still raise backend errors to the caller. +This only covers `@cache`. `invalidate()`, `CacheManager`, `CacheLock` and the +deprecated `StateManager` and sessions still raise backend errors to the caller. ## Cache keys @@ -295,7 +295,7 @@ another tag, so its keys never collide with these; on Redis and memory, This ensures that: -- Different HTTP methods (GET, POST, etc.) don't share cache +- The method is part of the key (only `GET` is cached, but a custom key builder sees it) - Different hosts don't share cache (useful for multi-tenant scenarios) - Different query parameters get separate cache entries - The same endpoint with different parameters can be cached independently @@ -348,9 +348,9 @@ The host and path come from the client, so `|` and `%` in them are percent-encod the components and make one request's key equal another's. The query string is URL-encoded already. `clear_path()` takes the path as your application sees it (`request.url.path`) and encodes it the same way; `clear_pattern()` matches the -stored key, so write `%7C` there for a `|`. Before 0.3.8 both were stored as sent, -so after upgrading, entries for a host or path containing `|` or `%` are cached -afresh once. +stored key, so write `%7C` there for a `|`. 0.4.0 does not read entries +written by 0.3.x at all; they are cached afresh once (see +[Migrating to 0.4.0](MIGRATING_0_4.md#cache-keys)). The host is normalised first, so every spelling of one origin shares an entry: it is lower-cased (hostnames are case-insensitive), and an empty port or the @@ -475,7 +475,7 @@ The key is not secret: it is listed by `get_all_keys()`, shown by the `/cached-records` and `/cached-hits` monitoring routes, and stored as-is in the Redis or Memcached keyspace. So for the headers that carry credentials, `Authorization`, `Proxy-Authorization`, `Cookie` and `X-Session-Token` (the -session subsystem's default `header_name`), matched in any case, the component +deprecated session subsystem's default `header_name`), matched in any case, the component holds the full hex SHA-256 of the value (trimmed and joined as above) instead of the value: @@ -580,8 +580,8 @@ request it is given selects. > `cache_authorized=True` when callers authenticate with an `Authorization` > header or a session: without it such requests bypass the backend too. > -> If callers authenticate with a cookie of your own rather than the library's -> session, nothing triggers the bypass: a plain `@cache` serves the first +> If callers authenticate with a cookie that no session middleware loads (for +> example a token cookie your own dependency reads), nothing triggers the bypass: a plain `@cache` serves the first > caller's response to everyone, so use one of the two options above. ```python diff --git a/docs/LOCK.md b/docs/LOCK.md index 2d24852..3860713 100644 --- a/docs/LOCK.md +++ b/docs/LOCK.md @@ -28,7 +28,7 @@ Complete runnable example: [`examples/cache_lock.py`](https://github.com/allen00 - **Safety & Token Ownership**: Each `CacheLock` instance generates a unique token (`secrets.token_hex(16)`) stored inside a `CacheEntry`. Releases (`release()`) and extensions (`extend()`) use owner-checked backend primitives (`delete_if_equals` and `expire_if_equals`), so a holder whose lock expired cannot release or renew a lock claimed by someone else. - **Blocking & Non-blocking Modes**: - Non-blocking (`acquire(blocking=False)`): Performs a single atomic `set_if_absent` and immediately returns `True` if acquired or `False` if held. - - Blocking (`acquire(blocking=True, timeout=None, poll_interval=0.1)`): Retries at `poll_interval` seconds until acquired or until `timeout` seconds elapse. The default `timeout=None` makes a blocking `acquire()` (and `async with CacheLock(...)`) wait indefinitely until the lock becomes free. If a finite `timeout` is reached, `acquire()` returns `False`. + - Blocking (the default; `CacheLock(..., blocking=True, timeout=None, poll_interval=0.1)`, each overridable per `acquire()` call, where `None` means "use the instance's value"): Retries at `poll_interval` seconds until acquired or until `timeout` seconds elapse. The default `timeout=None` makes a blocking `acquire()` (and `async with CacheLock(...)`) wait indefinitely until the lock becomes free. If a finite `timeout` is reached, `acquire()` returns `False`. - **Context Manager Timeouts**: Entering a context manager (`async with CacheLock(...)`) invokes `acquire()`. If acquisition fails or times out, it raises `LockTimeoutError`. - **TTL Expiration**: If a task takes longer than its `ttl` and fails to renew, the lock entry expires in the backend and becomes free. Another process or container can then acquire the lock while the original code is still running. Subsequent calls to `extend()` or `release()` by the original holder will safely return `False` without throwing an error. Always choose a `ttl` longer than the expected work, or call `extend()` periodically during long-running operations. - **TTL Renewal (`extend`)**: `extend(ttl)` updates the key's TTL only while the lock is still owned by this holder instance, preventing race conditions on expired locks. diff --git a/docs/MIGRATING_0_4.md b/docs/MIGRATING_0_4.md index dae2f97..4135098 100644 --- a/docs/MIGRATING_0_4.md +++ b/docs/MIGRATING_0_4.md @@ -1,6 +1,6 @@ # Migrating to 0.4.0 {#migrating-to-040} -0.3.9 is the last 0.3.x release. 0.4.0 contains breaking changes, collected in the [0.4.0 milestone](https://github.com/allen0099/FastAPI-CacheX/issues?q=milestone%3A0.4.0). This page lists every one of them, what to change, and whether 0.3.9 already warns about it. Some 0.4.0 designs are not final yet; where an issue leaves a detail open, the section says what is known and what is still undecided. +0.3.9 is the last 0.3.x release. 0.4.0 contains breaking changes, collected in the [0.4.0 milestone](https://github.com/allen0099/FastAPI-CacheX/issues?q=milestone%3A0.4.0). This page lists every one of them, what to change, and whether 0.3.9 already warns about it. Three changes 0.3.9 announced for sessions are not made as announced; see [Token sources](#token-source-priority) and [get_session_manager](#get-session-manager). ## Before you upgrade {#before-you-upgrade} @@ -40,7 +40,7 @@ Every warning below names the setting to change and links to its issue. `FutureW | `BackendProxy.get_backend()` / `set_backend()` removed | [#70](https://github.com/allen0099/FastAPI-CacheX/issues/70) | `DeprecationWarning` | [BackendProxy](#backend-proxy) | | `CacheError` removed | [#130](https://github.com/allen0099/FastAPI-CacheX/issues/130) | `DeprecationWarning` | [CacheError](#cache-error) | | Redis `clear_pattern()` prefix-stripped retry removed | [#125](https://github.com/allen0099/FastAPI-CacheX/issues/125) | `DeprecationWarning` | [Redis clear_pattern](#redis-clear-pattern) | -| `SessionConfig.use_bearer_token` stays deprecated, removed in 0.5.0 | [#377](https://github.com/allen0099/FastAPI-CacheX/issues/377) | `DeprecationWarning` | [Token sources](#token-source-priority) | +| `SessionConfig.use_bearer_token` stays deprecated, removed in 0.5.0 | [#377](https://github.com/allen0099/FastAPI-CacheX/issues/377), [#421](https://github.com/allen0099/FastAPI-CacheX/issues/421) | `DeprecationWarning` | [Token sources](#token-source-priority) | | `UserSessionDep` requires a user | [#127](https://github.com/allen0099/FastAPI-CacheX/issues/127) | No | [UserSessionDep](#user-session-dep) | | `memcache` extra removed | [#202](https://github.com/allen0099/FastAPI-CacheX/issues/202) | No | [memcache extra](#memcache-extra) | | `BaseCacheBackend.delete()` returns `bool` | [#71](https://github.com/allen0099/FastAPI-CacheX/issues/71) | No | [delete() return value](#backend-delete) | @@ -153,7 +153,7 @@ await login(request, SessionUser(user_id=user_id)) ### UserSessionDep {#user-session-dep} -`UserSessionDep` is an alias of `SessionDep` and admits anonymous sessions. In 0.4.0 it requires a session with a user, like `AuthenticatedSession` ([#127](https://github.com/allen0099/FastAPI-CacheX/issues/127)), so anonymous requests to routes that use it get `401`. 0.3.9 does not warn: a type alias has no hook that runs when it is used, and a warning on import would fire for everyone. Pick the dependency that says what you mean: +In 0.3.x, `UserSessionDep` is an alias of `SessionDep` and admits anonymous sessions. In 0.4.0 it requires a session with a user, like `AuthenticatedSession` ([#127](https://github.com/allen0099/FastAPI-CacheX/issues/127)), so anonymous requests to routes that use it get `401`. 0.3.9 does not warn: a type alias has no hook that runs when it is used, and a warning on import would fire for everyone. Pick the dependency that says what you mean: ```python # Before @@ -216,7 +216,7 @@ SessionConfig( 0.3.9 announced two changes here. Since sessions are deprecated ([#420](https://github.com/allen0099/FastAPI-CacheX/issues/420)), 0.4.0 makes neither of them: - **`token_source_priority` naming every token source ([#75](https://github.com/allen0099/FastAPI-CacheX/issues/75)) is dropped.** The middleware still reads the session cookie after the header sources whether or not the list names it, and `"cookie"` is still accepted only as the last entry. 0.4.0 no longer emits the `FutureWarning` for a list without `"cookie"`. A list that already ends in `"cookie"` keeps working. -- **`use_bearer_token` is not removed in 0.4.0 ([#377](https://github.com/allen0099/FastAPI-CacheX/issues/377)).** It stays deprecated, still emits a `DeprecationWarning` when it is passed, and is removed in 0.5.0 together with `fastapi_cachex.session`. The list alone decides whether bearer tokens are read: +- **`use_bearer_token` is not removed in 0.4.0 ([#377](https://github.com/allen0099/FastAPI-CacheX/issues/377)).** It stays deprecated, still emits a `DeprecationWarning` when it is passed, and is removed in 0.5.0 together with `fastapi_cachex.session`. `use_bearer_token=False` still turns bearer tokens off in 0.4.x; move to the list now, which alone decides from 0.5.0: ```python # Before @@ -241,19 +241,18 @@ manager = CacheManager(key_prefix="myapp:") value = await manager.get_or_set("report", build_report) ``` -After, either per manager or per call: +After, to keep the 0.3.x behaviour, either per manager or per call (`lock=True` is the default): ```python -manager = CacheManager(key_prefix="myapp:", lock=False) # keep today's behaviour -manager = CacheManager(key_prefix="myapp:", lock=True) # opt in now +manager = CacheManager(key_prefix="myapp:", lock=False) -value = await manager.get_or_set("report", build_report, lock=True) +value = await manager.get_or_set("report", build_report, lock=False) ``` -With `AppCache`, register your own manager at startup: +With `AppCache`, register your own manager at startup, after `BackendProxy.set(...)`: ```python -CacheManagerProxy.set(CacheManager(lock=True)) +CacheManagerProxy.set(CacheManager(lock=False)) ``` ## HTTP caching {#http-caching} @@ -374,7 +373,7 @@ backend = BackendProxy.get() ### CacheError {#cache-error} -The `CacheError` alias is removed ([#130](https://github.com/allen0099/FastAPI-CacheX/issues/130)); 0.3.x already emits a `DeprecationWarning` when it is imported. +The `CacheError` alias is removed ([#130](https://github.com/allen0099/FastAPI-CacheX/issues/130)); 0.3.8 and 0.3.9 already emit a `DeprecationWarning` when it is imported. ```python # Before diff --git a/i18n/zh-TW/docs/APP_CACHE.md b/i18n/zh-TW/docs/APP_CACHE.md index fa951e0..b9b19de 100644 --- a/i18n/zh-TW/docs/APP_CACHE.md +++ b/i18n/zh-TW/docs/APP_CACHE.md @@ -15,7 +15,8 @@ async def expensive_operation(cache: AppCache): return result -# 也可以直接建立實例,例如在請求之外使用。`lock` 預設為 True +# 也可以直接建立實例,例如在請求之外使用,前提是已用 BackendProxy.set(...) +# 設定後端。`lock` 預設為 True # (見「Cache stampede 保護」)。 manager = CacheManager(key_prefix="myapp:", default_ttl=60) await manager.set("user:42", {"name": "Alice"}) diff --git a/i18n/zh-TW/docs/BACKENDS.md b/i18n/zh-TW/docs/BACKENDS.md index 3a77497..d0ec97d 100644 --- a/i18n/zh-TW/docs/BACKENDS.md +++ b/i18n/zh-TW/docs/BACKENDS.md @@ -54,7 +54,7 @@ BackendProxy.set(backend) - 只有傳給 `clear_pattern()` 的模式是萬用字元(glob)模式。鍵前綴與傳給 `clear_path()` 的路徑都以字面值比對,因此其中的 `*`、`?`、`[` 或 `]` 不會觸及前綴以外的鍵,也不會漏掉該路徑 - `clear_pattern()` 比對的是邏輯鍵,也就是不含後端前綴的鍵,並一律自行加上前綴,因此模式中不要寫出前綴。以前綴開頭的模式不會被去掉前綴:它只會比對到本身以前綴開頭的邏輯鍵(見[遷移至 0.4.0](MIGRATING_0_4.md#redis-clear-pattern)) -**從模型設定**:`RedisConfig` 是具有相同設定項與驗證的 pydantic 模型,當設定來自環境變數或設定檔時很方便: +**從模型設定**:`RedisConfig` 是具有相同設定項的 pydantic 模型,並會驗證其範圍(連接埠範圍、`db >= 0`),當設定來自環境變數或設定檔時很方便: ```python from fastapi_cachex.backends import AsyncRedisCacheBackend @@ -128,7 +128,7 @@ BackendProxy.set(backend) - Memcached 協定不支援依模式清除鍵(`clear_pattern`):它會回傳 0 並發出 `RuntimeWarning` - 無法列舉鍵:`get_all_keys()`/`get_cache_data()` 會回傳空結果(並發出 `RuntimeWarning`),因此監控路由不會顯示任何內容 - `clear_path()` 找不到 HTTP 快取項目:它只會刪除名稱與路徑完全相同的鍵,忽略 `include_params`,而且每次呼叫都會發出 `RuntimeWarning`。資料變更後要刪除某個快取路由的項目,請呼叫 [`invalidate(request)`](HTTP_CACHING.md#invalidating-a-single-cached-route),它會重建完全相同的鍵 -- `backend.clear()`(`MemcachedBackend.clear()`)會發出 `flush_all`,清空整台 Memcached 伺服器,而不只是這個命名空間。`CacheManager.clear()` 則不同:它需要列舉鍵,因此在 Memcached 上不會刪除任何東西(見[應用層快取](APP_CACHE.md)) +- `backend.clear()`(`MemcachedBackend.clear()`)會發出 `flush_all`,清空整台 Memcached 伺服器,而不只是這個命名空間,且每次呼叫都會發出 `RuntimeWarning`。`CacheManager.clear()` 則不同:它需要列舉鍵,因此在 Memcached 上不會刪除任何東西(見[應用層快取](APP_CACHE.md)) - Memcached 會拒絕的鍵(超過 250 位元組、含空白字元或非 ASCII 字元)會改以其 SHA-256 摘要儲存 - 過期時間落在 2038-01-19 之後的 `ttl` 會拋出 `ValueError`(見 [TTL 值](#ttl-values)) - 超過伺服器項目大小上限(預設 1 MB,可用 `memcached -I` 調整)的值會被拒絕並拋出錯誤。`@cache` 會記錄該錯誤,並照常送出不儲存的回應(見[後端發生錯誤時](HTTP_CACHING.md#when-the-backend-fails));其他呼叫端則會收到該錯誤 diff --git a/i18n/zh-TW/docs/CACHE_FLOW.md b/i18n/zh-TW/docs/CACHE_FLOW.md index a898141..efc0a1a 100644 --- a/i18n/zh-TW/docs/CACHE_FLOW.md +++ b/i18n/zh-TW/docs/CACHE_FLOW.md @@ -107,10 +107,10 @@ cache_key = "|".join( # 一般快取行為 @cache(ttl=3600) # 快取 1 小時(也作為 max-age 的值) -@cache(public=True) # 允許共用快取,帶有 Authorization/Session 的請求也一樣 +@cache(ttl=3600, public=True) # 允許共用快取,帶有 Authorization/Session 的請求也一樣 @cache(private=True) # 僅限私有;永遠不接觸共用後端 @cache(ttl=60, key_builder=per_user_key, cache_authorized=True) # 帶有 Authorization/Session 的請求也使用後端,以 private 回應 -@cache(immutable=True) # 內容永不改變 +@cache(ttl=3600, immutable=True) # 內容永不改變 # 只影響標頭的指令(不會改變伺服器端行為) @cache(ttl=60, must_revalidate=True) # must-revalidate @@ -356,7 +356,7 @@ async def cleanup_task(): # 此任務只會在第一次呼叫 get/set/set_if_absent/increment/ # get_and_delete 時延遲啟動(它需要執行中的事件迴圈),因此只寫入的 -# 用法(例如 StateManager.create_state)也會啟動它。 +# 用法(例如 CacheManager.add())也會啟動它。 # Redis/Memcached:TTL 機制 # 使用後端內建的 TTL(SET ... EX、exptime) diff --git a/i18n/zh-TW/docs/HTTP_CACHING.md b/i18n/zh-TW/docs/HTTP_CACHING.md index 5621cb2..8a149a2 100644 --- a/i18n/zh-TW/docs/HTTP_CACHING.md +++ b/i18n/zh-TW/docs/HTTP_CACHING.md @@ -87,7 +87,7 @@ GET /items → 200, Cache-Control: max-age=60, Age: 42(儲存後 42 秒送出 屬於單一呼叫者的回應同樣不會被儲存(#296): -- **請求帶有 `Authorization` 或 Session。** 依照 RFC 9111 §3.5 對共用快取的要求,這類請求會像 `private=True` 一樣繞過後端:不讀取也不寫入,handler 照常執行,`If-None-Match` 與新產生的回應比對。回應(以及 304)會以 `private` 取代 `public` 送出,並保留裝飾器的其他指令(`no_cache` 路由則為 `private, no-cache`),讓 CDN 或代理也不會儲存它。`public=True` 的路由不受此限。設定 `cache_authorized=True`(給包含呼叫者身分的 key builder 使用的明確選項,見[需驗證身分的端點](#authenticated-endpoints))的路由會為這類請求讀寫後端,但回應仍帶有 `private`:項目只在後端依呼叫者區分,CDN 則只以 URL 為鍵(0.3.9 之前會原樣送出裝飾器的標頭,#372)。`must_revalidate=True` 不會解除繞過:RFC 9111 允許共用快取在 `must-revalidate` 下重複使用這類回應,但本函式庫要求明確選擇啟用。沒有正數 `ttl` 的路由本來就不經過後端,但它對這類請求的回應仍會加上 `private`(0.3.9 之前不會加,#362);`private=True` 的路由本來就會送出 `private`。請求「帶有 Session」是指 `FastAPICacheXSessionMiddleware` 為它載入了 Session(權杖來自標頭、Bearer 權杖或 Session Cookie 皆可,有沒有使用者都算),或在任何 Session 中介軟體(包括 Starlette 的)下 `request.session` 不是空的。解析不出 Session 的權杖(偽造、過期)不算,因此無法用來略過快取。0.3.9 之前只有 `Authorization` 會觸發繞過,讀取 Session 的路由只加上 `@cache` 時,會把一位訪客的回應提供給下一位(#319)。會讀取後端的路由第一次繞過時,會以 `WARNING` 等級記錄(見[帶有憑證的請求](#requests-with-credentials))。 +- **請求帶有 `Authorization` 或 Session。** 依照 RFC 9111 §3.5 對共用快取的要求,這類請求會像 `private=True` 一樣繞過後端:不讀取也不寫入,handler 照常執行,`If-None-Match` 與新產生的回應比對。回應(以及 304)會以 `private` 取代 `public` 送出,並保留裝飾器的其他指令(`no_cache` 路由則為 `private, no-cache`),讓 CDN 或代理也不會儲存它。`public=True` 的路由不受此限。設定 `cache_authorized=True`(給包含呼叫者身分的 key builder 使用的明確選項,見[需驗證身分的端點](#authenticated-endpoints))的路由會為這類請求讀寫後端,但回應仍帶有 `private`:項目只在後端依呼叫者區分,CDN 則只以 URL 為鍵(0.3.9 之前會原樣送出裝飾器的標頭,#372)。`must_revalidate=True` 不會解除繞過:RFC 9111 允許共用快取在 `must-revalidate` 下重複使用這類回應,但本函式庫要求明確選擇啟用。沒有正數 `ttl` 的路由本來就不經過後端,但它對這類請求的回應仍會加上 `private`(0.3.9 之前不會加,#362);`private=True` 的路由本來就會送出 `private`。請求「帶有 Session」是指 `FastAPICacheXSessionMiddleware`(已棄用,0.5.0 移除)為它載入了 Session(權杖來自標頭、Bearer 權杖或 Session Cookie 皆可,有沒有使用者都算),或在任何 Session 中介軟體(包括 Starlette 的)下 `request.session` 不是空的。解析不出 Session 的權杖(偽造、過期)不算,因此無法用來略過快取。0.3.9 之前只有 `Authorization` 會觸發繞過,讀取 Session 的路由只加上 `@cache` 時,會把一位訪客的回應提供給下一位(#319)。會讀取後端的路由第一次繞過時,會以 `WARNING` 等級記錄(見[帶有憑證的請求](#requests-with-credentials))。 - **handler 自己的 `Cache-Control` 含有 `private` 或 `no-store`**(完整指令,不分大小寫)。回應照常送出但不儲存,而且 handler 的標頭會原樣送出,不會被裝飾器的標頭取代。 - **回應設定了 cookie。** 回應照常送出(包含 `Set-Cookie`),但不儲存;它(以及 304)會以 `private` 取代 `public` 送出並保留其他指令,讓下游的共用快取也不會儲存它。 @@ -154,7 +154,7 @@ async def report(): return await build_report() ``` -這只適用於 `@cache`。`invalidate()`、`CacheManager`、`StateManager`、`CacheLock` 與 Session 仍會把後端錯誤拋給呼叫端。 +這只適用於 `@cache`。`invalidate()`、`CacheManager`、`CacheLock`,以及已棄用的 `StateManager` 與 Session 仍會把後端錯誤拋給呼叫端。 ## 快取鍵 {#cache-keys} @@ -168,7 +168,7 @@ http:v2|{method}|{host}|{path}|{query_params} 這可確保: -- 不同的 HTTP 方法(GET、POST 等)不共用快取 +- 方法是快取鍵的一部分(只有 `GET` 會被快取,但自訂的 key builder 看得到它) - 不同的主機不共用快取(適用於多租戶情境) - 不同的查詢參數各有獨立的快取項目 - 同一個端點搭配不同參數時可以各自快取 @@ -190,7 +190,7 @@ async def search(request: Request, q: str, limit: int = 10): 在鍵中編碼後超過 200 位元組的查詢字串,會改以 `sha256:` 加上 64 個十六進位字元的摘要儲存,因此用戶端無法讓鍵中查詢的部分無限變長。(超過 250 位元組的整個鍵,含前綴,Memcached 仍會整個雜湊;略低於門檻的查詢配上較長的 host 或路徑仍可能超過。)摘要在排序之後計算,因此順序不同的長查詢仍共用同一筆項目。路徑維持可讀,所以 `clear_path()` 仍找得到該項目(需帶上 `include_params=True`,因為查詢不是空的),監控路由則以 `query_params` 顯示這個摘要。用戶端送出的查詢不可能看起來像摘要:鍵中的 `:` 會寫成 `%3A`。 -host 與路徑來自用戶端,因此其中的 `|` 與 `%` 會以百分比編碼寫入(`%7C` 與 `%25`)。含有 `|` 的 `Host` 標頭或路徑因此無法讓各段錯位,使某個請求的快取鍵與另一個請求相同。查詢字串本來就經過 URL 編碼。`clear_path()` 接受應用程式看到的路徑(`request.url.path`),並以同樣方式編碼;`clear_pattern()` 比對的是儲存的快取鍵,所以在模式中要把 `|` 寫成 `%7C`。0.3.8 之前兩者都照原樣儲存,因此升級後,host 或路徑含有 `|` 或 `%` 的項目會重新快取一次。 +host 與路徑來自用戶端,因此其中的 `|` 與 `%` 會以百分比編碼寫入(`%7C` 與 `%25`)。含有 `|` 的 `Host` 標頭或路徑因此無法讓各段錯位,使某個請求的快取鍵與另一個請求相同。查詢字串本來就經過 URL 編碼。`clear_path()` 接受應用程式看到的路徑(`request.url.path`),並以同樣方式編碼;`clear_pattern()` 比對的是儲存的快取鍵,所以在模式中要把 `|` 寫成 `%7C`。0.4.0 完全不讀取 0.3.x 寫入的項目,它們會重新快取一次(見[遷移至 0.4.0](MIGRATING_0_4.md#cache-keys))。 host 會先經過正規化,讓同一個來源的各種寫法共用同一筆項目:轉為小寫(主機名稱不分大小寫),並去除空的連接埠或該 scheme 的預設連接埠(http 為 `:80`,https 為 `:443`)。在 http 上,`Example.com`、`example.com:80` 與 `example.com` 是同一個鍵 `example.com`;`example.com:8080` 保留連接埠,IPv6 位址則保留方括號(`[::1]:8000`)。scheme 以應用程式看到的為準:在終止 TLS 的代理之後,除非套用了代理的標頭(例如 `uvicorn --proxy-headers`),否則 scheme 是 `http`,因此這類代理送來的 `Host: example.com:443` 會保留連接埠。沒有 `Host` 標頭的請求使用 `unknown`。 @@ -260,7 +260,7 @@ async def greeting(request: Request): #### 憑證標頭會雜湊 {#credential-headers-are-hashed} -快取鍵並非機密:`get_all_keys()` 會列出它、`/cached-records` 與 `/cached-hits` 監控路由會顯示它,Redis 或 Memcached 的鍵空間也會原樣儲存它。因此對於攜帶憑證的標頭,也就是 `Authorization`、`Proxy-Authorization`、`Cookie` 與 `X-Session-Token`(Session 子系統預設的 `header_name`),不分大小寫,該段存放的是值(依上述方式去除空白並串接)的完整十六進位 SHA-256,而不是值本身: +快取鍵並非機密:`get_all_keys()` 會列出它、`/cached-records` 與 `/cached-hits` 監控路由會顯示它,Redis 或 Memcached 的鍵空間也會原樣儲存它。因此對於攜帶憑證的標頭,也就是 `Authorization`、`Proxy-Authorization`、`Cookie` 與 `X-Session-Token`(已棄用的 Session 子系統預設的 `header_name`),不分大小寫,該段存放的是值(依上述方式去除空白並串接)的完整十六進位 SHA-256,而不是值本身: ``` http:v2|GET|example.com|/me||authorization=sha256:3f0a…(64 個十六進位字元) @@ -321,7 +321,7 @@ warnings.filterwarnings("ignore", message="cache vary on Cookie") > 1. **`private=True`**:回應永遠不會從共用後端讀取,也不會寫入。`Cache-Control: private` 仍允許使用者自己的瀏覽器快取它,而 `If-None-Match` 重新驗證仍會對新產生的內容運作。 > 2. **包含呼叫者身分的 key builder**:確實需要依使用者區分的伺服器端快取時使用。不要設定 `private`:`private=True` 會繞過後端,key builder 就永遠不會被使用。呼叫者以 `Authorization` 標頭或 Session 驗證身分時,請傳入 `cache_authorized=True`:沒有它,這類請求同樣會繞過後端。 > -> 若呼叫者以你自己的 Cookie(而非本函式庫的 Session)驗證身分,沒有任何條件會觸發繞過:只加上 `@cache` 會把第一位呼叫者的回應提供給所有人,請改用上面兩種做法之一。 +> 若呼叫者以沒有任何 Session 中介軟體載入的 Cookie 驗證身分(例如由你自己的依賴項讀取的權杖 Cookie),沒有任何條件會觸發繞過:只加上 `@cache` 會把第一位呼叫者的回應提供給所有人,請改用上面兩種做法之一。 ```python from fastapi import Request diff --git a/i18n/zh-TW/docs/LOCK.md b/i18n/zh-TW/docs/LOCK.md index 4f9235d..7af1345 100644 --- a/i18n/zh-TW/docs/LOCK.md +++ b/i18n/zh-TW/docs/LOCK.md @@ -28,7 +28,7 @@ finally: - **安全性與權杖所有權**:每個 `CacheLock` 實例都會產生一個唯一的權杖(`secrets.token_hex(16)`),存放在 `CacheEntry` 中。釋放(`release()`)與續約(`extend()`)都使用會檢查持有者的後端原子操作(`delete_if_equals` 與 `expire_if_equals`),因此鎖已過期的持有者無法釋放或續約已被他人取得的鎖。 - **阻塞與非阻塞模式**: - 非阻塞(`acquire(blocking=False)`):只執行一次原子性的 `set_if_absent`,取得時立即回傳 `True`,已被占用時回傳 `False`。 - - 阻塞(`acquire(blocking=True, timeout=None, poll_interval=0.1)`):每隔 `poll_interval` 秒重試一次,直到取得鎖或經過 `timeout` 秒為止。預設的 `timeout=None` 會讓阻塞的 `acquire()`(以及 `async with CacheLock(...)`)一直等到鎖被釋放。若到達有限的 `timeout`,`acquire()` 會回傳 `False`。 + - 阻塞(預設;`CacheLock(..., blocking=True, timeout=None, poll_interval=0.1)`,每個值都可以在單次 `acquire()` 呼叫中覆寫,傳入 `None` 表示「沿用實例的值」):每隔 `poll_interval` 秒重試一次,直到取得鎖或經過 `timeout` 秒為止。預設的 `timeout=None` 會讓阻塞的 `acquire()`(以及 `async with CacheLock(...)`)一直等到鎖被釋放。若到達有限的 `timeout`,`acquire()` 會回傳 `False`。 - **Context manager 逾時**:進入 context manager(`async with CacheLock(...)`)時會呼叫 `acquire()`。若取得失敗或逾時,會拋出 `LockTimeoutError`。 - **TTL 過期**:若工作花費的時間超過 `ttl` 且沒有續約,鎖的項目會在後端過期並被釋出。此時其他行程或容器就能在原本的程式碼仍在執行時取得這把鎖。原持有者之後呼叫 `extend()` 或 `release()` 會安全地回傳 `False`,而不會拋出錯誤。請選擇比預期工作時間更長的 `ttl`,或在長時間執行的操作中定期呼叫 `extend()`。 - **TTL 續約(`extend`)**:`extend(ttl)` 只在鎖仍由這個持有者實例擁有時才更新鍵的 TTL,避免在已過期的鎖上發生競爭條件。 diff --git a/i18n/zh-TW/docs/MIGRATING_0_4.md b/i18n/zh-TW/docs/MIGRATING_0_4.md index e6e1162..b55c64d 100644 --- a/i18n/zh-TW/docs/MIGRATING_0_4.md +++ b/i18n/zh-TW/docs/MIGRATING_0_4.md @@ -1,6 +1,6 @@ # 遷移至 0.4.0 {#migrating-to-040} -0.3.9 是最後一個 0.3.x 版本。0.4.0 包含破壞性變更,收錄於 [0.4.0 milestone](https://github.com/allen0099/FastAPI-CacheX/issues?q=milestone%3A0.4.0)。本頁逐一列出這些變更、需要修改的地方,以及 0.3.9 是否已經發出警告。部分 0.4.0 的設計尚未定案;若 issue 對某個細節仍未決定,該節會說明目前已知與尚未決定的部分。 +0.3.9 是最後一個 0.3.x 版本。0.4.0 包含破壞性變更,收錄於 [0.4.0 milestone](https://github.com/allen0099/FastAPI-CacheX/issues?q=milestone%3A0.4.0)。本頁逐一列出這些變更、需要修改的地方,以及 0.3.9 是否已經發出警告。0.3.9 預告的三項 Session 變更並未照預告進行;見[權杖來源](#token-source-priority)與 [get_session_manager](#get-session-manager)。 ## 升級前 {#before-you-upgrade} @@ -40,7 +40,7 @@ filterwarnings = [ | 移除 `BackendProxy.get_backend()`/`set_backend()` | [#70](https://github.com/allen0099/FastAPI-CacheX/issues/70) | `DeprecationWarning` | [BackendProxy](#backend-proxy) | | 移除 `CacheError` | [#130](https://github.com/allen0099/FastAPI-CacheX/issues/130) | `DeprecationWarning` | [CacheError](#cache-error) | | 移除 Redis `clear_pattern()` 去除前綴後的重試 | [#125](https://github.com/allen0099/FastAPI-CacheX/issues/125) | `DeprecationWarning` | [Redis clear_pattern](#redis-clear-pattern) | -| `SessionConfig.use_bearer_token` 維持已棄用,0.5.0 移除 | [#377](https://github.com/allen0099/FastAPI-CacheX/issues/377) | `DeprecationWarning` | [權杖來源](#token-source-priority) | +| `SessionConfig.use_bearer_token` 維持已棄用,0.5.0 移除 | [#377](https://github.com/allen0099/FastAPI-CacheX/issues/377), [#421](https://github.com/allen0099/FastAPI-CacheX/issues/421) | `DeprecationWarning` | [權杖來源](#token-source-priority) | | `UserSessionDep` 需要使用者 | [#127](https://github.com/allen0099/FastAPI-CacheX/issues/127) | 否 | [UserSessionDep](#user-session-dep) | | 移除 `memcache` extra | [#202](https://github.com/allen0099/FastAPI-CacheX/issues/202) | 否 | [memcache extra](#memcache-extra) | | `BaseCacheBackend.delete()` 回傳 `bool` | [#71](https://github.com/allen0099/FastAPI-CacheX/issues/71) | 否 | [delete() 的回傳值](#backend-delete) | @@ -153,7 +153,7 @@ await login(request, SessionUser(user_id=user_id)) ### UserSessionDep {#user-session-dep} -`UserSessionDep` 是 `SessionDep` 的別名,也接受匿名 Session。0.4.0 起它與 `AuthenticatedSession` 一樣需要帶有使用者的 Session([#127](https://github.com/allen0099/FastAPI-CacheX/issues/127)),因此匿名請求存取使用它的路由會得到 `401`。0.3.9 不會警告:型別別名在被使用時沒有可以執行程式碼的時機,而在匯入時警告會對每個人觸發。請選擇符合你本意的依賴項: +在 0.3.x 中,`UserSessionDep` 是 `SessionDep` 的別名,也接受匿名 Session。0.4.0 起它與 `AuthenticatedSession` 一樣需要帶有使用者的 Session([#127](https://github.com/allen0099/FastAPI-CacheX/issues/127)),因此匿名請求存取使用它的路由會得到 `401`。0.3.9 不會警告:型別別名在被使用時沒有可以執行程式碼的時機,而在匯入時警告會對每個人觸發。請選擇符合你本意的依賴項: ```python # 修改前 @@ -216,7 +216,7 @@ SessionConfig( 0.3.9 在這裡預告了兩項變更。由於 Session 已棄用([#420](https://github.com/allen0099/FastAPI-CacheX/issues/420)),0.4.0 兩項都不會做: - **取消 `token_source_priority` 列出所有權杖來源([#75](https://github.com/allen0099/FastAPI-CacheX/issues/75))。** 不論清單是否列出 `"cookie"`,中介軟體仍會在標頭來源之後讀取 Session Cookie,且 `"cookie"` 仍只能放在最後一項。0.4.0 不再對沒有 `"cookie"` 的清單發出 `FutureWarning`。已經以 `"cookie"` 結尾的清單可以照常使用。 -- **0.4.0 不會移除 `use_bearer_token`([#377](https://github.com/allen0099/FastAPI-CacheX/issues/377))。** 它維持已棄用,傳入時仍會發出 `DeprecationWarning`,並在 0.5.0 隨 `fastapi_cachex.session` 一起移除。是否讀取 Bearer 權杖只由清單決定: +- **0.4.0 不會移除 `use_bearer_token`([#377](https://github.com/allen0099/FastAPI-CacheX/issues/377))。** 它維持已棄用,傳入時仍會發出 `DeprecationWarning`,並在 0.5.0 隨 `fastapi_cachex.session` 一起移除。0.4.x 中 `use_bearer_token=False` 仍會關閉 Bearer 權杖;請現在就改用清單,0.5.0 起只由清單決定: ```python # 修改前 @@ -241,19 +241,18 @@ manager = CacheManager(key_prefix="myapp:") value = await manager.get_or_set("report", build_report) ``` -修改後,可以針對 manager 或單次呼叫設定: +修改後,若要保留 0.3.x 的行為,可以針對 manager 或單次呼叫設定(`lock=True` 是預設值): ```python -manager = CacheManager(key_prefix="myapp:", lock=False) # 保留目前的行為 -manager = CacheManager(key_prefix="myapp:", lock=True) # 現在就啟用 +manager = CacheManager(key_prefix="myapp:", lock=False) -value = await manager.get_or_set("report", build_report, lock=True) +value = await manager.get_or_set("report", build_report, lock=False) ``` -使用 `AppCache` 時,請在啟動時註冊自己的 manager: +使用 `AppCache` 時,請在啟動時、`BackendProxy.set(...)` 之後註冊自己的 manager: ```python -CacheManagerProxy.set(CacheManager(lock=True)) +CacheManagerProxy.set(CacheManager(lock=False)) ``` ## HTTP 快取 {#http-caching} @@ -374,7 +373,7 @@ backend = BackendProxy.get() ### CacheError {#cache-error} -`CacheError` 別名會被移除([#130](https://github.com/allen0099/FastAPI-CacheX/issues/130));0.3.x 在匯入它時已經會發出 `DeprecationWarning`。 +`CacheError` 別名會被移除([#130](https://github.com/allen0099/FastAPI-CacheX/issues/130));0.3.8 與 0.3.9 在匯入它時已經會發出 `DeprecationWarning`。 ```python # 修改前 diff --git a/i18n/zh-TW/docs/index.md b/i18n/zh-TW/docs/index.md index e9ba183..6df3860 100644 --- a/i18n/zh-TW/docs/index.md +++ b/i18n/zh-TW/docs/index.md @@ -88,7 +88,7 @@ async def report(cache: AppCache): ## 文件 {#documentation} -- [遷移至 0.4.0](MIGRATING_0_4.md):0.4.0 的變更,以及現在如何處理 0.3.9 的警告 +- [遷移至 0.4.0](MIGRATING_0_4.md):0.4.0 的變更,以及如何從 0.3.x 升級 - [HTTP 快取](HTTP_CACHING.md):`@cache` 裝飾器、Cache-Control 指令、快取鍵、快取失效與監控路由 - [應用層快取](APP_CACHE.md):`CacheManager` - [後端](BACKENDS.md):選擇與設定後端、原子操作的基本功能 From 886a34ca220d783105203c92e8e78a33a0b500c9 Mon Sep 17 00:00:00 2001 From: allen0099 Date: Fri, 2 Oct 2026 14:52:57 +0000 Subject: [PATCH 3/3] docs: apply review findings (method-in-key wording, RedisConfig protocol, zh punctuation) --- docs/BACKENDS.md | 2 +- docs/HTTP_CACHING.md | 2 +- docs/MIGRATING_0_4.md | 2 +- docs/SESSION.md | 1 - i18n/zh-TW/docs/BACKENDS.md | 2 +- i18n/zh-TW/docs/HTTP_CACHING.md | 4 ++-- i18n/zh-TW/docs/MIGRATING_0_4.md | 4 ++-- i18n/zh-TW/docs/SESSION.md | 1 - 8 files changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/BACKENDS.md b/docs/BACKENDS.md index 850b87a..5678cef 100644 --- a/docs/BACKENDS.md +++ b/docs/BACKENDS.md @@ -82,7 +82,7 @@ BackendProxy.set(backend) the prefix (see [Migrating to 0.4.0](MIGRATING_0_4.md#redis-clear-pattern)) **Configuring from a model**: `RedisConfig` is a pydantic model with the same -settings, validated (port range, `db >= 0`), which is handy when they come from environment +settings, validated (port range, `db >= 0`, `protocol` 2 or 3), which is handy when they come from environment variables or a settings file: ```python diff --git a/docs/HTTP_CACHING.md b/docs/HTTP_CACHING.md index ffb44aa..d808bf7 100644 --- a/docs/HTTP_CACHING.md +++ b/docs/HTTP_CACHING.md @@ -295,7 +295,7 @@ another tag, so its keys never collide with these; on Redis and memory, This ensures that: -- The method is part of the key (only `GET` is cached, but a custom key builder sees it) +- The method is part of the key - Different hosts don't share cache (useful for multi-tenant scenarios) - Different query parameters get separate cache entries - The same endpoint with different parameters can be cached independently diff --git a/docs/MIGRATING_0_4.md b/docs/MIGRATING_0_4.md index 4135098..5d885c7 100644 --- a/docs/MIGRATING_0_4.md +++ b/docs/MIGRATING_0_4.md @@ -20,7 +20,7 @@ filterwarnings = [ ] ``` -Every warning below names the setting to change and links to its issue. `FutureWarning` announces a default that changes behaviour and is shown by default; `DeprecationWarning` announces a removal; `UserWarning` flags a configuration that is already wrong today and that 0.4.0 no longer accepts. Once 0.3.9 runs without these warnings, the changes in the "Warned in 0.3.9" column are done; the rest of this page covers what no warning can detect. +Every warning below names the setting to change and links to its issue. `FutureWarning` announces a default that changes behaviour and is shown by default; `DeprecationWarning` announces a removal; `UserWarning` flags a configuration that is already wrong today and that 0.4.0 no longer accepts. Once 0.3.9 runs without these warnings, the changes with a warning in the "Warned in 0.3.9" column are done (the "Dropped" rows need nothing); the rest of this page covers what no warning can detect. ## Summary {#summary} diff --git a/docs/SESSION.md b/docs/SESSION.md index 41e25dc..a6a8fb7 100644 --- a/docs/SESSION.md +++ b/docs/SESSION.md @@ -244,7 +244,6 @@ SessionConfig( # Token sources (API-first architecture) token_format="simple", # "simple" (default) or "jwt" header_name="X-Session-Token", - use_bearer_token=True, # deprecated: leave "bearer" out of token_source_priority instead token_source_priority=[ "header", "bearer", diff --git a/i18n/zh-TW/docs/BACKENDS.md b/i18n/zh-TW/docs/BACKENDS.md index d0ec97d..fd14b4b 100644 --- a/i18n/zh-TW/docs/BACKENDS.md +++ b/i18n/zh-TW/docs/BACKENDS.md @@ -54,7 +54,7 @@ BackendProxy.set(backend) - 只有傳給 `clear_pattern()` 的模式是萬用字元(glob)模式。鍵前綴與傳給 `clear_path()` 的路徑都以字面值比對,因此其中的 `*`、`?`、`[` 或 `]` 不會觸及前綴以外的鍵,也不會漏掉該路徑 - `clear_pattern()` 比對的是邏輯鍵,也就是不含後端前綴的鍵,並一律自行加上前綴,因此模式中不要寫出前綴。以前綴開頭的模式不會被去掉前綴:它只會比對到本身以前綴開頭的邏輯鍵(見[遷移至 0.4.0](MIGRATING_0_4.md#redis-clear-pattern)) -**從模型設定**:`RedisConfig` 是具有相同設定項的 pydantic 模型,並會驗證其範圍(連接埠範圍、`db >= 0`),當設定來自環境變數或設定檔時很方便: +**從模型設定**:`RedisConfig` 是具有相同設定項的 pydantic 模型,並會驗證其範圍(連接埠範圍、`db >= 0`、`protocol` 為 2 或 3),當設定來自環境變數或設定檔時很方便: ```python from fastapi_cachex.backends import AsyncRedisCacheBackend diff --git a/i18n/zh-TW/docs/HTTP_CACHING.md b/i18n/zh-TW/docs/HTTP_CACHING.md index 8a149a2..0c12382 100644 --- a/i18n/zh-TW/docs/HTTP_CACHING.md +++ b/i18n/zh-TW/docs/HTTP_CACHING.md @@ -168,12 +168,12 @@ http:v2|{method}|{host}|{path}|{query_params} 這可確保: -- 方法是快取鍵的一部分(只有 `GET` 會被快取,但自訂的 key builder 看得到它) +- 方法是快取鍵的一部分 - 不同的主機不共用快取(適用於多租戶情境) - 不同的查詢參數各有獨立的快取項目 - 同一個端點搭配不同參數時可以各自快取 -查詢參數會先依名稱排序,再建立快取鍵,因此 `?a=1&b=2` 與 `?b=2&a=1` 共用同一筆項目。排序是穩定的:同名參數的多個值保留用戶端送出的順序,因為以 `tag: list[str]` 讀取的 handler看到的正是這個順序,所以 `?tag=b&tag=a` 與 `?tag=a&tag=b` 仍是兩筆項目。名稱以解碼後的值比較(`%61` 視為 `a` 排序,快取鍵本來就這樣寫它),每個名稱與值的編碼都與未排序的快取鍵完全相同,只有順序改變:已經依序排列的查詢,不論是否排序都得到相同的鍵。快取鍵原本就視為相同的仍然相同(`?a` 與 `?a=`、`&&` 產生的空段),其餘一律不會合併。0.4.0 起預設會排序([#72](https://github.com/allen0099/FastAPI-CacheX/issues/72),見[遷移至 0.4.0](MIGRATING_0_4.md#cache-keys))。 +查詢參數會先依名稱排序,再建立快取鍵,因此 `?a=1&b=2` 與 `?b=2&a=1` 共用同一筆項目。排序是穩定的:同名參數的多個值保留用戶端送出的順序,因為以 `tag: list[str]` 讀取的 handler 看到的正是這個順序,所以 `?tag=b&tag=a` 與 `?tag=a&tag=b` 仍是兩筆項目。名稱以解碼後的值比較(`%61` 視為 `a` 排序,快取鍵本來就這樣寫它),每個名稱與值的編碼都與未排序的快取鍵完全相同,只有順序改變:已經依序排列的查詢,不論是否排序都得到相同的鍵。快取鍵原本就視為相同的仍然相同(`?a` 與 `?a=`、`&&` 產生的空段),其餘一律不會合併。0.4.0 起預設會排序([#72](https://github.com/allen0099/FastAPI-CacheX/issues/72),見[遷移至 0.4.0](MIGRATING_0_4.md#cache-keys))。 回應取決於用戶端送出之查詢順序的 handler(例如從 `request.url` 複製的自身連結或分頁連結)應關閉排序,否則第一位呼叫者的順序會被快取,並提供給送出其他順序的呼叫者: diff --git a/i18n/zh-TW/docs/MIGRATING_0_4.md b/i18n/zh-TW/docs/MIGRATING_0_4.md index b55c64d..59b4ce1 100644 --- a/i18n/zh-TW/docs/MIGRATING_0_4.md +++ b/i18n/zh-TW/docs/MIGRATING_0_4.md @@ -20,7 +20,7 @@ filterwarnings = [ ] ``` -下方每個警告都會指出要修改的設定,並附上對應 issue 的連結。`FutureWarning` 預告會改變行為的預設值,預設就會顯示;`DeprecationWarning` 預告移除;`UserWarning` 標示目前就已經有問題、0.4.0 不再接受的設定。0.3.9 不再發出這些警告後,「0.3.9 是否警告」欄位中有警告的變更就已處理完畢;本頁其餘部分說明警告無法偵測的變更。 +下方每個警告都會指出要修改的設定,並附上對應 issue 的連結。`FutureWarning` 預告會改變行為的預設值,預設就會顯示;`DeprecationWarning` 預告移除;`UserWarning` 標示目前就已經有問題、0.4.0 不再接受的設定。0.3.9 不再發出這些警告後,「0.3.9 是否警告」欄位中有警告的變更就已處理完畢(標為「已取消」的列不需處理);本頁其餘部分說明警告無法偵測的變更。 ## 總覽 {#summary} @@ -40,7 +40,7 @@ filterwarnings = [ | 移除 `BackendProxy.get_backend()`/`set_backend()` | [#70](https://github.com/allen0099/FastAPI-CacheX/issues/70) | `DeprecationWarning` | [BackendProxy](#backend-proxy) | | 移除 `CacheError` | [#130](https://github.com/allen0099/FastAPI-CacheX/issues/130) | `DeprecationWarning` | [CacheError](#cache-error) | | 移除 Redis `clear_pattern()` 去除前綴後的重試 | [#125](https://github.com/allen0099/FastAPI-CacheX/issues/125) | `DeprecationWarning` | [Redis clear_pattern](#redis-clear-pattern) | -| `SessionConfig.use_bearer_token` 維持已棄用,0.5.0 移除 | [#377](https://github.com/allen0099/FastAPI-CacheX/issues/377), [#421](https://github.com/allen0099/FastAPI-CacheX/issues/421) | `DeprecationWarning` | [權杖來源](#token-source-priority) | +| `SessionConfig.use_bearer_token` 維持已棄用,0.5.0 移除 | [#377](https://github.com/allen0099/FastAPI-CacheX/issues/377)、[#421](https://github.com/allen0099/FastAPI-CacheX/issues/421) | `DeprecationWarning` | [權杖來源](#token-source-priority) | | `UserSessionDep` 需要使用者 | [#127](https://github.com/allen0099/FastAPI-CacheX/issues/127) | 否 | [UserSessionDep](#user-session-dep) | | 移除 `memcache` extra | [#202](https://github.com/allen0099/FastAPI-CacheX/issues/202) | 否 | [memcache extra](#memcache-extra) | | `BaseCacheBackend.delete()` 回傳 `bool` | [#71](https://github.com/allen0099/FastAPI-CacheX/issues/71) | 否 | [delete() 的回傳值](#backend-delete) | diff --git a/i18n/zh-TW/docs/SESSION.md b/i18n/zh-TW/docs/SESSION.md index e25b92b..68affeb 100644 --- a/i18n/zh-TW/docs/SESSION.md +++ b/i18n/zh-TW/docs/SESSION.md @@ -144,7 +144,6 @@ SessionConfig( # 權杖來源(API 優先架構) token_format="simple", # "simple"(預設)或 "jwt" header_name="X-Session-Token", - use_bearer_token=True, # 已棄用:改為不在 token_source_priority 中列出 "bearer" token_source_priority=["header", "bearer"], # "cookie" 只能放在最後(見下文) # JWT(token_format == "jwt" 時使用) jwt_algorithm="HS256", # 拒絕 "none"