fastapi-caching-route 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastapi_caching_route-0.6.0/.github/workflows/coverage.yaml +34 -0
- fastapi_caching_route-0.6.0/.github/workflows/deploy-docs.yaml +44 -0
- fastapi_caching_route-0.6.0/.github/workflows/deploy-pypi.yaml +42 -0
- fastapi_caching_route-0.6.0/.github/workflows/pr.yaml +85 -0
- fastapi_caching_route-0.6.0/.gitignore +6 -0
- fastapi_caching_route-0.6.0/.vscode/settings.json +3 -0
- fastapi_caching_route-0.6.0/.zed/settings.json +10 -0
- fastapi_caching_route-0.6.0/LICENSE +21 -0
- fastapi_caching_route-0.6.0/PKG-INFO +250 -0
- fastapi_caching_route-0.6.0/README.md +230 -0
- fastapi_caching_route-0.6.0/docs/api_reference/caching_route.md +1 -0
- fastapi_caching_route-0.6.0/docs/api_reference/fastapi_cache.md +8 -0
- fastapi_caching_route-0.6.0/docs/api_reference/typing.md +5 -0
- fastapi_caching_route-0.6.0/docs/index.md +1 -0
- fastapi_caching_route-0.6.0/examples/README.md +155 -0
- fastapi_caching_route-0.6.0/examples/complex.py +77 -0
- fastapi_caching_route-0.6.0/examples/curl-format.txt +7 -0
- fastapi_caching_route-0.6.0/examples/invalidate.py +68 -0
- fastapi_caching_route-0.6.0/examples/ruff.toml +4 -0
- fastapi_caching_route-0.6.0/examples/simple.py +18 -0
- fastapi_caching_route-0.6.0/fastapi_caching_route/__init__.py +6 -0
- fastapi_caching_route-0.6.0/fastapi_caching_route/_version.py +1 -0
- fastapi_caching_route-0.6.0/fastapi_caching_route/_version.pyi +1 -0
- fastapi_caching_route-0.6.0/fastapi_caching_route/main.py +590 -0
- fastapi_caching_route-0.6.0/fastapi_caching_route/py.typed +0 -0
- fastapi_caching_route-0.6.0/justfile +19 -0
- fastapi_caching_route-0.6.0/mkdocs.yml +32 -0
- fastapi_caching_route-0.6.0/pyproject.toml +111 -0
- fastapi_caching_route-0.6.0/tests/ruff.toml +4 -0
- fastapi_caching_route-0.6.0/tests/test_main.py +391 -0
- fastapi_caching_route-0.6.0/uv.lock +1558 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Coverage
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
coverage:
|
|
10
|
+
name: Coverage
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout
|
|
14
|
+
uses: actions/checkout@v7
|
|
15
|
+
|
|
16
|
+
- name: Set up Python
|
|
17
|
+
uses: actions/setup-python@v6
|
|
18
|
+
with:
|
|
19
|
+
python-version-file: pyproject.toml
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
|
|
23
|
+
|
|
24
|
+
- name: Install the project
|
|
25
|
+
run: uv sync --frozen
|
|
26
|
+
|
|
27
|
+
- name: Coverage
|
|
28
|
+
run: uv run --no-sync pytest --cov-report=xml --cov-report=term
|
|
29
|
+
|
|
30
|
+
- name: Upload coverage to Coveralls
|
|
31
|
+
uses: coverallsapp/github-action@v2
|
|
32
|
+
with:
|
|
33
|
+
format: cobertura
|
|
34
|
+
file: coverage.xml
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Deploy documentation to Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types:
|
|
6
|
+
- published
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
pages: write
|
|
12
|
+
id-token: write
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: 'pages'
|
|
16
|
+
cancel-in-progress: true
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
deploy:
|
|
20
|
+
environment:
|
|
21
|
+
name: github-pages
|
|
22
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- name: Checkout
|
|
26
|
+
uses: actions/checkout@v7
|
|
27
|
+
|
|
28
|
+
- name: Install uv
|
|
29
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
|
|
30
|
+
|
|
31
|
+
- name: Build site
|
|
32
|
+
run: uv run --frozen --group docs mkdocs build
|
|
33
|
+
|
|
34
|
+
- name: Setup Pages
|
|
35
|
+
uses: actions/configure-pages@v6
|
|
36
|
+
|
|
37
|
+
- name: Upload artifact
|
|
38
|
+
uses: actions/upload-pages-artifact@v5
|
|
39
|
+
with:
|
|
40
|
+
path: ./site
|
|
41
|
+
|
|
42
|
+
- name: Deploy to GitHub Pages
|
|
43
|
+
id: deployment
|
|
44
|
+
uses: actions/deploy-pages@v5
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Deploy to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types:
|
|
6
|
+
- published
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
name: Build python packages
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout
|
|
14
|
+
uses: actions/checkout@v7
|
|
15
|
+
with:
|
|
16
|
+
fetch-depth: 0
|
|
17
|
+
|
|
18
|
+
- name: Build
|
|
19
|
+
run: pipx run build
|
|
20
|
+
|
|
21
|
+
- uses: actions/upload-artifact@v7
|
|
22
|
+
with:
|
|
23
|
+
path: dist/*
|
|
24
|
+
|
|
25
|
+
publish:
|
|
26
|
+
name: Publish to PyPI
|
|
27
|
+
needs: [build]
|
|
28
|
+
environment:
|
|
29
|
+
name: pypi
|
|
30
|
+
url: https://pypi.org/p/fastapi-caching-route
|
|
31
|
+
permissions:
|
|
32
|
+
id-token: write
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
if: github.event_name == 'release' && github.event.action == 'published'
|
|
35
|
+
steps:
|
|
36
|
+
- name: Download artifacts
|
|
37
|
+
uses: actions/download-artifact@v8
|
|
38
|
+
with:
|
|
39
|
+
path: dist
|
|
40
|
+
|
|
41
|
+
- name: Upload to PyPI
|
|
42
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
name: PR Checks
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
pytest:
|
|
10
|
+
name: Tests Python ${{ matrix.python-version }}
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14', '3.14t']
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout
|
|
18
|
+
uses: actions/checkout@v7
|
|
19
|
+
|
|
20
|
+
- uses: actions/setup-python@v6
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
|
|
26
|
+
|
|
27
|
+
- name: Install the project
|
|
28
|
+
run: uv sync --frozen
|
|
29
|
+
|
|
30
|
+
- name: Run pytest
|
|
31
|
+
run: uv run --no-sync pytest --no-cov
|
|
32
|
+
|
|
33
|
+
code-quality:
|
|
34
|
+
name: Code Quality
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- name: Checkout
|
|
38
|
+
uses: actions/checkout@v7
|
|
39
|
+
|
|
40
|
+
- name: Set up Python
|
|
41
|
+
uses: actions/setup-python@v6
|
|
42
|
+
with:
|
|
43
|
+
python-version-file: pyproject.toml
|
|
44
|
+
|
|
45
|
+
- name: Install uv
|
|
46
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
|
|
47
|
+
|
|
48
|
+
- name: Check uv.lock
|
|
49
|
+
run: uv lock --check
|
|
50
|
+
|
|
51
|
+
- name: Install the project
|
|
52
|
+
run: uv sync --frozen
|
|
53
|
+
|
|
54
|
+
- name: Ruff check
|
|
55
|
+
id: ruff-check
|
|
56
|
+
continue-on-error: true
|
|
57
|
+
run: uv run --no-sync ruff check --output-format=github
|
|
58
|
+
|
|
59
|
+
- name: Ruff format
|
|
60
|
+
id: ruff-format
|
|
61
|
+
continue-on-error: true
|
|
62
|
+
run: uv run --no-sync ruff format --check --output-format=github
|
|
63
|
+
|
|
64
|
+
- name: Type check
|
|
65
|
+
id: ty
|
|
66
|
+
continue-on-error: true
|
|
67
|
+
run: uv run --no-sync ty check --output-format=github
|
|
68
|
+
|
|
69
|
+
- name: Fail if any step failed
|
|
70
|
+
if: |
|
|
71
|
+
steps.ruff-check.outcome == 'failure' ||
|
|
72
|
+
steps.ruff-format.outcome == 'failure' ||
|
|
73
|
+
steps.ty.outcome == 'failure'
|
|
74
|
+
run: exit 1
|
|
75
|
+
|
|
76
|
+
summary:
|
|
77
|
+
name: PR Checks Summary
|
|
78
|
+
runs-on: ubuntu-latest
|
|
79
|
+
if: always()
|
|
80
|
+
needs: [pytest, code-quality]
|
|
81
|
+
steps:
|
|
82
|
+
- run: >
|
|
83
|
+
test
|
|
84
|
+
"${{ needs.pytest.result }}" = "success" -a
|
|
85
|
+
"${{ needs.code-quality.result }}" = "success"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Petr Tsymbarovich
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-caching-route
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: FastAPI route for efficient caching.
|
|
5
|
+
Project-URL: Homepage, https://github.com/AgroDT/fastapi-caching-route
|
|
6
|
+
Project-URL: Repository, https://github.com/AgroDT/fastapi-caching-route.git
|
|
7
|
+
Project-URL: Issues, https://github.com/AgroDT/fastapi-caching-route/issues
|
|
8
|
+
Project-URL: Documentation, https://agrodt.github.io/fastapi-caching-route/
|
|
9
|
+
Project-URL: Coverage, https://coveralls.io/github/AgroDT/fastapi-caching-route
|
|
10
|
+
Author-email: Petr Tsymbarovich <petr@tsymbarovich.ru>
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Classifier: Framework :: FastAPI
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: aiocache<1.0,>=0.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# FastAPI Caching Route
|
|
22
|
+
|
|
23
|
+
[](https://github.com/AgroDT/fastapi-caching-route/actions/workflows/pr.yaml)
|
|
24
|
+
[](https://github.com/AgroDT/fastapi-caching-route/actions/workflows/coverage.yaml)
|
|
25
|
+
[](https://coveralls.io/github/AgroDT/fastapi-caching-route)
|
|
26
|
+
[](https://github.com/AgroDT/fastapi-caching-route/actions/workflows/deploy-docs.yaml)
|
|
27
|
+
[](https://pypi.org/project/fastapi-caching-route/)
|
|
28
|
+
|
|
29
|
+
FastAPI route class for response caching before entering the endpoint handler.
|
|
30
|
+
|
|
31
|
+
`fastapi-caching-route` plugs into `fastapi.APIRouter`, stores complete response
|
|
32
|
+
payloads in an `aiocache` backend, and serves cache hits directly from the route
|
|
33
|
+
handler. It is useful for expensive read endpoints where the cache key can be
|
|
34
|
+
derived from the request path, query parameters, or a custom key builder.
|
|
35
|
+
|
|
36
|
+
**⚠️ This project is a proof of concept and is not yet recommended for production use!**
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- Cache regular FastAPI responses and `StreamingResponse` bodies.
|
|
41
|
+
- Return `X-Cache: MISS` for stored responses and `X-Cache: HIT` for cache hits.
|
|
42
|
+
- Generate `ETag` headers for cached responses.
|
|
43
|
+
- Return `304 Not Modified` for matching `If-None-Match` requests.
|
|
44
|
+
- Build default cache keys from the route path and declared query parameters.
|
|
45
|
+
- Provide custom key builders for path parameters or application-specific keys.
|
|
46
|
+
- Include selected request headers in default cache keys for negotiated responses.
|
|
47
|
+
- Run explicitly configured dependencies before cache lookup, for example API key
|
|
48
|
+
checks.
|
|
49
|
+
- Pass `namespace` and `ttl` through to the underlying `aiocache` backend.
|
|
50
|
+
- Manually invalidate cached values through `FastAPICache.invalidate_cached()`.
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
uv add fastapi-caching-route
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
pip install fastapi-caching-route
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Install FastAPI and a cache backend that matches your application. For local
|
|
63
|
+
development or tests, `aiocache.SimpleMemoryCache` is enough:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
uv add fastapi aiocache
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
pip install fastapi aiocache
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Basic Usage
|
|
74
|
+
|
|
75
|
+
Use `CachingRoute` as the router route class and decorate endpoints with
|
|
76
|
+
`FastAPICache`.
|
|
77
|
+
|
|
78
|
+
```py
|
|
79
|
+
from aiocache import SimpleMemoryCache
|
|
80
|
+
from fastapi import APIRouter, FastAPI
|
|
81
|
+
from fastapi_caching_route import CachingRoute, FastAPICache
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
router = APIRouter(route_class=CachingRoute)
|
|
85
|
+
cache = FastAPICache(SimpleMemoryCache())
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@cache()
|
|
89
|
+
@router.get('/')
|
|
90
|
+
def cached() -> str:
|
|
91
|
+
return 'Hello, World!'
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
app = FastAPI()
|
|
95
|
+
app.include_router(router)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Start an app with this route and call it twice. The first response is produced by
|
|
99
|
+
the endpoint and stored in the cache:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
X-Cache: MISS
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The second response is returned from the cache before the endpoint handler is
|
|
106
|
+
called:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
X-Cache: HIT
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Routes decorated with `@cache()` must be registered on a router that uses
|
|
113
|
+
`CachingRoute`. A plain `APIRoute` ignores the cache configuration.
|
|
114
|
+
|
|
115
|
+
## Cache Keys
|
|
116
|
+
|
|
117
|
+
By default, the cache key is built from the request path and declared query
|
|
118
|
+
parameters. Query parameter order is normalized, so these two requests hit the
|
|
119
|
+
same cache entry when the endpoint declares `a` and `b`:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
/query?a=a&b=b
|
|
123
|
+
/query?b=b&a=a
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Use a custom key builder when the cache key should be based on path parameters,
|
|
127
|
+
headers, user context, or another application-specific value.
|
|
128
|
+
|
|
129
|
+
```py
|
|
130
|
+
from fastapi import Request
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def user_key_builder(request: Request) -> str:
|
|
134
|
+
user_id = request.scope['path_params']['user_id']
|
|
135
|
+
return f'user:{user_id}'
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@cache(key_builder=user_key_builder, ttl=60, namespace='users')
|
|
139
|
+
@router.get('/users/{user_id}')
|
|
140
|
+
def get_user(user_id: int) -> dict[str, int]:
|
|
141
|
+
return {'id': user_id}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`ttl` and `namespace` are passed to `aiocache`. When the underlying cache instance
|
|
145
|
+
also has a namespace, `FastAPICache` concatenates the root and endpoint namespace
|
|
146
|
+
by default:
|
|
147
|
+
|
|
148
|
+
```py
|
|
149
|
+
cache = FastAPICache(RedisCache(namespace='api'))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@cache(namespace='users')
|
|
153
|
+
@router.get('/users/{user_id}')
|
|
154
|
+
def get_user(user_id: int): ...
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# Resulting namespace: "api:users"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Pass `namespace_policy="replace"` to `FastAPICache` if endpoint namespaces should
|
|
161
|
+
replace the root namespace instead.
|
|
162
|
+
|
|
163
|
+
If the response representation depends on request headers, include those headers
|
|
164
|
+
in the default cache key with `vary_headers`. The route also returns a matching
|
|
165
|
+
`Vary` header:
|
|
166
|
+
|
|
167
|
+
```py
|
|
168
|
+
@cache(vary_headers=['Accept-Language'])
|
|
169
|
+
@router.get('/localized')
|
|
170
|
+
def localized(request: Request) -> str:
|
|
171
|
+
return request.headers.get('accept-language', 'en')
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Responses with `Vary: *` are not cached.
|
|
175
|
+
|
|
176
|
+
## Dependencies Before Cache Lookup
|
|
177
|
+
|
|
178
|
+
FastAPI dependencies on the route still run on cache misses. If a dependency
|
|
179
|
+
must also be resolved before cache lookup, pass it to `@cache(dependencies=...)`.
|
|
180
|
+
This is mainly useful for security dependencies that should reject unauthorized
|
|
181
|
+
requests before a cached response can be served.
|
|
182
|
+
|
|
183
|
+
```py
|
|
184
|
+
from fastapi import Depends
|
|
185
|
+
from fastapi.security import APIKeyHeader
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
api_key = Depends(APIKeyHeader(name='X-Key'))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@cache(dependencies=[api_key])
|
|
192
|
+
@router.get('/private', dependencies=[api_key])
|
|
193
|
+
def private_data() -> str:
|
|
194
|
+
return 'secret'
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Keep the dependency on the route as well if it must be enforced for cache misses.
|
|
198
|
+
|
|
199
|
+
## Conditional Requests
|
|
200
|
+
|
|
201
|
+
Cached responses without an existing `ETag` get one based on the response body.
|
|
202
|
+
If the endpoint already sets `ETag`, that value is preserved. On a cache hit,
|
|
203
|
+
requests with a matching `If-None-Match` header return `304 Not Modified` with an
|
|
204
|
+
empty body. `If-None-Match` supports weak tags, tag lists, and `*`.
|
|
205
|
+
|
|
206
|
+
```sh
|
|
207
|
+
curl http://127.0.0.1:8000/cached -H 'If-None-Match: "..."'
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Invalidation
|
|
211
|
+
|
|
212
|
+
Use the same key builder logic when invalidating cache entries after writes.
|
|
213
|
+
|
|
214
|
+
```py
|
|
215
|
+
from fastapi import Request
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def user_cache_key(user_id: int) -> str:
|
|
219
|
+
return f'user:{user_id}'
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def user_key_builder(request: Request) -> str:
|
|
223
|
+
return user_cache_key(request.scope['path_params']['user_id'])
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@cache(key_builder=user_key_builder)
|
|
227
|
+
@router.get('/users/{user_id}')
|
|
228
|
+
def get_user(user_id: int): ...
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@router.patch('/users/{user_id}')
|
|
232
|
+
async def update_user(user_id: int) -> dict[str, int]:
|
|
233
|
+
await cache.invalidate_cached(user_cache_key(user_id))
|
|
234
|
+
return {'id': user_id}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`invalidate_cached()` returns the number of deleted keys reported by the
|
|
238
|
+
underlying cache backend.
|
|
239
|
+
|
|
240
|
+
## Examples
|
|
241
|
+
|
|
242
|
+
The repository contains runnable examples:
|
|
243
|
+
|
|
244
|
+
- `examples/simple.py`: minimal cached route.
|
|
245
|
+
- `examples/complex.py`: cache hits and misses, auth dependency, ETag handling,
|
|
246
|
+
streaming response caching, query parameter keys, and non-cacheable responses.
|
|
247
|
+
- `examples/invalidate.py`: custom key builder and manual invalidation after an
|
|
248
|
+
update.
|
|
249
|
+
|
|
250
|
+
Follow the detailed walkthrough in the examples README.
|