diff --git a/README.md b/README.md index 458b37b..1f4d429 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,22 @@ async def report(cache: AppCache): return await cache.get_or_set("report", build_report, ttl=300) ``` +> [!IMPORTANT] +> Put `@cache` **below** the route decorator. FastAPI registers whatever function +> reaches `@app.get(...)`; with `@cache` on top, FastAPI registers the +> undecorated handler, so the route works but nothing is cached and nothing warns +> (see [Decorator order](https://fastapi-cachex.readthedocs.io/en/latest/HTTP_CACHING/#decorator-order)). +> +> ```python +> @app.get("/items") # ✅ route decorator first, +> @cache(ttl=60) # @cache directly above the function +> async def items(): ... +> +> @cache(ttl=60) # ❌ never called: nothing is cached +> @app.get("/items") +> async def items(): ... +> ``` + > [!WARNING] > The default cache key carries no user identity. Cache authenticated endpoints > with `private=True` or a per-user key builder plus `cache_authorized=True` diff --git a/docs/HTTP_CACHING.md b/docs/HTTP_CACHING.md index 1c4e340..793d53f 100644 --- a/docs/HTTP_CACHING.md +++ b/docs/HTTP_CACHING.md @@ -38,6 +38,31 @@ handler does not need to declare a `Request` parameter — the decorator adds on when it is missing. If no backend has been configured, `@cache` falls back to a `MemoryBackend` (see [Backends](BACKENDS.md)). +### Decorator order + +`@cache` goes **below** the route decorator (`@app.get(...)`, +`@router.get(...)`), directly above the function. Python applies decorators +bottom-up, and FastAPI registers whatever function reaches the route decorator. +With `@cache` on top, FastAPI registers the undecorated handler and never calls +the cache wrapper: the route still works, but nothing is cached, no +`Cache-Control` or `ETag` header is sent, and nothing warns. + +```python +# ✅ Cached: FastAPI registers the @cache wrapper. +@app.get("/items") +@cache(ttl=60) +async def items(): ... + + +# ❌ Not cached: FastAPI registers the plain function; the wrapper is never called. +@cache(ttl=60) +@app.get("/items") +async def items(): ... +``` + +The same applies to `app.add_api_route(path, cache(ttl=60)(handler))`: pass the +decorated function, not the plain one. + ## Cache-Control directives `@cache` plays two roles. It writes a `Cache-Control` header for browsers and diff --git a/i18n/zh-TW/docs/HTTP_CACHING.md b/i18n/zh-TW/docs/HTTP_CACHING.md index 53ebbe3..23ec812 100644 --- a/i18n/zh-TW/docs/HTTP_CACHING.md +++ b/i18n/zh-TW/docs/HTTP_CACHING.md @@ -33,6 +33,25 @@ async def non_store_endpoint(): 只有 GET 請求會被快取;其他方法照常執行 handler。handler 不需要宣告 `Request` 參數:缺少時裝飾器會自動加上。如果尚未設定任何後端,`@cache` 會改用 `MemoryBackend`(見[後端](BACKENDS.md))。 +### 裝飾器順序 {#decorator-order} + +`@cache` 必須寫在路由裝飾器(`@app.get(...)`、`@router.get(...)`)的**下方**,緊貼在函式上方。Python 由下而上套用裝飾器,而 FastAPI 註冊的是傳到路由裝飾器的那個函式。若 `@cache` 寫在上方,FastAPI 註冊的是未經裝飾的 handler,永遠不會呼叫快取包裝函式:路由照常運作,但什麼都不會被快取,不會送出 `Cache-Control` 或 `ETag` 標頭,也不會有任何警告。 + +```python +# ✅ 會快取:FastAPI 註冊的是 @cache 的包裝函式。 +@app.get("/items") +@cache(ttl=60) +async def items(): ... + + +# ❌ 不會快取:FastAPI 註冊的是原本的函式,包裝函式永遠不會被呼叫。 +@cache(ttl=60) +@app.get("/items") +async def items(): ... +``` + +`app.add_api_route(path, cache(ttl=60)(handler))` 也是一樣:傳入裝飾後的函式,而不是原本的函式。 + ## Cache-Control 指令 {#cache-control-directives} `@cache` 有兩個角色:替瀏覽器與中間層(CDN、反向代理)寫出 `Cache-Control` 標頭,以及在後端維護自己的伺服器端快取。大部分指令只影響前者:它們會寫進標頭,但不論有沒有設定,伺服器端快取的行為都一樣。 diff --git a/i18n/zh-TW/docs/index.md b/i18n/zh-TW/docs/index.md index 1b75067..d9eba6b 100644 --- a/i18n/zh-TW/docs/index.md +++ b/i18n/zh-TW/docs/index.md @@ -70,6 +70,19 @@ async def report(cache: AppCache): return await cache.get_or_set("report", build_report, ttl=300) ``` +> [!IMPORTANT] +> `@cache` 必須寫在路由裝飾器的**下方**。FastAPI 註冊的是傳到 `@app.get(...)` 的那個函式;若 `@cache` 寫在上方,FastAPI 註冊的是未經裝飾的 handler,路由照常運作,但什麼都不會被快取,也不會有任何警告(詳見 [裝飾器順序](HTTP_CACHING.md#decorator-order))。 +> +> ```python +> @app.get("/items") # ✅ 先寫路由裝飾器, +> @cache(ttl=60) # @cache 緊貼在函式上方 +> async def items(): ... +> +> @cache(ttl=60) # ❌ 永遠不會被呼叫:什麼都不會被快取 +> @app.get("/items") +> async def items(): ... +> ``` + > [!WARNING] > 預設的快取鍵不包含使用者身分。需要驗證身分的端點請使用 `private=True`,或依使用者區分的 key builder 搭配 `cache_authorized=True`(否則帶有 `Authorization` 或 Session 的請求會繞過後端),詳見 [需驗證身分的端點](HTTP_CACHING.md#authenticated-endpoints)。