fastapi-cachex 0.2.4__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_cachex-0.2.4/PKG-INFO +269 -0
- fastapi_cachex-0.2.4/README.md +233 -0
- fastapi_cachex-0.2.4/fastapi_cachex/__init__.py +27 -0
- fastapi_cachex-0.2.4/fastapi_cachex/backends/__init__.py +15 -0
- fastapi_cachex-0.2.4/fastapi_cachex/backends/base.py +70 -0
- fastapi_cachex-0.2.4/fastapi_cachex/backends/config.py +15 -0
- fastapi_cachex-0.2.4/fastapi_cachex/backends/memcached.py +258 -0
- fastapi_cachex-0.2.4/fastapi_cachex/backends/memory.py +226 -0
- fastapi_cachex-0.2.4/fastapi_cachex/backends/redis.py +347 -0
- fastapi_cachex-0.2.4/fastapi_cachex/cache.py +341 -0
- fastapi_cachex-0.2.4/fastapi_cachex/dependencies.py +16 -0
- fastapi_cachex-0.2.4/fastapi_cachex/directives.py +21 -0
- fastapi_cachex-0.2.4/fastapi_cachex/exceptions.py +17 -0
- fastapi_cachex-0.2.4/fastapi_cachex/proxy.py +43 -0
- fastapi_cachex-0.2.4/fastapi_cachex/py.typed +0 -0
- fastapi_cachex-0.2.4/fastapi_cachex/routes.py +312 -0
- fastapi_cachex-0.2.4/fastapi_cachex/session/__init__.py +23 -0
- fastapi_cachex-0.2.4/fastapi_cachex/session/config.py +93 -0
- fastapi_cachex-0.2.4/fastapi_cachex/session/dependencies.py +99 -0
- fastapi_cachex-0.2.4/fastapi_cachex/session/exceptions.py +25 -0
- fastapi_cachex-0.2.4/fastapi_cachex/session/manager.py +445 -0
- fastapi_cachex-0.2.4/fastapi_cachex/session/middleware.py +158 -0
- fastapi_cachex-0.2.4/fastapi_cachex/session/models.py +185 -0
- fastapi_cachex-0.2.4/fastapi_cachex/session/security.py +111 -0
- fastapi_cachex-0.2.4/fastapi_cachex/session/token_serializers.py +166 -0
- fastapi_cachex-0.2.4/fastapi_cachex/state/__init__.py +8 -0
- fastapi_cachex-0.2.4/fastapi_cachex/state/exceptions.py +19 -0
- fastapi_cachex-0.2.4/fastapi_cachex/state/manager.py +258 -0
- fastapi_cachex-0.2.4/fastapi_cachex/state/models.py +31 -0
- fastapi_cachex-0.2.4/fastapi_cachex/types.py +34 -0
- fastapi_cachex-0.2.4/pyproject.toml +173 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-cachex
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: A caching library for FastAPI with support for Cache-Control, ETag, and multiple backends.
|
|
5
|
+
Keywords: fastapi,cache,etag,cache-control,redis,memcached,in-memory
|
|
6
|
+
Author: allen0099
|
|
7
|
+
Author-email: allen0099 <s96016641@gmail.com>
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Framework :: FastAPI
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
22
|
+
Requires-Dist: fastapi
|
|
23
|
+
Requires-Dist: pydantic
|
|
24
|
+
Requires-Dist: pyjwt>=2.9.0 ; extra == 'jwt'
|
|
25
|
+
Requires-Dist: pymemcache ; extra == 'memcache'
|
|
26
|
+
Requires-Dist: redis[hiredis] ; extra == 'redis'
|
|
27
|
+
Requires-Dist: orjson ; extra == 'redis'
|
|
28
|
+
Requires-Python: >=3.10
|
|
29
|
+
Project-URL: Homepage, https://github.com/allen0099/FastAPI-CacheX
|
|
30
|
+
Project-URL: Issues, https://github.com/allen0099/FastAPI-CacheX/issues
|
|
31
|
+
Project-URL: Repository, https://github.com/allen0099/FastAPI-CacheX.git
|
|
32
|
+
Provides-Extra: jwt
|
|
33
|
+
Provides-Extra: memcache
|
|
34
|
+
Provides-Extra: redis
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# FastAPI-Cache X
|
|
38
|
+
|
|
39
|
+
[](https://github.com/astral-sh/uv)
|
|
40
|
+
[](https://github.com/astral-sh/ruff)
|
|
41
|
+
[](https://github.com/allen0099/FastAPI-CacheX/actions/workflows/test.yml)
|
|
42
|
+
[](https://github.com/allen0099/FastAPI-CacheX/actions/workflows/coverage.yml)
|
|
43
|
+
|
|
44
|
+
[](https://pepy.tech/project/fastapi-cachex)
|
|
45
|
+
[](https://pepy.tech/project/fastapi-cachex)
|
|
46
|
+
[](https://pepy.tech/project/fastapi-cachex)
|
|
47
|
+
|
|
48
|
+
[](https://pypi.org/project/fastapi-cachex)
|
|
49
|
+
[](https://pypi.org/project/fastapi-cachex/)
|
|
50
|
+
|
|
51
|
+
[English](README.md) | [繁體中文](docs/README.zh-TW.md)
|
|
52
|
+
|
|
53
|
+
A high-performance caching extension for FastAPI, providing comprehensive HTTP caching support and optional session management.
|
|
54
|
+
|
|
55
|
+
## Features
|
|
56
|
+
|
|
57
|
+
### HTTP Caching
|
|
58
|
+
- Support for HTTP caching headers
|
|
59
|
+
- `Cache-Control`
|
|
60
|
+
- `ETag`
|
|
61
|
+
- `If-None-Match`
|
|
62
|
+
- Multiple backend cache support
|
|
63
|
+
- Redis
|
|
64
|
+
- Memcached
|
|
65
|
+
- In-memory cache
|
|
66
|
+
- Complete Cache-Control directive implementation
|
|
67
|
+
- Easy-to-use `@cache` decorator
|
|
68
|
+
|
|
69
|
+
### Session Management (Optional Extension)
|
|
70
|
+
- Secure session management with HMAC-SHA256 token signing
|
|
71
|
+
- Optional JWT token format for interoperability (install extra `jwt`)
|
|
72
|
+
- IP address and User-Agent binding (optional security features)
|
|
73
|
+
- Header and bearer token support (API-first architecture)
|
|
74
|
+
- Automatic session renewal (sliding expiration)
|
|
75
|
+
- Flash messages for cross-request communication
|
|
76
|
+
- Multiple backend support (Redis, Memcached, In-Memory)
|
|
77
|
+
- Complete session lifecycle management (create, validate, refresh, invalidate)
|
|
78
|
+
|
|
79
|
+
### Cache-Control Directives
|
|
80
|
+
|
|
81
|
+
| Directive | Supported | Description |
|
|
82
|
+
|--------------------------|--------------------|---------------------------------------------------------------------------------------------------------|
|
|
83
|
+
| `max-age` | :white_check_mark: | Specifies the maximum amount of time a resource is considered fresh. |
|
|
84
|
+
| `s-maxage` | :x: | Specifies the maximum amount of time a resource is considered fresh for shared caches. |
|
|
85
|
+
| `no-cache` | :white_check_mark: | Forces caches to submit the request to the origin server for validation before releasing a cached copy. |
|
|
86
|
+
| `no-store` | :white_check_mark: | Instructs caches not to store any part of the request or response. |
|
|
87
|
+
| `no-transform` | :x: | Instructs caches not to transform the response content. |
|
|
88
|
+
| `must-revalidate` | :white_check_mark: | Forces caches to revalidate the response with the origin server after it becomes stale. |
|
|
89
|
+
| `proxy-revalidate` | :x: | Similar to `must-revalidate`, but only for shared caches. |
|
|
90
|
+
| `must-understand` | :x: | Indicates that the recipient must understand the directive or treat it as an error. |
|
|
91
|
+
| `private` | :white_check_mark: | Indicates that the response is intended for a single user and should not be stored by shared caches. |
|
|
92
|
+
| `public` | :white_check_mark: | Indicates that the response may be cached by any cache, even if it is normally non-cacheable. |
|
|
93
|
+
| `immutable` | :white_check_mark: | Indicates that the response body will not change over time, allowing for longer caching. |
|
|
94
|
+
| `stale-while-revalidate` | :white_check_mark: | Indicates that a cache can serve a stale response while it revalidates the response in the background. |
|
|
95
|
+
| `stale-if-error` | :white_check_mark: | Indicates that a cache can serve a stale response if the origin server is unavailable. |
|
|
96
|
+
|
|
97
|
+
## Installation
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
uv add fastapi-cachex
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
To enable JWT token format support for sessions:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
uv add "fastapi-cachex[jwt]"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Development Installation
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
uv add git+https://github.com/allen0099/FastAPI-CacheX.git
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Quick Start
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from fastapi import FastAPI
|
|
119
|
+
from fastapi_cachex import cache
|
|
120
|
+
from fastapi_cachex import CacheBackend
|
|
121
|
+
|
|
122
|
+
app = FastAPI()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@app.get("/")
|
|
126
|
+
@cache(ttl=60) # Cache for 60 seconds
|
|
127
|
+
async def read_root():
|
|
128
|
+
return {"Hello": "World"}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@app.get("/no-cache")
|
|
132
|
+
@cache(no_cache=True) # Mark this endpoint as non-cacheable
|
|
133
|
+
async def non_cache_endpoint():
|
|
134
|
+
return {"Hello": "World"}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@app.get("/no-store")
|
|
138
|
+
@cache(no_store=True) # Mark this endpoint as non-cacheable
|
|
139
|
+
async def non_store_endpoint():
|
|
140
|
+
return {"Hello": "World"}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@app.get("/clear_cache")
|
|
144
|
+
async def remove_cache(cache: CacheBackend):
|
|
145
|
+
await cache.clear_path("/path/to/clear") # Clear cache for a specific path
|
|
146
|
+
await cache.clear_pattern("/path/to/clear/*") # Clear cache for a specific pattern
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Backend Configuration
|
|
150
|
+
|
|
151
|
+
FastAPI-CacheX supports multiple caching backends. You can easily switch between them using the `BackendProxy`.
|
|
152
|
+
|
|
153
|
+
### Cache Key Format
|
|
154
|
+
|
|
155
|
+
Cache keys are generated in the following format to avoid collisions:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
{method}|||{host}|||{path}|||{query_params}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
This ensures that:
|
|
162
|
+
- Different HTTP methods (GET, POST, etc.) don't share cache
|
|
163
|
+
- Different hosts don't share cache (useful for multi-tenant scenarios)
|
|
164
|
+
- Different query parameters get separate cache entries
|
|
165
|
+
- The same endpoint with different parameters can be cached independently
|
|
166
|
+
|
|
167
|
+
All backends automatically namespace keys with a prefix (e.g., `fastapi_cachex:`) to avoid conflicts with other applications.
|
|
168
|
+
|
|
169
|
+
### Cache Hit Behavior
|
|
170
|
+
|
|
171
|
+
When a cached entry is valid (within TTL):
|
|
172
|
+
- **Default behavior**: Returns the cached content with HTTP 200 status code directly without re-executing the endpoint handler
|
|
173
|
+
- **With `If-None-Match` header**: Returns HTTP 304 Not Modified if the ETag matches
|
|
174
|
+
- **With `no-cache` directive**: Forces revalidation with fresh content before deciding on 304
|
|
175
|
+
|
|
176
|
+
This means **cached hits are extremely fast** - the endpoint handler function is never executed.
|
|
177
|
+
|
|
178
|
+
### In-Memory Cache (default)
|
|
179
|
+
|
|
180
|
+
If you don't specify a backend, FastAPI-CacheX will use the in-memory cache by default.
|
|
181
|
+
This is suitable for development and testing purposes. The backend automatically runs
|
|
182
|
+
a cleanup task to remove expired entries every 60 seconds.
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from fastapi_cachex.backends import MemoryBackend
|
|
186
|
+
from fastapi_cachex import BackendProxy
|
|
187
|
+
|
|
188
|
+
backend = MemoryBackend()
|
|
189
|
+
BackendProxy.set_backend(backend)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Note**: In-memory cache is not suitable for production with multiple processes.
|
|
193
|
+
Each process maintains its own separate cache.
|
|
194
|
+
|
|
195
|
+
### Memcached
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
from fastapi_cachex.backends import MemcachedBackend
|
|
199
|
+
from fastapi_cachex import BackendProxy
|
|
200
|
+
|
|
201
|
+
backend = MemcachedBackend(servers=["localhost:11211"])
|
|
202
|
+
BackendProxy.set_backend(backend)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Limitations**:
|
|
206
|
+
- Pattern-based key clearing (`clear_pattern`) is not supported by the Memcached protocol
|
|
207
|
+
- Keys are namespaced with `fastapi_cachex:` prefix to avoid conflicts
|
|
208
|
+
- Consider using Redis backend if you need pattern-based cache clearing
|
|
209
|
+
|
|
210
|
+
### Redis
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
from fastapi_cachex.backends import AsyncRedisCacheBackend
|
|
214
|
+
from fastapi_cachex import BackendProxy
|
|
215
|
+
|
|
216
|
+
backend = AsyncRedisCacheBackend(host="127.0.0.1", port=6379, db=0)
|
|
217
|
+
BackendProxy.set_backend(backend)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Features**:
|
|
221
|
+
- Fully async implementation
|
|
222
|
+
- Supports pattern-based key clearing
|
|
223
|
+
- Uses SCAN instead of KEYS for safe production use (non-blocking)
|
|
224
|
+
- Namespaced with `fastapi_cachex:` prefix by default
|
|
225
|
+
- Optional custom key prefix for multi-tenant scenarios
|
|
226
|
+
|
|
227
|
+
**Example with custom prefix**:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
backend = AsyncRedisCacheBackend(
|
|
231
|
+
host="127.0.0.1",
|
|
232
|
+
port=6379,
|
|
233
|
+
key_prefix="myapp:cache:",
|
|
234
|
+
)
|
|
235
|
+
BackendProxy.set_backend(backend)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Performance Considerations
|
|
239
|
+
|
|
240
|
+
### Cache Hit Performance
|
|
241
|
+
|
|
242
|
+
When a cache hit occurs (within TTL), the response is returned directly without executing your endpoint handler. This is extremely fast:
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
@app.get("/expensive")
|
|
246
|
+
@cache(ttl=3600) # Cache for 1 hour
|
|
247
|
+
async def expensive_operation():
|
|
248
|
+
# This is ONLY executed when cache misses
|
|
249
|
+
# On cache hits, this function is never called
|
|
250
|
+
result = perform_expensive_calculation()
|
|
251
|
+
return result
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Backend Selection
|
|
255
|
+
|
|
256
|
+
- **MemoryBackend**: Fastest for single-process development; not suitable for production
|
|
257
|
+
- **Memcached**: Good for distributed systems; has limitations on pattern clearing
|
|
258
|
+
- **Redis**: Best for production; fully async, supports all features, non-blocking operations
|
|
259
|
+
|
|
260
|
+
## Documentation
|
|
261
|
+
|
|
262
|
+
- [Cache Flow Explanation](docs/CACHE_FLOW.md)
|
|
263
|
+
- [Development Guide](docs/DEVELOPMENT.md)
|
|
264
|
+
- [Contributing Guidelines](docs/CONTRIBUTING.md)
|
|
265
|
+
- [Session Management Guide](docs/SESSION.md) - Complete guide for session features
|
|
266
|
+
|
|
267
|
+
## License
|
|
268
|
+
|
|
269
|
+
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# FastAPI-Cache X
|
|
2
|
+
|
|
3
|
+
[](https://github.com/astral-sh/uv)
|
|
4
|
+
[](https://github.com/astral-sh/ruff)
|
|
5
|
+
[](https://github.com/allen0099/FastAPI-CacheX/actions/workflows/test.yml)
|
|
6
|
+
[](https://github.com/allen0099/FastAPI-CacheX/actions/workflows/coverage.yml)
|
|
7
|
+
|
|
8
|
+
[](https://pepy.tech/project/fastapi-cachex)
|
|
9
|
+
[](https://pepy.tech/project/fastapi-cachex)
|
|
10
|
+
[](https://pepy.tech/project/fastapi-cachex)
|
|
11
|
+
|
|
12
|
+
[](https://pypi.org/project/fastapi-cachex)
|
|
13
|
+
[](https://pypi.org/project/fastapi-cachex/)
|
|
14
|
+
|
|
15
|
+
[English](README.md) | [繁體中文](docs/README.zh-TW.md)
|
|
16
|
+
|
|
17
|
+
A high-performance caching extension for FastAPI, providing comprehensive HTTP caching support and optional session management.
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
### HTTP Caching
|
|
22
|
+
- Support for HTTP caching headers
|
|
23
|
+
- `Cache-Control`
|
|
24
|
+
- `ETag`
|
|
25
|
+
- `If-None-Match`
|
|
26
|
+
- Multiple backend cache support
|
|
27
|
+
- Redis
|
|
28
|
+
- Memcached
|
|
29
|
+
- In-memory cache
|
|
30
|
+
- Complete Cache-Control directive implementation
|
|
31
|
+
- Easy-to-use `@cache` decorator
|
|
32
|
+
|
|
33
|
+
### Session Management (Optional Extension)
|
|
34
|
+
- Secure session management with HMAC-SHA256 token signing
|
|
35
|
+
- Optional JWT token format for interoperability (install extra `jwt`)
|
|
36
|
+
- IP address and User-Agent binding (optional security features)
|
|
37
|
+
- Header and bearer token support (API-first architecture)
|
|
38
|
+
- Automatic session renewal (sliding expiration)
|
|
39
|
+
- Flash messages for cross-request communication
|
|
40
|
+
- Multiple backend support (Redis, Memcached, In-Memory)
|
|
41
|
+
- Complete session lifecycle management (create, validate, refresh, invalidate)
|
|
42
|
+
|
|
43
|
+
### Cache-Control Directives
|
|
44
|
+
|
|
45
|
+
| Directive | Supported | Description |
|
|
46
|
+
|--------------------------|--------------------|---------------------------------------------------------------------------------------------------------|
|
|
47
|
+
| `max-age` | :white_check_mark: | Specifies the maximum amount of time a resource is considered fresh. |
|
|
48
|
+
| `s-maxage` | :x: | Specifies the maximum amount of time a resource is considered fresh for shared caches. |
|
|
49
|
+
| `no-cache` | :white_check_mark: | Forces caches to submit the request to the origin server for validation before releasing a cached copy. |
|
|
50
|
+
| `no-store` | :white_check_mark: | Instructs caches not to store any part of the request or response. |
|
|
51
|
+
| `no-transform` | :x: | Instructs caches not to transform the response content. |
|
|
52
|
+
| `must-revalidate` | :white_check_mark: | Forces caches to revalidate the response with the origin server after it becomes stale. |
|
|
53
|
+
| `proxy-revalidate` | :x: | Similar to `must-revalidate`, but only for shared caches. |
|
|
54
|
+
| `must-understand` | :x: | Indicates that the recipient must understand the directive or treat it as an error. |
|
|
55
|
+
| `private` | :white_check_mark: | Indicates that the response is intended for a single user and should not be stored by shared caches. |
|
|
56
|
+
| `public` | :white_check_mark: | Indicates that the response may be cached by any cache, even if it is normally non-cacheable. |
|
|
57
|
+
| `immutable` | :white_check_mark: | Indicates that the response body will not change over time, allowing for longer caching. |
|
|
58
|
+
| `stale-while-revalidate` | :white_check_mark: | Indicates that a cache can serve a stale response while it revalidates the response in the background. |
|
|
59
|
+
| `stale-if-error` | :white_check_mark: | Indicates that a cache can serve a stale response if the origin server is unavailable. |
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
uv add fastapi-cachex
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
To enable JWT token format support for sessions:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uv add "fastapi-cachex[jwt]"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Development Installation
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
uv add git+https://github.com/allen0099/FastAPI-CacheX.git
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Quick Start
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from fastapi import FastAPI
|
|
83
|
+
from fastapi_cachex import cache
|
|
84
|
+
from fastapi_cachex import CacheBackend
|
|
85
|
+
|
|
86
|
+
app = FastAPI()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@app.get("/")
|
|
90
|
+
@cache(ttl=60) # Cache for 60 seconds
|
|
91
|
+
async def read_root():
|
|
92
|
+
return {"Hello": "World"}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@app.get("/no-cache")
|
|
96
|
+
@cache(no_cache=True) # Mark this endpoint as non-cacheable
|
|
97
|
+
async def non_cache_endpoint():
|
|
98
|
+
return {"Hello": "World"}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@app.get("/no-store")
|
|
102
|
+
@cache(no_store=True) # Mark this endpoint as non-cacheable
|
|
103
|
+
async def non_store_endpoint():
|
|
104
|
+
return {"Hello": "World"}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@app.get("/clear_cache")
|
|
108
|
+
async def remove_cache(cache: CacheBackend):
|
|
109
|
+
await cache.clear_path("/path/to/clear") # Clear cache for a specific path
|
|
110
|
+
await cache.clear_pattern("/path/to/clear/*") # Clear cache for a specific pattern
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Backend Configuration
|
|
114
|
+
|
|
115
|
+
FastAPI-CacheX supports multiple caching backends. You can easily switch between them using the `BackendProxy`.
|
|
116
|
+
|
|
117
|
+
### Cache Key Format
|
|
118
|
+
|
|
119
|
+
Cache keys are generated in the following format to avoid collisions:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
{method}|||{host}|||{path}|||{query_params}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
This ensures that:
|
|
126
|
+
- Different HTTP methods (GET, POST, etc.) don't share cache
|
|
127
|
+
- Different hosts don't share cache (useful for multi-tenant scenarios)
|
|
128
|
+
- Different query parameters get separate cache entries
|
|
129
|
+
- The same endpoint with different parameters can be cached independently
|
|
130
|
+
|
|
131
|
+
All backends automatically namespace keys with a prefix (e.g., `fastapi_cachex:`) to avoid conflicts with other applications.
|
|
132
|
+
|
|
133
|
+
### Cache Hit Behavior
|
|
134
|
+
|
|
135
|
+
When a cached entry is valid (within TTL):
|
|
136
|
+
- **Default behavior**: Returns the cached content with HTTP 200 status code directly without re-executing the endpoint handler
|
|
137
|
+
- **With `If-None-Match` header**: Returns HTTP 304 Not Modified if the ETag matches
|
|
138
|
+
- **With `no-cache` directive**: Forces revalidation with fresh content before deciding on 304
|
|
139
|
+
|
|
140
|
+
This means **cached hits are extremely fast** - the endpoint handler function is never executed.
|
|
141
|
+
|
|
142
|
+
### In-Memory Cache (default)
|
|
143
|
+
|
|
144
|
+
If you don't specify a backend, FastAPI-CacheX will use the in-memory cache by default.
|
|
145
|
+
This is suitable for development and testing purposes. The backend automatically runs
|
|
146
|
+
a cleanup task to remove expired entries every 60 seconds.
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from fastapi_cachex.backends import MemoryBackend
|
|
150
|
+
from fastapi_cachex import BackendProxy
|
|
151
|
+
|
|
152
|
+
backend = MemoryBackend()
|
|
153
|
+
BackendProxy.set_backend(backend)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Note**: In-memory cache is not suitable for production with multiple processes.
|
|
157
|
+
Each process maintains its own separate cache.
|
|
158
|
+
|
|
159
|
+
### Memcached
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from fastapi_cachex.backends import MemcachedBackend
|
|
163
|
+
from fastapi_cachex import BackendProxy
|
|
164
|
+
|
|
165
|
+
backend = MemcachedBackend(servers=["localhost:11211"])
|
|
166
|
+
BackendProxy.set_backend(backend)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Limitations**:
|
|
170
|
+
- Pattern-based key clearing (`clear_pattern`) is not supported by the Memcached protocol
|
|
171
|
+
- Keys are namespaced with `fastapi_cachex:` prefix to avoid conflicts
|
|
172
|
+
- Consider using Redis backend if you need pattern-based cache clearing
|
|
173
|
+
|
|
174
|
+
### Redis
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from fastapi_cachex.backends import AsyncRedisCacheBackend
|
|
178
|
+
from fastapi_cachex import BackendProxy
|
|
179
|
+
|
|
180
|
+
backend = AsyncRedisCacheBackend(host="127.0.0.1", port=6379, db=0)
|
|
181
|
+
BackendProxy.set_backend(backend)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Features**:
|
|
185
|
+
- Fully async implementation
|
|
186
|
+
- Supports pattern-based key clearing
|
|
187
|
+
- Uses SCAN instead of KEYS for safe production use (non-blocking)
|
|
188
|
+
- Namespaced with `fastapi_cachex:` prefix by default
|
|
189
|
+
- Optional custom key prefix for multi-tenant scenarios
|
|
190
|
+
|
|
191
|
+
**Example with custom prefix**:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
backend = AsyncRedisCacheBackend(
|
|
195
|
+
host="127.0.0.1",
|
|
196
|
+
port=6379,
|
|
197
|
+
key_prefix="myapp:cache:",
|
|
198
|
+
)
|
|
199
|
+
BackendProxy.set_backend(backend)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Performance Considerations
|
|
203
|
+
|
|
204
|
+
### Cache Hit Performance
|
|
205
|
+
|
|
206
|
+
When a cache hit occurs (within TTL), the response is returned directly without executing your endpoint handler. This is extremely fast:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
@app.get("/expensive")
|
|
210
|
+
@cache(ttl=3600) # Cache for 1 hour
|
|
211
|
+
async def expensive_operation():
|
|
212
|
+
# This is ONLY executed when cache misses
|
|
213
|
+
# On cache hits, this function is never called
|
|
214
|
+
result = perform_expensive_calculation()
|
|
215
|
+
return result
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Backend Selection
|
|
219
|
+
|
|
220
|
+
- **MemoryBackend**: Fastest for single-process development; not suitable for production
|
|
221
|
+
- **Memcached**: Good for distributed systems; has limitations on pattern clearing
|
|
222
|
+
- **Redis**: Best for production; fully async, supports all features, non-blocking operations
|
|
223
|
+
|
|
224
|
+
## Documentation
|
|
225
|
+
|
|
226
|
+
- [Cache Flow Explanation](docs/CACHE_FLOW.md)
|
|
227
|
+
- [Development Guide](docs/DEVELOPMENT.md)
|
|
228
|
+
- [Contributing Guidelines](docs/CONTRIBUTING.md)
|
|
229
|
+
- [Session Management Guide](docs/SESSION.md) - Complete guide for session features
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""FastAPI-CacheX: A powerful and flexible caching extension for FastAPI."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from .cache import cache as cache
|
|
6
|
+
from .cache import default_key_builder as default_key_builder
|
|
7
|
+
from .dependencies import CacheBackend as CacheBackend
|
|
8
|
+
from .dependencies import get_cache_backend as get_cache_backend
|
|
9
|
+
from .proxy import BackendProxy as BackendProxy
|
|
10
|
+
from .routes import add_routes as add_routes
|
|
11
|
+
from .types import CacheKeyBuilder as CacheKeyBuilder
|
|
12
|
+
|
|
13
|
+
_package_logger = logging.getLogger("fastapi_cachex")
|
|
14
|
+
_package_logger.addHandler(
|
|
15
|
+
logging.NullHandler()
|
|
16
|
+
) # Attach a NullHandler to avoid "No handler found" warnings in user applications.
|
|
17
|
+
|
|
18
|
+
# Session management (optional feature)
|
|
19
|
+
__all__ = [
|
|
20
|
+
"BackendProxy",
|
|
21
|
+
"CacheBackend",
|
|
22
|
+
"CacheKeyBuilder",
|
|
23
|
+
"add_routes",
|
|
24
|
+
"cache",
|
|
25
|
+
"default_key_builder",
|
|
26
|
+
"get_cache_backend",
|
|
27
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Cache backend implementations for FastAPI-CacheX."""
|
|
2
|
+
|
|
3
|
+
from .base import BaseCacheBackend
|
|
4
|
+
from .config import RedisConfig
|
|
5
|
+
from .memcached import MemcachedBackend
|
|
6
|
+
from .memory import MemoryBackend
|
|
7
|
+
from .redis import AsyncRedisCacheBackend
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"AsyncRedisCacheBackend",
|
|
11
|
+
"BaseCacheBackend",
|
|
12
|
+
"MemcachedBackend",
|
|
13
|
+
"MemoryBackend",
|
|
14
|
+
"RedisConfig",
|
|
15
|
+
]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Base cache backend interface and abstract implementation."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC
|
|
4
|
+
from abc import abstractmethod
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from fastapi_cachex.types import ETagContent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseCacheBackend(ABC):
|
|
11
|
+
"""Base class for all cache backends."""
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
async def get(self, key: str) -> ETagContent | None:
|
|
15
|
+
"""Retrieve a cached response."""
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
async def set(self, key: str, value: ETagContent, ttl: int | None = None) -> None:
|
|
19
|
+
"""Store a response in the cache."""
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
async def delete(self, key: str) -> None:
|
|
23
|
+
"""Remove a response from the cache."""
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
async def clear(self) -> None:
|
|
27
|
+
"""Clear all cached responses."""
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
async def clear_path(self, path: str, include_params: bool = False) -> int:
|
|
31
|
+
"""Clear cached responses for a specific path.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
path: The path to clear cache for
|
|
35
|
+
include_params: Whether to clear all parameter variations of the path
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Number of cache entries cleared
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
async def clear_pattern(self, pattern: str) -> int:
|
|
43
|
+
"""Clear cached responses matching a pattern.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
pattern: A glob pattern to match cache keys against (e.g., "/users/*")
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Number of cache entries cleared
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
async def get_all_keys(self) -> list[str]:
|
|
54
|
+
"""Get all cache keys in the backend.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
List of all cache keys currently stored in the backend
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
async def get_cache_data(self) -> dict[str, tuple[Any, float | None]]:
|
|
62
|
+
"""Get all cache data with expiry information.
|
|
63
|
+
|
|
64
|
+
This method is primarily used for cache monitoring and statistics.
|
|
65
|
+
Returns cache keys mapped to tuples of (value, expiry_time).
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Dictionary mapping cache keys to (value, expiry) tuples.
|
|
69
|
+
Expiry is None if the item never expires.
|
|
70
|
+
"""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Configuration models for cache backends."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
from pydantic import SecretStr
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RedisConfig(BaseModel):
|
|
9
|
+
"""Configuration for Redis backend."""
|
|
10
|
+
|
|
11
|
+
host: str = Field(default="localhost", description="Redis server address")
|
|
12
|
+
port: int = Field(default=6379, description="Redis server port")
|
|
13
|
+
password: SecretStr | None = Field(
|
|
14
|
+
default=None, description="Redis server password"
|
|
15
|
+
)
|