fastapi-cachex 0.1.5__py3-none-any.whl → 0.1.6__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.

Potentially problematic release.


This version of fastapi-cachex might be problematic. Click here for more details.

@@ -1,3 +1,11 @@
1
- from .base import BaseCacheBackend as BaseCacheBackend
2
- from .memcached import MemcachedBackend as MemcachedBackend
3
- from .memory import MemoryBackend as MemoryBackend
1
+ from fastapi_cachex.backends.base import BaseCacheBackend
2
+ from fastapi_cachex.backends.memcached import MemcachedBackend
3
+ from fastapi_cachex.backends.memory import MemoryBackend
4
+ from fastapi_cachex.backends.redis import AsyncRedisCacheBackend
5
+
6
+ __all__ = [
7
+ "AsyncRedisCacheBackend",
8
+ "BaseCacheBackend",
9
+ "MemcachedBackend",
10
+ "MemoryBackend",
11
+ ]
@@ -0,0 +1,137 @@
1
+ from typing import TYPE_CHECKING
2
+ from typing import Any
3
+ from typing import Literal
4
+ from typing import Optional
5
+
6
+ from fastapi_cachex.backends.base import BaseCacheBackend
7
+ from fastapi_cachex.exceptions import CacheXError
8
+ from fastapi_cachex.types import ETagContent
9
+
10
+ if TYPE_CHECKING:
11
+ from redis.asyncio import Redis as AsyncRedis
12
+
13
+ try:
14
+ import orjson as json
15
+
16
+ except ImportError: # pragma: no cover
17
+ import json # type: ignore[no-redef]
18
+
19
+
20
+ class AsyncRedisCacheBackend(BaseCacheBackend):
21
+ """Async Redis cache backend implementation."""
22
+
23
+ client: "AsyncRedis[str]"
24
+
25
+ def __init__(
26
+ self,
27
+ host: str = "127.0.0.1",
28
+ port: int = 6379,
29
+ password: Optional[str] = None,
30
+ db: int = 0,
31
+ encoding: str = "utf-8",
32
+ decode_responses: Literal[True] = True,
33
+ socket_timeout: float = 1.0,
34
+ socket_connect_timeout: float = 1.0,
35
+ **kwargs: Any,
36
+ ) -> None:
37
+ """Initialize async Redis cache backend.
38
+
39
+ Args:
40
+ host: Redis host
41
+ port: Redis port
42
+ password: Redis password
43
+ db: Redis database number
44
+ encoding: Character encoding to use
45
+ decode_responses: Whether to decode response automatically
46
+ socket_timeout: Timeout for socket operations (in seconds)
47
+ socket_connect_timeout: Timeout for socket connection (in seconds)
48
+ **kwargs: Additional arguments to pass to Redis client
49
+ """
50
+ try:
51
+ from redis.asyncio import Redis as AsyncRedis
52
+ except ImportError:
53
+ raise CacheXError(
54
+ "redis[hiredis] is not installed. Please install it with 'pip install \"redis[hiredis]\"'"
55
+ )
56
+
57
+ self.client = AsyncRedis(
58
+ host=host,
59
+ port=port,
60
+ password=password,
61
+ db=db,
62
+ encoding=encoding,
63
+ decode_responses=decode_responses,
64
+ socket_timeout=socket_timeout,
65
+ socket_connect_timeout=socket_connect_timeout,
66
+ **kwargs,
67
+ )
68
+
69
+ def _serialize(self, value: ETagContent) -> str:
70
+ """Serialize ETagContent to JSON string."""
71
+ serialized = json.dumps(
72
+ {
73
+ "etag": value.etag,
74
+ "content": value.content.decode()
75
+ if isinstance(value.content, bytes)
76
+ else value.content,
77
+ }
78
+ )
79
+
80
+ if isinstance(serialized, bytes):
81
+ # If using orjson, it returns bytes
82
+ return serialized.decode()
83
+
84
+ return serialized # type: ignore[unreachable]
85
+
86
+ def _deserialize(self, value: Optional[str]) -> Optional[ETagContent]:
87
+ """Deserialize JSON string to ETagContent."""
88
+ if value is None:
89
+ return None
90
+ try:
91
+ data = json.loads(value)
92
+ return ETagContent(
93
+ etag=data["etag"],
94
+ content=data["content"].encode()
95
+ if isinstance(data["content"], str)
96
+ else data["content"],
97
+ )
98
+ except (json.JSONDecodeError, KeyError):
99
+ return None
100
+
101
+ async def get(self, key: str) -> Optional[ETagContent]:
102
+ """Retrieve a cached response."""
103
+ result = await self.client.get(key)
104
+ return self._deserialize(result)
105
+
106
+ async def set(
107
+ self, key: str, value: ETagContent, ttl: Optional[int] = None
108
+ ) -> None:
109
+ """Store a response in the cache."""
110
+ serialized = self._serialize(value)
111
+ if ttl is not None:
112
+ await self.client.setex(key, ttl, serialized)
113
+ else:
114
+ await self.client.set(key, serialized)
115
+
116
+ async def delete(self, key: str) -> None:
117
+ """Remove a response from the cache."""
118
+ await self.client.delete(key)
119
+
120
+ async def clear(self) -> None:
121
+ """Clear all cached responses."""
122
+ await self.client.flushdb()
123
+
124
+ async def clear_path(self, path: str, include_params: bool = False) -> int:
125
+ """Clear cached responses for a specific path."""
126
+ pattern = f"{path}*" if include_params else path
127
+ keys = await self.client.keys(pattern)
128
+ if keys:
129
+ return await self.client.delete(*keys)
130
+ return 0
131
+
132
+ async def clear_pattern(self, pattern: str) -> int:
133
+ """Clear cached responses matching a pattern."""
134
+ keys = await self.client.keys(pattern)
135
+ if keys:
136
+ return await self.client.delete(*keys)
137
+ return 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-cachex
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: A caching library for FastAPI with support for Cache-Control, ETag, and multiple backends.
5
5
  Author-email: Allen <s96016641@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -24,9 +24,11 @@ Requires-Python: >=3.10
24
24
  Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
26
  Requires-Dist: fastapi
27
- Requires-Dist: httpx
28
27
  Provides-Extra: memcache
29
28
  Requires-Dist: pymemcache; extra == "memcache"
29
+ Provides-Extra: redis
30
+ Requires-Dist: redis[hiredis]; extra == "redis"
31
+ Requires-Dist: orjson; extra == "redis"
30
32
  Dynamic: license-file
31
33
 
32
34
  # FastAPI-Cache X
@@ -34,7 +36,7 @@ Dynamic: license-file
34
36
  [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
35
37
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
36
38
  [![Tests](https://github.com/allen0099/FastAPI-CacheX/actions/workflows/test.yml/badge.svg)](https://github.com/allen0099/FastAPI-CacheX/actions/workflows/test.yml)
37
- [![Coverage Status](https://raw.githubusercontent.com/allen0099/FastAPI-CacheX/coverage-badge/coverage.svg)](https://github.com/allen0099/FastAPI-CacheX/actions/workflows/test.yml)
39
+ [![Coverage Status](https://raw.githubusercontent.com/allen0099/FastAPI-CacheX/coverage-badge/coverage.svg)](https://github.com/allen0099/FastAPI-CacheX/actions/workflows/coverage.yml)
38
40
 
39
41
  [![Downloads](https://static.pepy.tech/badge/fastapi-cachex)](https://pepy.tech/project/fastapi-cachex)
40
42
  [![Weekly downloads](https://static.pepy.tech/badge/fastapi-cachex/week)](https://pepy.tech/project/fastapi-cachex)
@@ -60,6 +62,24 @@ A high-performance caching extension for FastAPI, providing comprehensive HTTP c
60
62
  - Complete Cache-Control directive implementation
61
63
  - Easy-to-use `@cache` decorator
62
64
 
65
+ ### Cache-Control Directives
66
+
67
+ | Directive | Supported | Description |
68
+ |--------------------------|--------------------|---------------------------------------------------------------------------------------------------------|
69
+ | `max-age` | :white_check_mark: | Specifies the maximum amount of time a resource is considered fresh. |
70
+ | `s-maxage` | :x: | Specifies the maximum amount of time a resource is considered fresh for shared caches. |
71
+ | `no-cache` | :white_check_mark: | Forces caches to submit the request to the origin server for validation before releasing a cached copy. |
72
+ | `no-store` | :white_check_mark: | Instructs caches not to store any part of the request or response. |
73
+ | `no-transform` | :x: | Instructs caches not to transform the response content. |
74
+ | `must-revalidate` | :white_check_mark: | Forces caches to revalidate the response with the origin server after it becomes stale. |
75
+ | `proxy-revalidate` | :x: | Similar to `must-revalidate`, but only for shared caches. |
76
+ | `must-understand` | :x: | Indicates that the recipient must understand the directive or treat it as an error. |
77
+ | `private` | :white_check_mark: | Indicates that the response is intended for a single user and should not be stored by shared caches. |
78
+ | `public` | :white_check_mark: | Indicates that the response may be cached by any cache, even if it is normally non-cacheable. |
79
+ | `immutable` | :white_check_mark: | Indicates that the response body will not change over time, allowing for longer caching. |
80
+ | `stale-while-revalidate` | :white_check_mark: | Indicates that a cache can serve a stale response while it revalidates the response in the background. |
81
+ | `stale-if-error` | :white_check_mark: | Indicates that a cache can serve a stale response if the origin server is unavailable. |
82
+
63
83
  ## Installation
64
84
 
65
85
  ### Using pip
@@ -78,31 +98,44 @@ uv pip install fastapi-cachex
78
98
 
79
99
  ```python
80
100
  from fastapi import FastAPI
81
- from fastapi_cachex import cache, BackendProxy
82
- from fastapi_cachex.backends import MemoryBackend, MemcachedBackend
101
+ from fastapi_cachex import cache
102
+ from fastapi_cachex import CacheBackend
83
103
 
84
104
  app = FastAPI()
85
105
 
86
- # Configure your cache backend
87
- memory_backend = MemoryBackend() # In-memory cache
88
- # or
89
- memcached_backend = MemcachedBackend(servers=["localhost:11211"]) # Memcached
90
-
91
- # Set the backend you want to use
92
- BackendProxy.set_backend(memory_backend) # or memcached_backend
93
-
94
106
 
95
107
  @app.get("/")
96
108
  @cache(ttl=60) # Cache for 60 seconds
97
109
  async def read_root():
98
110
  return {"Hello": "World"}
111
+
112
+
113
+ @app.get("/no-cache")
114
+ @cache(no_cache=True) # Mark this endpoint as non-cacheable
115
+ async def non_cache_endpoint():
116
+ return {"Hello": "World"}
117
+
118
+
119
+ @app.get("/no-store")
120
+ @cache(no_store=True) # Mark this endpoint as non-cacheable
121
+ async def non_store_endpoint():
122
+ return {"Hello": "World"}
123
+
124
+
125
+ @app.get("/clear_cache")
126
+ async def remove_cache(cache: CacheBackend):
127
+ await cache.clear_path("/path/to/clear") # Clear cache for a specific path
128
+ await cache.clear_pattern("/path/to/clear/*") # Clear cache for a specific pattern
99
129
  ```
100
130
 
101
131
  ## Backend Configuration
102
132
 
103
133
  FastAPI-CacheX supports multiple caching backends. You can easily switch between them using the `BackendProxy`.
104
134
 
105
- ### In-Memory Cache
135
+ ### In-Memory Cache (default)
136
+
137
+ If you don't specify a backend, FastAPI-CacheX will use the in-memory cache by default.
138
+ This is suitable for development and testing purposes.
106
139
 
107
140
  ```python
108
141
  from fastapi_cachex.backends import MemoryBackend
@@ -122,9 +155,15 @@ backend = MemcachedBackend(servers=["localhost:11211"])
122
155
  BackendProxy.set_backend(backend)
123
156
  ```
124
157
 
125
- ### Redis (Coming Soon)
158
+ ### Redis
159
+
160
+ ```python
161
+ from fastapi_cachex.backends import AsyncRedisCacheBackend
162
+ from fastapi_cachex import BackendProxy
126
163
 
127
- Redis support is under development and will be available in future releases.
164
+ backend = AsyncRedisCacheBackend(host="127.0.1", port=6379, db=0)
165
+ BackendProxy.set_backend(backend)
166
+ ```
128
167
 
129
168
  ## Documentation
130
169
 
@@ -6,12 +6,13 @@ fastapi_cachex/exceptions.py,sha256=coYct4u6uK_pdjetUWDwM5OUCfhql0OkTECynMRUq4M,
6
6
  fastapi_cachex/proxy.py,sha256=vFShY7_xp4Sh1XU9dJzsBv2ICN8Rtwx6g1qCcCvmdf8,810
7
7
  fastapi_cachex/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  fastapi_cachex/types.py,sha256=YkXlBARIr5lHQE4PYQrwXjEoLHdz9CIjfX7V-S9N8p0,328
9
- fastapi_cachex/backends/__init__.py,sha256=U65JrCeh1eusklqUfV5yvZGK7Kfy5RctzfVrRfFPuaI,166
9
+ fastapi_cachex/backends/__init__.py,sha256=KJHzYs2NZLl-LDAz-VIynaDhZ8XCXKrcnVG-j3sYMRI,357
10
10
  fastapi_cachex/backends/base.py,sha256=oBoHUaejZNQ_ex1n1YrpUP4CU94w2TbsXd6qau0F_T8,1383
11
11
  fastapi_cachex/backends/memcached.py,sha256=vy7isgu2qW2odBPTl8Q8ulqUo36KIsnZ3YVCr3sflfc,3211
12
12
  fastapi_cachex/backends/memory.py,sha256=4TBdvxnjMY0BnN1Gjr93rJllWJjU055RxG12QJixrrM,3611
13
- fastapi_cachex-0.1.5.dist-info/licenses/LICENSE,sha256=asJkHbd10YDSnjeAOIlKafh7E_exwtKXY5rA-qc_Mno,11339
14
- fastapi_cachex-0.1.5.dist-info/METADATA,sha256=OoxnkzzbW8iLikvibtF0Oud0xEUPwZDx9XIGMVNqmLM,4669
15
- fastapi_cachex-0.1.5.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
16
- fastapi_cachex-0.1.5.dist-info/top_level.txt,sha256=97FfG5FDycd3hks-_JznEr-5lUOgg8AZd8pqK5imWj0,15
17
- fastapi_cachex-0.1.5.dist-info/RECORD,,
13
+ fastapi_cachex/backends/redis.py,sha256=bCfF0GHBaLXkfQiwciaAtZFD0fWCcv0wNihuDGzdtUI,4489
14
+ fastapi_cachex-0.1.6.dist-info/licenses/LICENSE,sha256=asJkHbd10YDSnjeAOIlKafh7E_exwtKXY5rA-qc_Mno,11339
15
+ fastapi_cachex-0.1.6.dist-info/METADATA,sha256=6f_-B8g8b6DoEWK-GXNoGjQ5cWplWw59wTw9AQyjqZE,7634
16
+ fastapi_cachex-0.1.6.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
17
+ fastapi_cachex-0.1.6.dist-info/top_level.txt,sha256=97FfG5FDycd3hks-_JznEr-5lUOgg8AZd8pqK5imWj0,15
18
+ fastapi_cachex-0.1.6.dist-info/RECORD,,