fastapi-cachekit 0.1.4__tar.gz → 0.2.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_cachekit-0.1.4/fastapi_cachekit.egg-info → fastapi_cachekit-0.2.0}/PKG-INFO +5 -2
- {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fast_cache/__init__.py +5 -1
- {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fast_cache/backends/backend.py +6 -4
- {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fast_cache/backends/dynamodb.py +55 -36
- fastapi_cachekit-0.2.0/fast_cache/backends/google_firestore.py +506 -0
- fastapi_cachekit-0.2.0/fast_cache/backends/memcached.py +331 -0
- fastapi_cachekit-0.2.0/fast_cache/backends/memory.py +462 -0
- {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fast_cache/backends/mongodb.py +10 -8
- fastapi_cachekit-0.2.0/fast_cache/backends/postgres.py +555 -0
- {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fast_cache/backends/redis.py +162 -25
- {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fast_cache/integration.py +86 -11
- {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0/fastapi_cachekit.egg-info}/PKG-INFO +5 -2
- {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fastapi_cachekit.egg-info/requires.txt +3 -1
- {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/pyproject.toml +24 -3
- fastapi_cachekit-0.1.4/fast_cache/backends/google_firestore.py +0 -351
- fastapi_cachekit-0.1.4/fast_cache/backends/memcached.py +0 -136
- fastapi_cachekit-0.1.4/fast_cache/backends/memory.py +0 -318
- fastapi_cachekit-0.1.4/fast_cache/backends/postgres.py +0 -274
- {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/LICENSE.md +0 -0
- {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/README.md +0 -0
- {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fast_cache/backends/__init__.py +0 -0
- {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fastapi_cachekit.egg-info/SOURCES.txt +0 -0
- {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fastapi_cachekit.egg-info/dependency_links.txt +0 -0
- {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fastapi_cachekit.egg-info/top_level.txt +0 -0
- {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-cachekit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: High-performance caching solution for FastAPI applications
|
|
5
5
|
Author-email: Bijay Nayak <bijay6779@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
23
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
23
24
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
@@ -28,6 +29,7 @@ Requires-Python: >=3.9
|
|
|
28
29
|
Description-Content-Type: text/markdown
|
|
29
30
|
License-File: LICENSE.md
|
|
30
31
|
Requires-Dist: fastapi>=0.75.0
|
|
32
|
+
Requires-Dist: apscheduler>=3.4.0
|
|
31
33
|
Provides-Extra: redis
|
|
32
34
|
Requires-Dist: redis>=4.2.0; extra == "redis"
|
|
33
35
|
Provides-Extra: postgres
|
|
@@ -36,7 +38,7 @@ Provides-Extra: memcached
|
|
|
36
38
|
Requires-Dist: aiomcache>=0.8.1; extra == "memcached"
|
|
37
39
|
Requires-Dist: pymemcache>=4.0.0; extra == "memcached"
|
|
38
40
|
Provides-Extra: mongodb
|
|
39
|
-
Requires-Dist: pymongo[gssapi,snappy
|
|
41
|
+
Requires-Dist: pymongo[gssapi,snappy]>=4.6.0; extra == "mongodb"
|
|
40
42
|
Provides-Extra: firestore
|
|
41
43
|
Requires-Dist: google-cloud-firestore>=2.3.0; extra == "firestore"
|
|
42
44
|
Provides-Extra: dynamodb
|
|
@@ -45,6 +47,7 @@ Requires-Dist: aioboto3>=6.0.0; extra == "dynamodb"
|
|
|
45
47
|
Provides-Extra: all
|
|
46
48
|
Requires-Dist: redis>=4.2.0; extra == "all"
|
|
47
49
|
Requires-Dist: psycopg[pool]>=3.2.9; extra == "all"
|
|
50
|
+
Requires-Dist: apscheduler>=3.4.0; extra == "all"
|
|
48
51
|
Requires-Dist: aiomcache>=0.8.1; extra == "all"
|
|
49
52
|
Requires-Dist: pymemcache>=4.0.0; extra == "all"
|
|
50
53
|
Requires-Dist: pymongo[gssapi,snappy,srv]>=4.6.0; extra == "all"
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
from importlib.metadata import version
|
|
2
|
+
|
|
1
3
|
from .integration import FastAPICache
|
|
2
4
|
from .backends.backend import CacheBackend
|
|
3
5
|
|
|
6
|
+
__version__ = version("fastapi-cachekit")
|
|
7
|
+
|
|
4
8
|
from .backends.redis import RedisBackend
|
|
5
9
|
from .backends.memory import InMemoryBackend
|
|
6
10
|
from .backends.postgres import PostgresBackend
|
|
@@ -19,7 +23,7 @@ __all__ = [
|
|
|
19
23
|
"MemcachedBackend",
|
|
20
24
|
"MongoDBBackend",
|
|
21
25
|
"FirestoreBackend",
|
|
22
|
-
"DynamoDBBackend"
|
|
26
|
+
"DynamoDBBackend",
|
|
23
27
|
]
|
|
24
28
|
|
|
25
29
|
|
|
@@ -12,28 +12,30 @@ class CacheBackend(ABC):
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
@abstractmethod
|
|
15
|
-
async def aget(self, key: str) ->
|
|
15
|
+
async def aget(self, key: str, default: Any = None) -> Any:
|
|
16
16
|
"""
|
|
17
17
|
Asynchronously retrieve a value from the cache.
|
|
18
18
|
|
|
19
19
|
Args:
|
|
20
20
|
key (str): The key to retrieve.
|
|
21
|
+
default (Any): Value to return if key is not found. Defaults to None.
|
|
21
22
|
|
|
22
23
|
Returns:
|
|
23
|
-
|
|
24
|
+
Any: The cached value, or default if not found.
|
|
24
25
|
"""
|
|
25
26
|
pass
|
|
26
27
|
|
|
27
28
|
@abstractmethod
|
|
28
|
-
def get(self, key: str) ->
|
|
29
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
29
30
|
"""
|
|
30
31
|
Synchronously retrieve a value from the cache.
|
|
31
32
|
|
|
32
33
|
Args:
|
|
33
34
|
key (str): The key to retrieve.
|
|
35
|
+
default (Any): Value to return if key is not found. Defaults to None.
|
|
34
36
|
|
|
35
37
|
Returns:
|
|
36
|
-
|
|
38
|
+
Any: The cached value, or default if not found.
|
|
37
39
|
"""
|
|
38
40
|
pass
|
|
39
41
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import hashlib
|
|
3
|
+
import logging
|
|
2
4
|
from typing import Any, Optional, Union
|
|
3
5
|
from datetime import timedelta
|
|
4
6
|
import pickle
|
|
@@ -6,6 +8,8 @@ import time
|
|
|
6
8
|
|
|
7
9
|
from .backend import CacheBackend
|
|
8
10
|
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
9
13
|
|
|
10
14
|
class DynamoDBBackend(CacheBackend):
|
|
11
15
|
"""
|
|
@@ -79,24 +83,32 @@ class DynamoDBBackend(CacheBackend):
|
|
|
79
83
|
self._async_resource = None
|
|
80
84
|
self._async_table = None
|
|
81
85
|
self._async_session = aioboto3.Session()
|
|
86
|
+
self._async_table_lock = asyncio.Lock()
|
|
82
87
|
|
|
83
88
|
# Create table if requested
|
|
84
89
|
if create_table:
|
|
85
90
|
self._ensure_table_exists()
|
|
86
91
|
|
|
87
92
|
async def _get_async_table(self):
|
|
88
|
-
if self._async_table is None:
|
|
89
|
-
|
|
93
|
+
if self._async_table is not None:
|
|
94
|
+
return self._async_table
|
|
95
|
+
async with self._async_table_lock:
|
|
96
|
+
if self._async_table is not None:
|
|
97
|
+
return self._async_table
|
|
98
|
+
# Create the resource context manager
|
|
90
99
|
self._async_resource = self._async_session.resource(
|
|
91
100
|
"dynamodb", **self._connection_params
|
|
92
101
|
)
|
|
93
102
|
|
|
94
|
-
# Enter the context and get the actual resource
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
103
|
+
# Enter the context and get the actual resource,
|
|
104
|
+
# ensuring cleanup on failure to prevent leaks
|
|
105
|
+
try:
|
|
106
|
+
actual_resource = await self._async_resource.__aenter__()
|
|
107
|
+
self._async_table = await actual_resource.Table(self._table_name)
|
|
108
|
+
except BaseException:
|
|
109
|
+
await self._async_resource.__aexit__(None, None, None)
|
|
110
|
+
self._async_resource = None
|
|
111
|
+
raise
|
|
100
112
|
|
|
101
113
|
return self._async_table
|
|
102
114
|
|
|
@@ -234,60 +246,64 @@ class DynamoDBBackend(CacheBackend):
|
|
|
234
246
|
|
|
235
247
|
return item
|
|
236
248
|
|
|
237
|
-
def get(self, key: str) ->
|
|
249
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
238
250
|
"""
|
|
239
251
|
Synchronously retrieve a value from the cache.
|
|
240
252
|
|
|
241
253
|
Args:
|
|
242
254
|
key (str): The key to retrieve.
|
|
255
|
+
default (Any): Value to return if key is not found. Defaults to None.
|
|
243
256
|
|
|
244
257
|
Returns:
|
|
245
|
-
|
|
258
|
+
Any: The cached value, or default if not found.
|
|
246
259
|
"""
|
|
247
260
|
try:
|
|
248
261
|
response = self._sync_table.get_item(Key={"cache_key": self._make_key(key)})
|
|
249
262
|
|
|
250
263
|
if "Item" not in response:
|
|
251
|
-
return
|
|
264
|
+
return default
|
|
252
265
|
|
|
253
266
|
item = response["Item"]
|
|
254
267
|
|
|
255
268
|
# Check if item has expired and delete if so
|
|
256
269
|
if self._is_expired(item):
|
|
257
270
|
self.delete(key)
|
|
258
|
-
return
|
|
271
|
+
return default
|
|
259
272
|
value = self._deserialize_value(item["value"])
|
|
260
273
|
return value
|
|
261
|
-
except Exception:
|
|
262
|
-
|
|
274
|
+
except Exception as e:
|
|
275
|
+
logger.warning("Cache get failed: %s", e)
|
|
276
|
+
return default
|
|
263
277
|
|
|
264
|
-
async def aget(self, key: str) ->
|
|
278
|
+
async def aget(self, key: str, default: Any = None) -> Any:
|
|
265
279
|
"""
|
|
266
280
|
Asynchronously retrieve a value from the cache.
|
|
267
281
|
|
|
268
282
|
Args:
|
|
269
283
|
key (str): The key to retrieve.
|
|
284
|
+
default (Any): Value to return if key is not found. Defaults to None.
|
|
270
285
|
|
|
271
286
|
Returns:
|
|
272
|
-
|
|
287
|
+
Any: The cached value, or default if not found.
|
|
273
288
|
"""
|
|
274
289
|
try:
|
|
275
290
|
table = await self._get_async_table()
|
|
276
291
|
response = await table.get_item(Key={"cache_key": self._make_key(key)})
|
|
277
292
|
|
|
278
293
|
if "Item" not in response:
|
|
279
|
-
return
|
|
294
|
+
return default
|
|
280
295
|
|
|
281
296
|
item = response["Item"]
|
|
282
297
|
|
|
283
298
|
# Check if item has expired and delete if so
|
|
284
299
|
if self._is_expired(item):
|
|
285
300
|
await self.adelete(key)
|
|
286
|
-
return
|
|
301
|
+
return default
|
|
287
302
|
|
|
288
303
|
return self._deserialize_value(item["value"])
|
|
289
|
-
except Exception:
|
|
290
|
-
|
|
304
|
+
except Exception as e:
|
|
305
|
+
logger.warning("Cache aget failed: %s", e)
|
|
306
|
+
return default
|
|
291
307
|
|
|
292
308
|
def set(
|
|
293
309
|
self, key: str, value: Any, expire: Optional[Union[int, timedelta]] = None
|
|
@@ -303,8 +319,8 @@ class DynamoDBBackend(CacheBackend):
|
|
|
303
319
|
try:
|
|
304
320
|
item = self._build_item(key, value, expire)
|
|
305
321
|
self._sync_table.put_item(Item=item)
|
|
306
|
-
except Exception:
|
|
307
|
-
|
|
322
|
+
except Exception as e:
|
|
323
|
+
logger.warning("Cache set failed: %s", e)
|
|
308
324
|
|
|
309
325
|
async def aset(
|
|
310
326
|
self, key: str, value: Any, expire: Optional[Union[int, timedelta]] = None
|
|
@@ -321,8 +337,8 @@ class DynamoDBBackend(CacheBackend):
|
|
|
321
337
|
table = await self._get_async_table()
|
|
322
338
|
item = self._build_item(key, value, expire)
|
|
323
339
|
await table.put_item(Item=item)
|
|
324
|
-
except Exception:
|
|
325
|
-
|
|
340
|
+
except Exception as e:
|
|
341
|
+
logger.warning("Cache aset failed: %s", e)
|
|
326
342
|
|
|
327
343
|
def delete(self, key: str) -> None:
|
|
328
344
|
"""
|
|
@@ -333,8 +349,8 @@ class DynamoDBBackend(CacheBackend):
|
|
|
333
349
|
"""
|
|
334
350
|
try:
|
|
335
351
|
self._sync_table.delete_item(Key={"cache_key": self._make_key(key)})
|
|
336
|
-
except Exception:
|
|
337
|
-
|
|
352
|
+
except Exception as e:
|
|
353
|
+
logger.warning("Cache delete failed: %s", e)
|
|
338
354
|
|
|
339
355
|
async def adelete(self, key: str) -> None:
|
|
340
356
|
"""
|
|
@@ -346,8 +362,8 @@ class DynamoDBBackend(CacheBackend):
|
|
|
346
362
|
try:
|
|
347
363
|
table = await self._get_async_table()
|
|
348
364
|
await table.delete_item(Key={"cache_key": self._make_key(key)})
|
|
349
|
-
except Exception:
|
|
350
|
-
|
|
365
|
+
except Exception as e:
|
|
366
|
+
logger.warning("Cache adelete failed: %s", e)
|
|
351
367
|
|
|
352
368
|
def has(self, key: str) -> bool:
|
|
353
369
|
"""
|
|
@@ -366,7 +382,6 @@ class DynamoDBBackend(CacheBackend):
|
|
|
366
382
|
ExpressionAttributeNames={"#ttl": "ttl"},
|
|
367
383
|
)
|
|
368
384
|
|
|
369
|
-
|
|
370
385
|
if "Item" not in response:
|
|
371
386
|
return False
|
|
372
387
|
|
|
@@ -378,7 +393,8 @@ class DynamoDBBackend(CacheBackend):
|
|
|
378
393
|
return False
|
|
379
394
|
|
|
380
395
|
return True
|
|
381
|
-
except Exception:
|
|
396
|
+
except Exception as e:
|
|
397
|
+
logger.warning("Cache has failed: %s", e)
|
|
382
398
|
return False
|
|
383
399
|
|
|
384
400
|
async def ahas(self, key: str) -> bool:
|
|
@@ -410,7 +426,8 @@ class DynamoDBBackend(CacheBackend):
|
|
|
410
426
|
return False
|
|
411
427
|
|
|
412
428
|
return True
|
|
413
|
-
except Exception:
|
|
429
|
+
except Exception as e:
|
|
430
|
+
logger.warning("Cache ahas failed: %s", e)
|
|
414
431
|
return False
|
|
415
432
|
|
|
416
433
|
def clear(self) -> None:
|
|
@@ -445,8 +462,8 @@ class DynamoDBBackend(CacheBackend):
|
|
|
445
462
|
for item in response["Items"]:
|
|
446
463
|
batch.delete_item(Key={"cache_key": item["cache_key"]})
|
|
447
464
|
|
|
448
|
-
except Exception:
|
|
449
|
-
|
|
465
|
+
except Exception as e:
|
|
466
|
+
logger.warning("Cache clear failed: %s", e)
|
|
450
467
|
|
|
451
468
|
async def aclear(self) -> None:
|
|
452
469
|
"""
|
|
@@ -480,10 +497,12 @@ class DynamoDBBackend(CacheBackend):
|
|
|
480
497
|
if response.get("Items"):
|
|
481
498
|
async with table.batch_writer() as batch:
|
|
482
499
|
for item in response["Items"]:
|
|
483
|
-
await batch.delete_item(
|
|
500
|
+
await batch.delete_item(
|
|
501
|
+
Key={"cache_key": item["cache_key"]}
|
|
502
|
+
)
|
|
484
503
|
|
|
485
|
-
except Exception:
|
|
486
|
-
|
|
504
|
+
except Exception as e:
|
|
505
|
+
logger.warning("Cache aclear failed: %s", e)
|
|
487
506
|
|
|
488
507
|
async def close(self) -> None:
|
|
489
508
|
"""
|