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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
25 changes: 25 additions & 0 deletions docs/HTTP_CACHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions i18n/zh-TW/docs/HTTP_CACHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` 標頭,以及在後端維護自己的伺服器端快取。大部分指令只影響前者:它們會寫進標頭,但不論有沒有設定,伺服器端快取的行為都一樣。
Expand Down
13 changes: 13 additions & 0 deletions i18n/zh-TW/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)。

Expand Down
Loading