fast-cache-middleware 0.0.3__py3-none-any.whl โ 0.0.5__py3-none-any.whl
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.
- fast_cache_middleware/_helpers.py +28 -0
- fast_cache_middleware/controller.py +11 -10
- fast_cache_middleware/depends.py +7 -19
- fast_cache_middleware/middleware.py +203 -140
- fast_cache_middleware/schemas.py +71 -14
- fast_cache_middleware/serializers.py +8 -27
- fast_cache_middleware/storages.py +9 -9
- fast_cache_middleware-0.0.5.dist-info/LICENSE +21 -0
- fast_cache_middleware-0.0.5.dist-info/METADATA +344 -0
- fast_cache_middleware-0.0.5.dist-info/RECORD +13 -0
- fast_cache_middleware-0.0.3.dist-info/METADATA +0 -371
- fast_cache_middleware-0.0.3.dist-info/RECORD +0 -11
- {fast_cache_middleware-0.0.3.dist-info โ fast_cache_middleware-0.0.5.dist-info}/WHEEL +0 -0
@@ -1,23 +1,21 @@
|
|
1
1
|
import json
|
2
|
-
|
2
|
+
from typing import Any, Callable, Dict, Optional, Tuple, TypeAlias, Union
|
3
3
|
|
4
4
|
from starlette.requests import Request
|
5
5
|
from starlette.responses import Response
|
6
6
|
|
7
7
|
# Define types for metadata and stored response
|
8
|
-
Metadata:
|
9
|
-
StoredResponse:
|
8
|
+
Metadata: TypeAlias = Dict[str, Any] # todo: make it models
|
9
|
+
StoredResponse: TypeAlias = Tuple[Response, Request, Metadata]
|
10
10
|
|
11
11
|
|
12
12
|
class BaseSerializer:
|
13
13
|
def dumps(
|
14
14
|
self, response: Response, request: Request, metadata: Metadata
|
15
|
-
) ->
|
15
|
+
) -> Union[str, bytes]:
|
16
16
|
raise NotImplementedError()
|
17
17
|
|
18
|
-
def loads(
|
19
|
-
self, data: tp.Union[str, bytes]
|
20
|
-
) -> tp.Tuple[Response, Request, Metadata]:
|
18
|
+
def loads(self, data: Union[str, bytes]) -> Tuple[Response, Request, Metadata]:
|
21
19
|
raise NotImplementedError()
|
22
20
|
|
23
21
|
@property
|
@@ -27,26 +25,9 @@ class BaseSerializer:
|
|
27
25
|
|
28
26
|
class JSONSerializer(BaseSerializer):
|
29
27
|
def dumps(self, response: Response, request: Request, metadata: Metadata) -> str:
|
30
|
-
|
31
|
-
"response": {
|
32
|
-
"status_code": response.status_code,
|
33
|
-
"headers": [[k.decode(), v.decode()] for k, v in response.headers.raw],
|
34
|
-
"content": (
|
35
|
-
response.body.decode("utf-8", errors="ignore")
|
36
|
-
if response.body
|
37
|
-
else None
|
38
|
-
),
|
39
|
-
},
|
40
|
-
"request": {
|
41
|
-
"method": request.method,
|
42
|
-
"url": str(request.url),
|
43
|
-
"headers": [[k.decode(), v.decode()] for k, v in request.headers.raw],
|
44
|
-
},
|
45
|
-
"metadata": metadata,
|
46
|
-
}
|
47
|
-
return json.dumps(serialized)
|
28
|
+
raise NotImplementedError() # fixme: bad implementation now, maybe async?
|
48
29
|
|
49
|
-
def loads(self, data:
|
30
|
+
def loads(self, data: Union[str, bytes]) -> StoredResponse:
|
50
31
|
if isinstance(data, bytes):
|
51
32
|
data = data.decode()
|
52
33
|
|
@@ -80,7 +61,7 @@ class JSONSerializer(BaseSerializer):
|
|
80
61
|
}
|
81
62
|
|
82
63
|
# Create empty receive function
|
83
|
-
async def receive():
|
64
|
+
async def receive() -> Dict[str, Any]:
|
84
65
|
return {"type": "http.request", "body": b""}
|
85
66
|
|
86
67
|
request = Request(scope, receive)
|
@@ -1,8 +1,8 @@
|
|
1
1
|
import logging
|
2
2
|
import re
|
3
3
|
import time
|
4
|
-
import typing as tp
|
5
4
|
from collections import OrderedDict
|
5
|
+
from typing import Any, Dict, Optional, Tuple, Union
|
6
6
|
|
7
7
|
from starlette.requests import Request
|
8
8
|
from starlette.responses import Response
|
@@ -14,7 +14,7 @@ from .serializers import BaseSerializer, JSONSerializer, Metadata
|
|
14
14
|
logger = logging.getLogger(__name__)
|
15
15
|
|
16
16
|
# Define type for stored response
|
17
|
-
StoredResponse: TypeAlias =
|
17
|
+
StoredResponse: TypeAlias = Tuple[Response, Request, Metadata]
|
18
18
|
|
19
19
|
|
20
20
|
# Define base class for cache storage
|
@@ -28,8 +28,8 @@ class BaseStorage:
|
|
28
28
|
|
29
29
|
def __init__(
|
30
30
|
self,
|
31
|
-
serializer:
|
32
|
-
ttl:
|
31
|
+
serializer: Optional[BaseSerializer] = None,
|
32
|
+
ttl: Optional[Union[int, float]] = None,
|
33
33
|
) -> None:
|
34
34
|
self._serializer = serializer or JSONSerializer()
|
35
35
|
|
@@ -43,7 +43,7 @@ class BaseStorage:
|
|
43
43
|
) -> None:
|
44
44
|
raise NotImplementedError()
|
45
45
|
|
46
|
-
async def retrieve(self, key: str) ->
|
46
|
+
async def retrieve(self, key: str) -> Optional[StoredResponse]:
|
47
47
|
raise NotImplementedError()
|
48
48
|
|
49
49
|
async def remove(self, path: re.Pattern) -> None:
|
@@ -70,8 +70,8 @@ class InMemoryStorage(BaseStorage):
|
|
70
70
|
def __init__(
|
71
71
|
self,
|
72
72
|
max_size: int = 1000,
|
73
|
-
serializer:
|
74
|
-
ttl:
|
73
|
+
serializer: Optional[BaseSerializer] = None,
|
74
|
+
ttl: Optional[Union[int, float]] = None,
|
75
75
|
) -> None:
|
76
76
|
super().__init__(serializer=serializer, ttl=ttl)
|
77
77
|
|
@@ -87,7 +87,7 @@ class InMemoryStorage(BaseStorage):
|
|
87
87
|
# OrderedDict for efficient LRU
|
88
88
|
self._storage: OrderedDict[str, StoredResponse] = OrderedDict()
|
89
89
|
# Separate expiry time storage for fast TTL checking
|
90
|
-
self._expiry_times:
|
90
|
+
self._expiry_times: Dict[str, float] = {}
|
91
91
|
self._last_expiry_check_time: float = 0
|
92
92
|
self._expiry_check_interval: float = 60
|
93
93
|
|
@@ -126,7 +126,7 @@ class InMemoryStorage(BaseStorage):
|
|
126
126
|
|
127
127
|
self._cleanup_lru_items()
|
128
128
|
|
129
|
-
async def retrieve(self, key: str) ->
|
129
|
+
async def retrieve(self, key: str) -> Optional[StoredResponse]:
|
130
130
|
"""Gets response from cache with lazy TTL checking.
|
131
131
|
|
132
132
|
Element moves to the end to update LRU position.
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Vadim Vakilov
|
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,344 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: fast-cache-middleware
|
3
|
+
Version: 0.0.5
|
4
|
+
Summary: An intelligent middleware for caching FastAPI responses
|
5
|
+
License: MIT
|
6
|
+
Author: www.chud0@gmail.com
|
7
|
+
Requires-Python: >=3.11,<4.0
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
13
|
+
Requires-Dist: fastapi (>=0.111.1,<1.0.0)
|
14
|
+
Requires-Dist: pydantic (>=1.7.4,!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0)
|
15
|
+
Description-Content-Type: text/markdown
|
16
|
+
|
17
|
+
# FastCacheMiddleware
|
18
|
+
|
19
|
+
๐ **High-performance ASGI middleware for caching with route resolution approach**
|
20
|
+
|
21
|
+
## โจ Key Features
|
22
|
+
|
23
|
+
FastCacheMiddleware uses a **route resolution approach** - it analyzes application routes at startup and extracts cache configurations from FastAPI dependencies.
|
24
|
+
|
25
|
+
### ๐ง How it works
|
26
|
+
|
27
|
+
1. **At application startup:**
|
28
|
+
- Middleware analyzes all routes and their dependencies
|
29
|
+
- Extracts `CacheConfig` and `CacheDropConfig` from dependencies
|
30
|
+
- Creates internal route index with caching configurations
|
31
|
+
|
32
|
+
2. **During request processing:**
|
33
|
+
- Checks HTTP method (cache only GET, invalidate for POST/PUT/DELETE)
|
34
|
+
- Finds matching route by path and method
|
35
|
+
- Extracts cache configuration from pre-analyzed dependencies
|
36
|
+
- Performs caching or invalidation according to configuration
|
37
|
+
|
38
|
+
### ๐ก Benefits
|
39
|
+
|
40
|
+
- **โก High performance** - pre-route analysis
|
41
|
+
- **๐ฏ Easy integration** - standard FastAPI dependencies
|
42
|
+
- **๐ง Flexible configuration** - custom key functions, route-level TTL
|
43
|
+
- **๐ก๏ธ Automatic invalidation** - cache invalidation for modifying requests
|
44
|
+
- **๐ Minimal overhead** - efficient handling of large numbers of routes
|
45
|
+
|
46
|
+
## ๐ฆ Installation
|
47
|
+
|
48
|
+
```bash
|
49
|
+
pip install fast-cache-middleware
|
50
|
+
```
|
51
|
+
|
52
|
+
## ๐ฏ Quick Start
|
53
|
+
|
54
|
+
```python
|
55
|
+
import uvicorn
|
56
|
+
from fastapi import FastAPI
|
57
|
+
|
58
|
+
from fast_cache_middleware import CacheConfig, CacheDropConfig, FastCacheMiddleware
|
59
|
+
|
60
|
+
app = FastAPI()
|
61
|
+
|
62
|
+
# Add middleware - it will automatically analyze routes
|
63
|
+
app.add_middleware(FastCacheMiddleware)
|
64
|
+
|
65
|
+
|
66
|
+
# Routes with caching
|
67
|
+
@app.get("/users/{user_id}", dependencies=[CacheConfig(max_age=300)])
|
68
|
+
async def get_user(user_id: int) -> dict[str, int | str]:
|
69
|
+
"""This endpoint is cached for 5 minutes."""
|
70
|
+
# Simulate database load
|
71
|
+
return {"user_id": user_id, "name": f"User {user_id}"}
|
72
|
+
|
73
|
+
|
74
|
+
# Routes with cache invalidation
|
75
|
+
@app.post(
|
76
|
+
"/users/{user_id}",
|
77
|
+
dependencies=[CacheDropConfig(paths=["/users/*", "/api/users/*"])],
|
78
|
+
)
|
79
|
+
async def update_user(user_id: int) -> dict[str, int | str]:
|
80
|
+
"""It will invalidate cache for all /users/* paths."""
|
81
|
+
return {"user_id": user_id, "status": "updated"}
|
82
|
+
|
83
|
+
|
84
|
+
if __name__ == "__main__":
|
85
|
+
uvicorn.run(app, host="127.0.0.1", port=8000)
|
86
|
+
```
|
87
|
+
|
88
|
+
## ๐ง Configuration
|
89
|
+
|
90
|
+
### CacheConfig
|
91
|
+
|
92
|
+
Configure caching for GET requests:
|
93
|
+
|
94
|
+
```python
|
95
|
+
from fast_cache_middleware import CacheConfig
|
96
|
+
|
97
|
+
# Simple caching
|
98
|
+
CacheConfig(max_age=300) # 5 minutes
|
99
|
+
|
100
|
+
# With custom key function, for personalized cache
|
101
|
+
def key_func(request: Request):
|
102
|
+
user_id = request.headers.get("Authorization", "anonymous")
|
103
|
+
path = request.url.path
|
104
|
+
query = str(request.query_params)
|
105
|
+
return f"{path}:{user_id}:{query}"
|
106
|
+
|
107
|
+
CacheConfig(max_age=600, key_func=key_func) # 10 minutes
|
108
|
+
```
|
109
|
+
|
110
|
+
### CacheDropConfig
|
111
|
+
|
112
|
+
Configure cache invalidation for modifying requests:
|
113
|
+
|
114
|
+
```python
|
115
|
+
# Paths can be matched by startswith
|
116
|
+
CacheDropConfig(
|
117
|
+
paths=[
|
118
|
+
"/users/", # Will match /users/123, /users/profile, etc.
|
119
|
+
"/api/", # Will match all API paths
|
120
|
+
]
|
121
|
+
)
|
122
|
+
|
123
|
+
# Paths can be matched by regexp
|
124
|
+
CacheDropConfig(
|
125
|
+
paths=[
|
126
|
+
r"^/users/\d+$", # Will match /users/123, /users/456, etc.
|
127
|
+
r"^/api/.*", # Will match all API paths
|
128
|
+
]
|
129
|
+
)
|
130
|
+
|
131
|
+
# You can mix regexp and simple string matching - use what's more convenient
|
132
|
+
CacheDropConfig(
|
133
|
+
paths=[
|
134
|
+
"/users/", # Simple prefix match
|
135
|
+
r"^/api/\w+/\d+$" # Regexp for specific API endpoints
|
136
|
+
]
|
137
|
+
)
|
138
|
+
```
|
139
|
+
|
140
|
+
## ๐๏ธ Architecture
|
141
|
+
|
142
|
+
### System Components
|
143
|
+
|
144
|
+
```
|
145
|
+
FastCacheMiddleware
|
146
|
+
โโโ RouteInfo # Route information with cache configuration
|
147
|
+
โโโ Controller # Caching logic and validation
|
148
|
+
โโโ Storage # Storages (InMemory, Redis, etc.)
|
149
|
+
โโโ Serializers # Cached data serialization
|
150
|
+
โโโ Dependencies # FastAPI dependencies for configuration
|
151
|
+
```
|
152
|
+
|
153
|
+
### Request Processing Flow
|
154
|
+
|
155
|
+
```mermaid
|
156
|
+
graph TD
|
157
|
+
A[HTTP Request] --> B{Route analysis done?}
|
158
|
+
B -->|No| C[Analyze application routes]
|
159
|
+
C --> D[Save route configurations]
|
160
|
+
B -->|Yes| E{Method supports caching?}
|
161
|
+
D --> E
|
162
|
+
E -->|No| F[Pass to application]
|
163
|
+
E -->|Yes| G[Find matching route]
|
164
|
+
G --> H{Route found?}
|
165
|
+
H -->|No| F
|
166
|
+
H -->|Yes| I{GET request + CacheConfig?}
|
167
|
+
I -->|Yes| J[Check cache]
|
168
|
+
J --> K{Cache found?}
|
169
|
+
K -->|Yes| L[Return from cache]
|
170
|
+
K -->|No| M[Execute request + save to cache]
|
171
|
+
I -->|No| N{POST/PUT/DELETE + CacheDropConfig?}
|
172
|
+
N -->|Yes| O[Invalidate cache]
|
173
|
+
N -->|No| F
|
174
|
+
O --> F
|
175
|
+
M --> P[Return response]
|
176
|
+
```
|
177
|
+
|
178
|
+
## ๐๏ธ Storages
|
179
|
+
|
180
|
+
### InMemoryStorage (default)
|
181
|
+
|
182
|
+
```python
|
183
|
+
from fast_cache_middleware import FastCacheMiddleware, InMemoryStorage
|
184
|
+
|
185
|
+
storage = InMemoryStorage(max_size=1000)
|
186
|
+
app.add_middleware(FastCacheMiddleware, storage=storage)
|
187
|
+
```
|
188
|
+
|
189
|
+
InMemoryStorage uses batch cleanup for better performance
|
190
|
+
|
191
|
+
### Custom Storage
|
192
|
+
|
193
|
+
```python
|
194
|
+
from fast_cache_middleware import BaseStorage
|
195
|
+
|
196
|
+
class RedisStorage(BaseStorage):
|
197
|
+
def __init__(self, redis_url: str):
|
198
|
+
import redis
|
199
|
+
self.redis = redis.from_url(redis_url)
|
200
|
+
|
201
|
+
async def store(self, key: str, response, request, metadata):
|
202
|
+
# Implementation for saving to Redis
|
203
|
+
pass
|
204
|
+
|
205
|
+
async def retrieve(self, key: str):
|
206
|
+
# Implementation for retrieving from Redis
|
207
|
+
pass
|
208
|
+
|
209
|
+
app.add_middleware(FastCacheMiddleware, storage=RedisStorage("redis://localhost"))
|
210
|
+
```
|
211
|
+
|
212
|
+
## ๐งช Testing
|
213
|
+
|
214
|
+
```python
|
215
|
+
import pytest
|
216
|
+
from httpx import AsyncClient
|
217
|
+
from examples.basic import app
|
218
|
+
|
219
|
+
@pytest.mark.asyncio
|
220
|
+
async def test_caching():
|
221
|
+
async with AsyncClient(app=app, base_url="http://test") as client:
|
222
|
+
# First request - cache miss
|
223
|
+
response1 = await client.get("/users/1")
|
224
|
+
assert response1.status_code == 200
|
225
|
+
|
226
|
+
# Second request - cache hit (should be faster)
|
227
|
+
response2 = await client.get("/users/1")
|
228
|
+
assert response2.status_code == 200
|
229
|
+
assert response1.json() == response2.json()
|
230
|
+
|
231
|
+
@pytest.mark.asyncio
|
232
|
+
async def test_cache_invalidation():
|
233
|
+
async with AsyncClient(app=app, base_url="http://test") as client:
|
234
|
+
# Cache data
|
235
|
+
await client.get("/users/1")
|
236
|
+
|
237
|
+
# Invalidate cache
|
238
|
+
await client.post("/users/1", json={})
|
239
|
+
|
240
|
+
# Next GET should execute new request
|
241
|
+
response = await client.get("/users/1")
|
242
|
+
assert response.status_code == 200
|
243
|
+
```
|
244
|
+
|
245
|
+
## ๐ Performance
|
246
|
+
|
247
|
+
### Benchmarks
|
248
|
+
|
249
|
+
- **Route analysis**: ~5ms for 100 routes at startup
|
250
|
+
- **Route lookup**: ~0.1ms per request (O(n) by number of cached routes)
|
251
|
+
- **Cache hit**: ~1ms per request
|
252
|
+
- **Cache miss**: original request time + ~2ms for saving
|
253
|
+
|
254
|
+
### Optimization
|
255
|
+
|
256
|
+
```python
|
257
|
+
# For applications with many routes
|
258
|
+
app.add_middleware(
|
259
|
+
FastCacheMiddleware,
|
260
|
+
storage=InMemoryStorage(max_size=10000), # Increase cache size
|
261
|
+
controller=Controller(default_ttl=3600) # Increase default TTL
|
262
|
+
)
|
263
|
+
```
|
264
|
+
|
265
|
+
## ๐ Security
|
266
|
+
|
267
|
+
### Cache Isolation
|
268
|
+
|
269
|
+
```python
|
270
|
+
def user_specific_cache() -> CacheConfig:
|
271
|
+
def secure_key_func(request):
|
272
|
+
# Include user token in key
|
273
|
+
token = request.headers.get("authorization", "").split(" ")[-1]
|
274
|
+
return f"{request.url.path}:token:{token}"
|
275
|
+
|
276
|
+
return CacheConfig(max_age=300, key_func=secure_key_func)
|
277
|
+
|
278
|
+
@app.get("/private/data", dependencies=[Depends(user_specific_cache)])
|
279
|
+
async def get_private_data():
|
280
|
+
return {"sensitive": "data"}
|
281
|
+
```
|
282
|
+
|
283
|
+
### Header Validation
|
284
|
+
|
285
|
+
Middleware automatically respects standard HTTP caching headers:
|
286
|
+
|
287
|
+
- `Cache-Control: no-cache` - skip cache
|
288
|
+
- `Cache-Control: no-store` - forbid caching
|
289
|
+
- `Cache-Control: private` - don't cache private responses
|
290
|
+
|
291
|
+
|
292
|
+
## ๐ ๏ธ Advanced Usage
|
293
|
+
|
294
|
+
### Custom Controller
|
295
|
+
|
296
|
+
```python
|
297
|
+
from fast_cache_middleware import Controller
|
298
|
+
|
299
|
+
class CustomController(Controller):
|
300
|
+
async def is_cachable_request(self, request):
|
301
|
+
# Custom logic - don't cache admin requests
|
302
|
+
if request.headers.get("x-admin-request"):
|
303
|
+
return False
|
304
|
+
return await super().should_cache_request(request)
|
305
|
+
|
306
|
+
async def generate_cache_key(self, request):
|
307
|
+
# Add API version to key
|
308
|
+
version = request.headers.get("api-version", "v1")
|
309
|
+
base_key = await super().generate_cache_key(request)
|
310
|
+
return f"{version}:{base_key}"
|
311
|
+
|
312
|
+
app.add_middleware(
|
313
|
+
FastCacheMiddleware,
|
314
|
+
controller=CustomController()
|
315
|
+
)
|
316
|
+
```
|
317
|
+
|
318
|
+
## ๐ Examples
|
319
|
+
|
320
|
+
More examples in the `examples/` folder:
|
321
|
+
|
322
|
+
- **quick_start.py** - minimal example showing basic caching and invalidation
|
323
|
+
- **basic.py** - basic usage with FastAPI
|
324
|
+
|
325
|
+
## ๐ค Contributing
|
326
|
+
|
327
|
+
```bash
|
328
|
+
git clone https://github.com/chud0/FastCacheMiddleware
|
329
|
+
cd FastCacheMiddleware
|
330
|
+
poetry install --with dev
|
331
|
+
./scripts/test.sh
|
332
|
+
```
|
333
|
+
|
334
|
+
## ๐ License
|
335
|
+
|
336
|
+
MIT License - see [LICENSE](LICENSE)
|
337
|
+
|
338
|
+
---
|
339
|
+
|
340
|
+
โญ **Like the project? Give it a star!**
|
341
|
+
|
342
|
+
๐ **Found a bug?** [Create an issue](https://github.com/chud0/FastCacheMiddleware/issues)
|
343
|
+
|
344
|
+
๐ก **Have an idea?** [Suggest a feature](https://github.com/chud0/FastCacheMiddleware/discussions/categories/ideas)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
fast_cache_middleware/__init__.py,sha256=0wuBPKrzjyBA2K9Fnm-xzC1mYSXCMuYr1uPutNXCI5Y,983
|
2
|
+
fast_cache_middleware/_helpers.py,sha256=0VbqByNP5AL7mEJpdZLYwef2YhgZsGJOf1tJBshuIZI,904
|
3
|
+
fast_cache_middleware/controller.py,sha256=gWGS6r6mWiCOS-cxuzniukzunKowyrnkvl1psamYJHg,7216
|
4
|
+
fast_cache_middleware/depends.py,sha256=oHahWfC4PN-rGXbUbmK8TWVlztGO5eswZzaIZdEcfs4,1431
|
5
|
+
fast_cache_middleware/exceptions.py,sha256=lv3p2-wAj6UZI7czVSy91eZtuXItr1hZHW2LDz8fI9s,109
|
6
|
+
fast_cache_middleware/middleware.py,sha256=ptUwKIe-dpd8kdA9DZDSE98luGtom2BTDvNzlUF9qlM,11940
|
7
|
+
fast_cache_middleware/schemas.py,sha256=h7Gq4Pdc1DVkHFGUWUZnI063DXcX-n66Uky9z88cOl8,2331
|
8
|
+
fast_cache_middleware/serializers.py,sha256=_aBMbqVWVNYo77xhc7qKuVHg3opIfh8Wa0bYO2j6xn4,2342
|
9
|
+
fast_cache_middleware/storages.py,sha256=lErquGfVMDXFZayFDuHm3MKq6Z-0D9-Iru7I1U7b5kE,7614
|
10
|
+
fast_cache_middleware-0.0.5.dist-info/LICENSE,sha256=8YvnSj0hf8-FrLOBF2gLYdVKdJsvS83pgYwGZ0EN-Xk,1069
|
11
|
+
fast_cache_middleware-0.0.5.dist-info/METADATA,sha256=MYnxUUzrwifLSUrtk_pRHgXtKnk3RFpeGOUtNE2Md4Q,9547
|
12
|
+
fast_cache_middleware-0.0.5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
13
|
+
fast_cache_middleware-0.0.5.dist-info/RECORD,,
|