fastapi-cachex 0.1.4__tar.gz → 0.1.6__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.

Potentially problematic release.


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

Files changed (29) hide show
  1. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/PKG-INFO +55 -16
  2. fastapi_cachex-0.1.6/README.md +142 -0
  3. fastapi_cachex-0.1.6/fastapi_cachex/__init__.py +4 -0
  4. fastapi_cachex-0.1.6/fastapi_cachex/backends/__init__.py +11 -0
  5. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/fastapi_cachex/backends/base.py +23 -0
  6. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/fastapi_cachex/backends/memcached.py +28 -1
  7. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/fastapi_cachex/backends/memory.py +34 -0
  8. fastapi_cachex-0.1.6/fastapi_cachex/backends/redis.py +137 -0
  9. fastapi_cachex-0.1.6/fastapi_cachex/dependencies.py +14 -0
  10. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/fastapi_cachex.egg-info/PKG-INFO +55 -16
  11. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/fastapi_cachex.egg-info/SOURCES.txt +3 -0
  12. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/fastapi_cachex.egg-info/requires.txt +4 -1
  13. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/pyproject.toml +9 -4
  14. fastapi_cachex-0.1.6/tests/test_dependencies.py +38 -0
  15. fastapi_cachex-0.1.4/README.md +0 -105
  16. fastapi_cachex-0.1.4/fastapi_cachex/__init__.py +0 -2
  17. fastapi_cachex-0.1.4/fastapi_cachex/backends/__init__.py +0 -3
  18. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/LICENSE +0 -0
  19. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/fastapi_cachex/cache.py +0 -0
  20. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/fastapi_cachex/directives.py +0 -0
  21. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/fastapi_cachex/exceptions.py +0 -0
  22. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/fastapi_cachex/proxy.py +0 -0
  23. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/fastapi_cachex/py.typed +0 -0
  24. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/fastapi_cachex/types.py +0 -0
  25. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/fastapi_cachex.egg-info/dependency_links.txt +0 -0
  26. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/fastapi_cachex.egg-info/top_level.txt +0 -0
  27. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/setup.cfg +0 -0
  28. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/tests/test_cache.py +0 -0
  29. {fastapi_cachex-0.1.4 → fastapi_cachex-0.1.6}/tests/test_proxybackend.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-cachex
3
- Version: 0.1.4
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
 
@@ -0,0 +1,142 @@
1
+ # FastAPI-Cache X
2
+
3
+ [![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)
4
+ [![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)
5
+ [![Tests](https://github.com/allen0099/FastAPI-CacheX/actions/workflows/test.yml/badge.svg)](https://github.com/allen0099/FastAPI-CacheX/actions/workflows/test.yml)
6
+ [![Coverage Status](https://raw.githubusercontent.com/allen0099/FastAPI-CacheX/coverage-badge/coverage.svg)](https://github.com/allen0099/FastAPI-CacheX/actions/workflows/coverage.yml)
7
+
8
+ [![Downloads](https://static.pepy.tech/badge/fastapi-cachex)](https://pepy.tech/project/fastapi-cachex)
9
+ [![Weekly downloads](https://static.pepy.tech/badge/fastapi-cachex/week)](https://pepy.tech/project/fastapi-cachex)
10
+ [![Monthly downloads](https://static.pepy.tech/badge/fastapi-cachex/month)](https://pepy.tech/project/fastapi-cachex)
11
+
12
+ [![PyPI version](https://img.shields.io/pypi/v/fastapi-cachex.svg?logo=pypi&logoColor=gold&label=PyPI)](https://pypi.org/project/fastapi-cachex)
13
+ [![Python Versions](https://img.shields.io/pypi/pyversions/fastapi-cachex.svg?logo=python&label=Python&logoColor=gold)](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.
18
+
19
+ ## Features
20
+
21
+ - Support for HTTP caching headers
22
+ - `Cache-Control`
23
+ - `ETag`
24
+ - `If-None-Match`
25
+ - Multiple backend cache support
26
+ - Redis
27
+ - Memcached
28
+ - In-memory cache
29
+ - Complete Cache-Control directive implementation
30
+ - Easy-to-use `@cache` decorator
31
+
32
+ ### Cache-Control Directives
33
+
34
+ | Directive | Supported | Description |
35
+ |--------------------------|--------------------|---------------------------------------------------------------------------------------------------------|
36
+ | `max-age` | :white_check_mark: | Specifies the maximum amount of time a resource is considered fresh. |
37
+ | `s-maxage` | :x: | Specifies the maximum amount of time a resource is considered fresh for shared caches. |
38
+ | `no-cache` | :white_check_mark: | Forces caches to submit the request to the origin server for validation before releasing a cached copy. |
39
+ | `no-store` | :white_check_mark: | Instructs caches not to store any part of the request or response. |
40
+ | `no-transform` | :x: | Instructs caches not to transform the response content. |
41
+ | `must-revalidate` | :white_check_mark: | Forces caches to revalidate the response with the origin server after it becomes stale. |
42
+ | `proxy-revalidate` | :x: | Similar to `must-revalidate`, but only for shared caches. |
43
+ | `must-understand` | :x: | Indicates that the recipient must understand the directive or treat it as an error. |
44
+ | `private` | :white_check_mark: | Indicates that the response is intended for a single user and should not be stored by shared caches. |
45
+ | `public` | :white_check_mark: | Indicates that the response may be cached by any cache, even if it is normally non-cacheable. |
46
+ | `immutable` | :white_check_mark: | Indicates that the response body will not change over time, allowing for longer caching. |
47
+ | `stale-while-revalidate` | :white_check_mark: | Indicates that a cache can serve a stale response while it revalidates the response in the background. |
48
+ | `stale-if-error` | :white_check_mark: | Indicates that a cache can serve a stale response if the origin server is unavailable. |
49
+
50
+ ## Installation
51
+
52
+ ### Using pip
53
+
54
+ ```bash
55
+ pip install fastapi-cachex
56
+ ```
57
+
58
+ ### Using uv (recommended)
59
+
60
+ ```bash
61
+ uv pip install fastapi-cachex
62
+ ```
63
+
64
+ ## Quick Start
65
+
66
+ ```python
67
+ from fastapi import FastAPI
68
+ from fastapi_cachex import cache
69
+ from fastapi_cachex import CacheBackend
70
+
71
+ app = FastAPI()
72
+
73
+
74
+ @app.get("/")
75
+ @cache(ttl=60) # Cache for 60 seconds
76
+ async def read_root():
77
+ return {"Hello": "World"}
78
+
79
+
80
+ @app.get("/no-cache")
81
+ @cache(no_cache=True) # Mark this endpoint as non-cacheable
82
+ async def non_cache_endpoint():
83
+ return {"Hello": "World"}
84
+
85
+
86
+ @app.get("/no-store")
87
+ @cache(no_store=True) # Mark this endpoint as non-cacheable
88
+ async def non_store_endpoint():
89
+ return {"Hello": "World"}
90
+
91
+
92
+ @app.get("/clear_cache")
93
+ async def remove_cache(cache: CacheBackend):
94
+ await cache.clear_path("/path/to/clear") # Clear cache for a specific path
95
+ await cache.clear_pattern("/path/to/clear/*") # Clear cache for a specific pattern
96
+ ```
97
+
98
+ ## Backend Configuration
99
+
100
+ FastAPI-CacheX supports multiple caching backends. You can easily switch between them using the `BackendProxy`.
101
+
102
+ ### In-Memory Cache (default)
103
+
104
+ If you don't specify a backend, FastAPI-CacheX will use the in-memory cache by default.
105
+ This is suitable for development and testing purposes.
106
+
107
+ ```python
108
+ from fastapi_cachex.backends import MemoryBackend
109
+ from fastapi_cachex import BackendProxy
110
+
111
+ backend = MemoryBackend()
112
+ BackendProxy.set_backend(backend)
113
+ ```
114
+
115
+ ### Memcached
116
+
117
+ ```python
118
+ from fastapi_cachex.backends import MemcachedBackend
119
+ from fastapi_cachex import BackendProxy
120
+
121
+ backend = MemcachedBackend(servers=["localhost:11211"])
122
+ BackendProxy.set_backend(backend)
123
+ ```
124
+
125
+ ### Redis
126
+
127
+ ```python
128
+ from fastapi_cachex.backends import AsyncRedisCacheBackend
129
+ from fastapi_cachex import BackendProxy
130
+
131
+ backend = AsyncRedisCacheBackend(host="127.0.1", port=6379, db=0)
132
+ BackendProxy.set_backend(backend)
133
+ ```
134
+
135
+ ## Documentation
136
+
137
+ - [Development Guide](docs/DEVELOPMENT.md)
138
+ - [Contributing Guidelines](docs/CONTRIBUTING.md)
139
+
140
+ ## License
141
+
142
+ This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,4 @@
1
+ from .cache import cache as cache
2
+ from .dependencies import CacheBackend as CacheBackend
3
+ from .dependencies import get_cache_backend as get_cache_backend
4
+ from .proxy import BackendProxy as BackendProxy
@@ -0,0 +1,11 @@
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
+ ]
@@ -25,3 +25,26 @@ class BaseCacheBackend(ABC):
25
25
  @abstractmethod
26
26
  async def clear(self) -> None:
27
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
+ """
@@ -1,4 +1,5 @@
1
1
  import ast
2
+ import warnings
2
3
  from typing import Optional
3
4
 
4
5
  from fastapi_cachex.backends.base import BaseCacheBackend
@@ -25,7 +26,7 @@ class MemcachedBackend(BaseCacheBackend):
25
26
  "pymemcache is not installed. Please install it with 'pip install pymemcache'"
26
27
  )
27
28
 
28
- self.client = HashClient(servers)
29
+ self.client = HashClient(servers, connect_timeout=5, timeout=5)
29
30
 
30
31
  async def get(self, key: str) -> Optional[ETagContent]:
31
32
  """Get value from cache.
@@ -72,3 +73,29 @@ class MemcachedBackend(BaseCacheBackend):
72
73
  async def clear(self) -> None:
73
74
  """Clear all values from cache."""
74
75
  self.client.flush_all()
76
+
77
+ async def clear_path(self, path: str, include_params: bool = False) -> int:
78
+ """Clear cached responses for a specific path."""
79
+ if include_params:
80
+ warnings.warn(
81
+ "Memcached backend does not support pattern-based key clearing. "
82
+ "The include_params option will have no effect.",
83
+ RuntimeWarning,
84
+ stacklevel=2,
85
+ )
86
+ return 0
87
+
88
+ # If we're not including params, we can just try to delete the exact path
89
+ if self.client.delete(path, noreply=False):
90
+ return 1
91
+ return 0
92
+
93
+ async def clear_pattern(self, pattern: str) -> int: # noqa: ARG002
94
+ """Clear cached responses matching a pattern."""
95
+ warnings.warn(
96
+ "Memcached backend does not support pattern matching. "
97
+ "Pattern-based cache clearing is not available.",
98
+ RuntimeWarning,
99
+ stacklevel=2,
100
+ )
101
+ return 0
@@ -53,6 +53,40 @@ class MemoryBackend(BaseCacheBackend):
53
53
  async with self.lock:
54
54
  self.cache.clear()
55
55
 
56
+ async def clear_path(self, path: str, include_params: bool = False) -> int:
57
+ """Clear cached responses for a specific path."""
58
+ cleared_count = 0
59
+ async with self.lock:
60
+ keys_to_delete = []
61
+ for key in self.cache:
62
+ cache_path, *params = key.split(":", 1)
63
+ if cache_path == path and (include_params or not params):
64
+ keys_to_delete.append(key)
65
+ cleared_count += 1
66
+
67
+ for key in keys_to_delete:
68
+ del self.cache[key]
69
+
70
+ return cleared_count
71
+
72
+ async def clear_pattern(self, pattern: str) -> int:
73
+ """Clear cached responses matching a pattern."""
74
+ import fnmatch
75
+
76
+ cleared_count = 0
77
+ async with self.lock:
78
+ keys_to_delete = []
79
+ for key in self.cache:
80
+ cache_path = key.split(":", 1)[0] # Get path part only
81
+ if fnmatch.fnmatch(cache_path, pattern):
82
+ keys_to_delete.append(key)
83
+ cleared_count += 1
84
+
85
+ for key in keys_to_delete:
86
+ del self.cache[key]
87
+
88
+ return cleared_count
89
+
56
90
  async def _cleanup_task_impl(self) -> None:
57
91
  try:
58
92
  while True:
@@ -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
@@ -0,0 +1,14 @@
1
+ from typing import Annotated
2
+
3
+ from fastapi import Depends
4
+
5
+ from fastapi_cachex.backends.base import BaseCacheBackend
6
+ from fastapi_cachex.proxy import BackendProxy
7
+
8
+
9
+ def get_cache_backend() -> BaseCacheBackend:
10
+ """Dependency to get the current cache backend instance."""
11
+ return BackendProxy.get_backend()
12
+
13
+
14
+ CacheBackend = Annotated[BaseCacheBackend, Depends(get_cache_backend)]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-cachex
3
- Version: 0.1.4
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
 
@@ -3,6 +3,7 @@ README.md
3
3
  pyproject.toml
4
4
  fastapi_cachex/__init__.py
5
5
  fastapi_cachex/cache.py
6
+ fastapi_cachex/dependencies.py
6
7
  fastapi_cachex/directives.py
7
8
  fastapi_cachex/exceptions.py
8
9
  fastapi_cachex/proxy.py
@@ -17,5 +18,7 @@ fastapi_cachex/backends/__init__.py
17
18
  fastapi_cachex/backends/base.py
18
19
  fastapi_cachex/backends/memcached.py
19
20
  fastapi_cachex/backends/memory.py
21
+ fastapi_cachex/backends/redis.py
20
22
  tests/test_cache.py
23
+ tests/test_dependencies.py
21
24
  tests/test_proxybackend.py
@@ -1,5 +1,8 @@
1
1
  fastapi
2
- httpx
3
2
 
4
3
  [memcache]
5
4
  pymemcache
5
+
6
+ [redis]
7
+ redis[hiredis]
8
+ orjson
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fastapi-cachex"
3
- version = "0.1.4" # Initial release version
3
+ version = "0.1.6" # Initial release version
4
4
  description = "A caching library for FastAPI with support for Cache-Control, ETag, and multiple backends."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -25,7 +25,6 @@ classifiers = [
25
25
  keywords = ["fastapi", "cache", "etag", "cache-control", "redis", "memcached", "in-memory"]
26
26
  dependencies = [
27
27
  "fastapi",
28
- "httpx",
29
28
  ]
30
29
 
31
30
  [project.urls]
@@ -36,25 +35,31 @@ Issues = "https://github.com/allen0099/FastAPI-CacheX/issues"
36
35
  [dependency-groups]
37
36
  dev = [
38
37
  "coverage>=7.8.0",
38
+ "httpx>=0.28.1",
39
39
  "mypy>=1.15.0",
40
+ "orjson>=3.10.16",
40
41
  "pre-commit>=4.2.0",
41
42
  "pymemcache>=4.0.0",
42
43
  "pytest>=8.3.5",
43
44
  "pytest-asyncio>=0.26.0",
44
45
  "pytest-cov>=6.1.0",
46
+ "redis[hiredis]>=5.2.1",
45
47
  "ruff>=0.11.2",
46
48
  "tox>=4.25.0",
49
+ "types-orjson>=3.6.2",
50
+ "types-redis>=4.6.0.20241004",
47
51
  ]
48
52
 
49
53
  [project.optional-dependencies]
50
54
  memcache = ["pymemcache"]
55
+ redis = ["redis[hiredis]", "orjson"]
51
56
 
52
57
  [tool.setuptools]
53
- package-data = {"fastapi_cachex" = ["py.typed"]}
58
+ package-data = { "fastapi_cachex" = ["py.typed"] }
54
59
 
55
60
  [tool.pytest.ini_options]
56
61
  pythonpath = [
57
- "."
62
+ "."
58
63
  ]
59
64
  asyncio_default_fixture_loop_scope = "function"
60
65
 
@@ -0,0 +1,38 @@
1
+ import pytest
2
+ from fastapi import FastAPI
3
+ from fastapi.testclient import TestClient
4
+
5
+ from fastapi_cachex import BackendProxy
6
+ from fastapi_cachex import CacheBackend
7
+ from fastapi_cachex.backends import MemoryBackend
8
+ from fastapi_cachex.exceptions import BackendNotFoundError
9
+
10
+ # Setup FastAPI application
11
+ app = FastAPI()
12
+ client = TestClient(app)
13
+
14
+
15
+ # Define test endpoint (not a test function)
16
+ @app.get("/test-backend")
17
+ async def backend_endpoint(backend: CacheBackend):
18
+ return {"backend_type": backend.__class__.__name__}
19
+
20
+
21
+ # Actual test functions
22
+ @pytest.mark.asyncio
23
+ async def test_get_cache_backend_no_backend():
24
+ """Test that get_cache_backend raises BackendNotFoundError when no backend is set."""
25
+ BackendProxy.set_backend(None)
26
+ with pytest.raises(BackendNotFoundError):
27
+ client.get("/test-backend")
28
+
29
+
30
+ @pytest.mark.asyncio
31
+ async def test_get_cache_backend_with_memory_backend():
32
+ """Test that get_cache_backend returns the configured backend."""
33
+ backend = MemoryBackend()
34
+ BackendProxy.set_backend(backend)
35
+
36
+ response = client.get("/test-backend")
37
+ assert response.status_code == 200
38
+ assert response.json() == {"backend_type": "MemoryBackend"}
@@ -1,105 +0,0 @@
1
- # FastAPI-Cache X
2
-
3
- [![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)
4
- [![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)
5
- [![Tests](https://github.com/allen0099/FastAPI-CacheX/actions/workflows/test.yml/badge.svg)](https://github.com/allen0099/FastAPI-CacheX/actions/workflows/test.yml)
6
- [![Coverage Status](https://raw.githubusercontent.com/allen0099/FastAPI-CacheX/coverage-badge/coverage.svg)](https://github.com/allen0099/FastAPI-CacheX/actions/workflows/test.yml)
7
-
8
- [![Downloads](https://static.pepy.tech/badge/fastapi-cachex)](https://pepy.tech/project/fastapi-cachex)
9
- [![Weekly downloads](https://static.pepy.tech/badge/fastapi-cachex/week)](https://pepy.tech/project/fastapi-cachex)
10
- [![Monthly downloads](https://static.pepy.tech/badge/fastapi-cachex/month)](https://pepy.tech/project/fastapi-cachex)
11
-
12
- [![PyPI version](https://img.shields.io/pypi/v/fastapi-cachex.svg?logo=pypi&logoColor=gold&label=PyPI)](https://pypi.org/project/fastapi-cachex)
13
- [![Python Versions](https://img.shields.io/pypi/pyversions/fastapi-cachex.svg?logo=python&label=Python&logoColor=gold)](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.
18
-
19
- ## Features
20
-
21
- - Support for HTTP caching headers
22
- - `Cache-Control`
23
- - `ETag`
24
- - `If-None-Match`
25
- - Multiple backend cache support
26
- - Redis
27
- - Memcached
28
- - In-memory cache
29
- - Complete Cache-Control directive implementation
30
- - Easy-to-use `@cache` decorator
31
-
32
- ## Installation
33
-
34
- ### Using pip
35
-
36
- ```bash
37
- pip install fastapi-cachex
38
- ```
39
-
40
- ### Using uv (recommended)
41
-
42
- ```bash
43
- uv pip install fastapi-cachex
44
- ```
45
-
46
- ## Quick Start
47
-
48
- ```python
49
- from fastapi import FastAPI
50
- from fastapi_cachex import cache, BackendProxy
51
- from fastapi_cachex.backends import MemoryBackend, MemcachedBackend
52
-
53
- app = FastAPI()
54
-
55
- # Configure your cache backend
56
- memory_backend = MemoryBackend() # In-memory cache
57
- # or
58
- memcached_backend = MemcachedBackend(servers=["localhost:11211"]) # Memcached
59
-
60
- # Set the backend you want to use
61
- BackendProxy.set_backend(memory_backend) # or memcached_backend
62
-
63
-
64
- @app.get("/")
65
- @cache(ttl=60) # Cache for 60 seconds
66
- async def read_root():
67
- return {"Hello": "World"}
68
- ```
69
-
70
- ## Backend Configuration
71
-
72
- FastAPI-CacheX supports multiple caching backends. You can easily switch between them using the `BackendProxy`.
73
-
74
- ### In-Memory Cache
75
-
76
- ```python
77
- from fastapi_cachex.backends import MemoryBackend
78
- from fastapi_cachex import BackendProxy
79
-
80
- backend = MemoryBackend()
81
- BackendProxy.set_backend(backend)
82
- ```
83
-
84
- ### Memcached
85
-
86
- ```python
87
- from fastapi_cachex.backends import MemcachedBackend
88
- from fastapi_cachex import BackendProxy
89
-
90
- backend = MemcachedBackend(servers=["localhost:11211"])
91
- BackendProxy.set_backend(backend)
92
- ```
93
-
94
- ### Redis (Coming Soon)
95
-
96
- Redis support is under development and will be available in future releases.
97
-
98
- ## Documentation
99
-
100
- - [Development Guide](docs/DEVELOPMENT.md)
101
- - [Contributing Guidelines](docs/CONTRIBUTING.md)
102
-
103
- ## License
104
-
105
- This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
@@ -1,2 +0,0 @@
1
- from .cache import cache as cache
2
- from .proxy import BackendProxy as BackendProxy
@@ -1,3 +0,0 @@
1
- from .base import BaseCacheBackend as BaseCacheBackend
2
- from .memcached import MemcachedBackend as MemcachedBackend
3
- from .memory import MemoryBackend as MemoryBackend
File without changes
File without changes