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.
Files changed (25) hide show
  1. {fastapi_cachekit-0.1.4/fastapi_cachekit.egg-info → fastapi_cachekit-0.2.0}/PKG-INFO +5 -2
  2. {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fast_cache/__init__.py +5 -1
  3. {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fast_cache/backends/backend.py +6 -4
  4. {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fast_cache/backends/dynamodb.py +55 -36
  5. fastapi_cachekit-0.2.0/fast_cache/backends/google_firestore.py +506 -0
  6. fastapi_cachekit-0.2.0/fast_cache/backends/memcached.py +331 -0
  7. fastapi_cachekit-0.2.0/fast_cache/backends/memory.py +462 -0
  8. {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fast_cache/backends/mongodb.py +10 -8
  9. fastapi_cachekit-0.2.0/fast_cache/backends/postgres.py +555 -0
  10. {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fast_cache/backends/redis.py +162 -25
  11. {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fast_cache/integration.py +86 -11
  12. {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0/fastapi_cachekit.egg-info}/PKG-INFO +5 -2
  13. {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fastapi_cachekit.egg-info/requires.txt +3 -1
  14. {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/pyproject.toml +24 -3
  15. fastapi_cachekit-0.1.4/fast_cache/backends/google_firestore.py +0 -351
  16. fastapi_cachekit-0.1.4/fast_cache/backends/memcached.py +0 -136
  17. fastapi_cachekit-0.1.4/fast_cache/backends/memory.py +0 -318
  18. fastapi_cachekit-0.1.4/fast_cache/backends/postgres.py +0 -274
  19. {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/LICENSE.md +0 -0
  20. {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/README.md +0 -0
  21. {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fast_cache/backends/__init__.py +0 -0
  22. {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fastapi_cachekit.egg-info/SOURCES.txt +0 -0
  23. {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fastapi_cachekit.egg-info/dependency_links.txt +0 -0
  24. {fastapi_cachekit-0.1.4 → fastapi_cachekit-0.2.0}/fastapi_cachekit.egg-info/top_level.txt +0 -0
  25. {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.1.4
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,srv]>=4.6.0; extra == "mongodb"
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) -> Optional[Any]:
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
- Optional[Any]: The cached value, or None if not found.
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) -> Optional[Any]:
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
- Optional[Any]: The cached value, or None if not found.
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
- # Create the resource context
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
- actual_resource = await self._async_resource.__aenter__()
96
-
97
- # Create the table from the actual resource
98
- self._async_table = await actual_resource.Table(self._table_name)
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) -> Optional[Any]:
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
- Optional[Any]: The cached value, or None if not found.
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 None
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 None
271
+ return default
259
272
  value = self._deserialize_value(item["value"])
260
273
  return value
261
- except Exception:
262
- return None
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) -> Optional[Any]:
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
- Optional[Any]: The cached value, or None if not found.
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 None
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 None
301
+ return default
287
302
 
288
303
  return self._deserialize_value(item["value"])
289
- except Exception:
290
- return None
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
- pass
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
- pass
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
- pass
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
- pass
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
- pass
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(Key={"cache_key": item["cache_key"]})
500
+ await batch.delete_item(
501
+ Key={"cache_key": item["cache_key"]}
502
+ )
484
503
 
485
- except Exception:
486
- pass
504
+ except Exception as e:
505
+ logger.warning("Cache aclear failed: %s", e)
487
506
 
488
507
  async def close(self) -> None:
489
508
  """