fastapi-cachex 0.2.1__tar.gz → 0.2.3__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 (31) hide show
  1. {fastapi_cachex-0.2.1 → fastapi_cachex-0.2.3}/PKG-INFO +23 -5
  2. {fastapi_cachex-0.2.1 → fastapi_cachex-0.2.3}/README.md +19 -2
  3. fastapi_cachex-0.2.3/fastapi_cachex/__init__.py +27 -0
  4. fastapi_cachex-0.2.3/fastapi_cachex/backends/__init__.py +13 -0
  5. {fastapi_cachex-0.2.1 → fastapi_cachex-0.2.3}/fastapi_cachex/backends/memcached.py +21 -2
  6. {fastapi_cachex-0.2.1 → fastapi_cachex-0.2.3}/fastapi_cachex/backends/memory.py +33 -5
  7. {fastapi_cachex-0.2.1 → fastapi_cachex-0.2.3}/fastapi_cachex/backends/redis.py +29 -6
  8. {fastapi_cachex-0.2.1 → fastapi_cachex-0.2.3}/fastapi_cachex/cache.py +59 -19
  9. {fastapi_cachex-0.2.1 → fastapi_cachex-0.2.3}/fastapi_cachex/dependencies.py +2 -2
  10. {fastapi_cachex-0.2.1 → fastapi_cachex-0.2.3}/fastapi_cachex/proxy.py +9 -2
  11. {fastapi_cachex-0.2.1 → fastapi_cachex-0.2.3}/fastapi_cachex/routes.py +6 -5
  12. fastapi_cachex-0.2.3/fastapi_cachex/session/__init__.py +21 -0
  13. fastapi_cachex-0.2.3/fastapi_cachex/session/config.py +70 -0
  14. fastapi_cachex-0.2.3/fastapi_cachex/session/dependencies.py +65 -0
  15. fastapi_cachex-0.2.3/fastapi_cachex/session/exceptions.py +25 -0
  16. fastapi_cachex-0.2.3/fastapi_cachex/session/manager.py +389 -0
  17. fastapi_cachex-0.2.3/fastapi_cachex/session/middleware.py +149 -0
  18. fastapi_cachex-0.2.3/fastapi_cachex/session/models.py +185 -0
  19. fastapi_cachex-0.2.3/fastapi_cachex/session/security.py +111 -0
  20. fastapi_cachex-0.2.3/fastapi_cachex/state/__init__.py +8 -0
  21. fastapi_cachex-0.2.3/fastapi_cachex/state/exceptions.py +19 -0
  22. fastapi_cachex-0.2.3/fastapi_cachex/state/manager.py +258 -0
  23. fastapi_cachex-0.2.3/fastapi_cachex/state/models.py +31 -0
  24. {fastapi_cachex-0.2.1 → fastapi_cachex-0.2.3}/fastapi_cachex/types.py +9 -0
  25. {fastapi_cachex-0.2.1 → fastapi_cachex-0.2.3}/pyproject.toml +3 -2
  26. fastapi_cachex-0.2.1/fastapi_cachex/__init__.py +0 -7
  27. fastapi_cachex-0.2.1/fastapi_cachex/backends/__init__.py +0 -13
  28. {fastapi_cachex-0.2.1 → fastapi_cachex-0.2.3}/fastapi_cachex/backends/base.py +0 -0
  29. {fastapi_cachex-0.2.1 → fastapi_cachex-0.2.3}/fastapi_cachex/directives.py +0 -0
  30. {fastapi_cachex-0.2.1 → fastapi_cachex-0.2.3}/fastapi_cachex/exceptions.py +0 -0
  31. {fastapi_cachex-0.2.1 → fastapi_cachex-0.2.3}/fastapi_cachex/py.typed +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-cachex
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: A caching library for FastAPI with support for Cache-Control, ETag, and multiple backends.
5
5
  Keywords: fastapi,cache,etag,cache-control,redis,memcached,in-memory
6
- Author: Allen
7
- Author-email: Allen <s96016641@gmail.com>
6
+ Author: allen0099
7
+ Author-email: allen0099 <s96016641@gmail.com>
8
8
  License-Expression: Apache-2.0
9
9
  Classifier: Development Status :: 3 - Alpha
10
10
  Classifier: Intended Audience :: Developers
@@ -20,6 +20,7 @@ Classifier: Framework :: FastAPI
20
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
21
  Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
22
22
  Requires-Dist: fastapi
23
+ Requires-Dist: pydantic
23
24
  Requires-Dist: pymemcache ; extra == 'memcache'
24
25
  Requires-Dist: redis[hiredis] ; extra == 'redis'
25
26
  Requires-Dist: orjson ; extra == 'redis'
@@ -47,10 +48,11 @@ Description-Content-Type: text/markdown
47
48
 
48
49
  [English](README.md) | [繁體中文](docs/README.zh-TW.md)
49
50
 
50
- A high-performance caching extension for FastAPI, providing comprehensive HTTP caching support.
51
+ A high-performance caching extension for FastAPI, providing comprehensive HTTP caching support and optional session management.
51
52
 
52
53
  ## Features
53
54
 
55
+ ### HTTP Caching
54
56
  - Support for HTTP caching headers
55
57
  - `Cache-Control`
56
58
  - `ETag`
@@ -62,6 +64,15 @@ A high-performance caching extension for FastAPI, providing comprehensive HTTP c
62
64
  - Complete Cache-Control directive implementation
63
65
  - Easy-to-use `@cache` decorator
64
66
 
67
+ ### Session Management (Optional Extension)
68
+ - Secure session management with HMAC-SHA256 token signing
69
+ - IP address and User-Agent binding (optional security features)
70
+ - Header and bearer token support (API-first architecture)
71
+ - Automatic session renewal (sliding expiration)
72
+ - Flash messages for cross-request communication
73
+ - Multiple backend support (Redis, Memcached, In-Memory)
74
+ - Complete session lifecycle management (create, validate, refresh, invalidate)
75
+
65
76
  ### Cache-Control Directives
66
77
 
67
78
  | Directive | Supported | Description |
@@ -86,6 +97,12 @@ A high-performance caching extension for FastAPI, providing comprehensive HTTP c
86
97
  uv add fastapi-cachex
87
98
  ```
88
99
 
100
+ ### Development Installation
101
+
102
+ ```bash
103
+ uv add git+https://github.com/allen0099/FastAPI-CacheX.git
104
+ ```
105
+
89
106
  ## Quick Start
90
107
 
91
108
  ```python
@@ -129,7 +146,7 @@ FastAPI-CacheX supports multiple caching backends. You can easily switch between
129
146
  Cache keys are generated in the following format to avoid collisions:
130
147
 
131
148
  ```
132
- {method}:{host}:{path}:{query_params}
149
+ {method}|||{host}|||{path}|||{query_params}
133
150
  ```
134
151
 
135
152
  This ensures that:
@@ -236,6 +253,7 @@ async def expensive_operation():
236
253
  - [Cache Flow Explanation](docs/CACHE_FLOW.md)
237
254
  - [Development Guide](docs/DEVELOPMENT.md)
238
255
  - [Contributing Guidelines](docs/CONTRIBUTING.md)
256
+ - [Session Management Guide](docs/SESSION.md) - Complete guide for session features
239
257
 
240
258
  ## License
241
259
 
@@ -14,10 +14,11 @@
14
14
 
15
15
  [English](README.md) | [繁體中文](docs/README.zh-TW.md)
16
16
 
17
- A high-performance caching extension for FastAPI, providing comprehensive HTTP caching support.
17
+ A high-performance caching extension for FastAPI, providing comprehensive HTTP caching support and optional session management.
18
18
 
19
19
  ## Features
20
20
 
21
+ ### HTTP Caching
21
22
  - Support for HTTP caching headers
22
23
  - `Cache-Control`
23
24
  - `ETag`
@@ -29,6 +30,15 @@ A high-performance caching extension for FastAPI, providing comprehensive HTTP c
29
30
  - Complete Cache-Control directive implementation
30
31
  - Easy-to-use `@cache` decorator
31
32
 
33
+ ### Session Management (Optional Extension)
34
+ - Secure session management with HMAC-SHA256 token signing
35
+ - IP address and User-Agent binding (optional security features)
36
+ - Header and bearer token support (API-first architecture)
37
+ - Automatic session renewal (sliding expiration)
38
+ - Flash messages for cross-request communication
39
+ - Multiple backend support (Redis, Memcached, In-Memory)
40
+ - Complete session lifecycle management (create, validate, refresh, invalidate)
41
+
32
42
  ### Cache-Control Directives
33
43
 
34
44
  | Directive | Supported | Description |
@@ -53,6 +63,12 @@ A high-performance caching extension for FastAPI, providing comprehensive HTTP c
53
63
  uv add fastapi-cachex
54
64
  ```
55
65
 
66
+ ### Development Installation
67
+
68
+ ```bash
69
+ uv add git+https://github.com/allen0099/FastAPI-CacheX.git
70
+ ```
71
+
56
72
  ## Quick Start
57
73
 
58
74
  ```python
@@ -96,7 +112,7 @@ FastAPI-CacheX supports multiple caching backends. You can easily switch between
96
112
  Cache keys are generated in the following format to avoid collisions:
97
113
 
98
114
  ```
99
- {method}:{host}:{path}:{query_params}
115
+ {method}|||{host}|||{path}|||{query_params}
100
116
  ```
101
117
 
102
118
  This ensures that:
@@ -203,6 +219,7 @@ async def expensive_operation():
203
219
  - [Cache Flow Explanation](docs/CACHE_FLOW.md)
204
220
  - [Development Guide](docs/DEVELOPMENT.md)
205
221
  - [Contributing Guidelines](docs/CONTRIBUTING.md)
222
+ - [Session Management Guide](docs/SESSION.md) - Complete guide for session features
206
223
 
207
224
  ## License
208
225
 
@@ -0,0 +1,27 @@
1
+ """FastAPI-CacheX: A powerful and flexible caching extension for FastAPI."""
2
+
3
+ import logging
4
+
5
+ from .cache import cache as cache
6
+ from .cache import default_key_builder as default_key_builder
7
+ from .dependencies import CacheBackend as CacheBackend
8
+ from .dependencies import get_cache_backend as get_cache_backend
9
+ from .proxy import BackendProxy as BackendProxy
10
+ from .routes import add_routes as add_routes
11
+ from .types import CacheKeyBuilder as CacheKeyBuilder
12
+
13
+ _package_logger = logging.getLogger("fastapi_cachex")
14
+ _package_logger.addHandler(
15
+ logging.NullHandler()
16
+ ) # Attach a NullHandler to avoid "No handler found" warnings in user applications.
17
+
18
+ # Session management (optional feature)
19
+ __all__ = [
20
+ "BackendProxy",
21
+ "CacheBackend",
22
+ "CacheKeyBuilder",
23
+ "add_routes",
24
+ "cache",
25
+ "default_key_builder",
26
+ "get_cache_backend",
27
+ ]
@@ -0,0 +1,13 @@
1
+ """Cache backend implementations for FastAPI-CacheX."""
2
+
3
+ from .base import BaseCacheBackend
4
+ from .memcached import MemcachedBackend
5
+ from .memory import MemoryBackend
6
+ from .redis import AsyncRedisCacheBackend
7
+
8
+ __all__ = [
9
+ "AsyncRedisCacheBackend",
10
+ "BaseCacheBackend",
11
+ "MemcachedBackend",
12
+ "MemoryBackend",
13
+ ]
@@ -1,17 +1,21 @@
1
1
  """Memcached cache backend implementation."""
2
2
 
3
+ import logging
3
4
  import warnings
4
5
 
5
- from fastapi_cachex.backends.base import BaseCacheBackend
6
6
  from fastapi_cachex.exceptions import CacheXError
7
7
  from fastapi_cachex.types import ETagContent
8
8
 
9
+ from .base import BaseCacheBackend
10
+
9
11
  try:
10
12
  import orjson as json
11
13
 
12
14
  except ImportError: # pragma: no cover
13
15
  import json # type: ignore[no-redef] # pragma: no cover
14
16
 
17
+ logger = logging.getLogger(__name__)
18
+
15
19
  # Default Memcached key prefix for fastapi-cachex
16
20
  DEFAULT_MEMCACHE_PREFIX = "fastapi_cachex:"
17
21
 
@@ -70,11 +74,13 @@ class MemcachedBackend(BaseCacheBackend):
70
74
  prefixed_key = self._make_key(key)
71
75
  value = self.client.get(prefixed_key)
72
76
  if value is None:
77
+ logger.debug("Memcached MISS; key=%s", key)
73
78
  return None
74
79
 
75
80
  # Memcached stores data as bytes; deserialize from JSON
76
81
  try:
77
82
  data = json.loads(value.decode("utf-8"))
83
+ logger.debug("Memcached HIT; key=%s", key)
78
84
  return ETagContent(
79
85
  etag=data["etag"],
80
86
  content=data["content"].encode()
@@ -82,6 +88,7 @@ class MemcachedBackend(BaseCacheBackend):
82
88
  else data["content"],
83
89
  )
84
90
  except (json.JSONDecodeError, KeyError, ValueError):
91
+ logger.debug("Memcached DESERIALIZE ERROR; key=%s", key)
85
92
  return None
86
93
 
87
94
  async def set(self, key: str, value: ETagContent, ttl: int | None = None) -> None:
@@ -119,6 +126,7 @@ class MemcachedBackend(BaseCacheBackend):
119
126
  serialized_bytes,
120
127
  expire=ttl if ttl is not None else 0,
121
128
  )
129
+ logger.debug("Memcached SET; key=%s ttl=%s", key, ttl)
122
130
 
123
131
  async def delete(self, key: str) -> None:
124
132
  """Delete value from cache.
@@ -127,6 +135,7 @@ class MemcachedBackend(BaseCacheBackend):
127
135
  key: Cache key to delete
128
136
  """
129
137
  self.client.delete(self._make_key(key))
138
+ logger.debug("Memcached DELETE; key=%s", key)
130
139
 
131
140
  async def clear(self) -> None:
132
141
  """Clear all values from cache.
@@ -142,6 +151,7 @@ class MemcachedBackend(BaseCacheBackend):
142
151
  stacklevel=2,
143
152
  )
144
153
  self.client.flush_all()
154
+ logger.debug("Memcached CLEAR; flush_all issued")
145
155
 
146
156
  async def clear_path(self, path: str, include_params: bool = False) -> int:
147
157
  """Clear cached responses for a specific path.
@@ -175,9 +185,15 @@ class MemcachedBackend(BaseCacheBackend):
175
185
  except Exception: # noqa: BLE001
176
186
  return 0
177
187
  else:
188
+ logger.debug(
189
+ "Memcached CLEAR_PATH; path=%s include_params=%s removed=%s",
190
+ path,
191
+ include_params,
192
+ 1 if result else 0,
193
+ )
178
194
  return 1 if result else 0
179
195
 
180
- async def clear_pattern(self, pattern: str) -> int: # noqa: ARG002
196
+ async def clear_pattern(self, pattern: str) -> int:
181
197
  """Clear cached responses matching a pattern.
182
198
 
183
199
  Memcached does not support pattern matching or key scanning.
@@ -197,6 +213,7 @@ class MemcachedBackend(BaseCacheBackend):
197
213
  RuntimeWarning,
198
214
  stacklevel=2,
199
215
  )
216
+ logger.debug("Memcached CLEAR_PATTERN unsupported; pattern=%s", pattern)
200
217
  return 0
201
218
 
202
219
  async def get_all_keys(self) -> list[str]:
@@ -218,6 +235,7 @@ class MemcachedBackend(BaseCacheBackend):
218
235
  RuntimeWarning,
219
236
  stacklevel=2,
220
237
  )
238
+ logger.debug("Memcached GET_ALL_KEYS unsupported; returning empty list")
221
239
  return []
222
240
 
223
241
  async def get_cache_data(self) -> dict[str, tuple[ETagContent, float | None]]:
@@ -236,4 +254,5 @@ class MemcachedBackend(BaseCacheBackend):
236
254
  RuntimeWarning,
237
255
  stacklevel=2,
238
256
  )
257
+ logger.debug("Memcached GET_CACHE_DATA unsupported; returning empty dict")
239
258
  return {}
@@ -3,14 +3,18 @@
3
3
  import asyncio
4
4
  import contextlib
5
5
  import fnmatch
6
+ import logging
6
7
  import time
7
8
 
9
+ from fastapi_cachex.types import CACHE_KEY_SEPARATOR
8
10
  from fastapi_cachex.types import CacheItem
9
11
  from fastapi_cachex.types import ETagContent
10
12
 
11
13
  from .base import BaseCacheBackend
12
14
 
13
- # Cache keys are formatted as: method:host:path:query_params
15
+ logger = logging.getLogger(__name__)
16
+
17
+ # Cache keys are formatted as: method|||host|||path|||query_params
14
18
  # Minimum parts required to extract path component
15
19
  _MIN_KEY_PARTS = 3
16
20
  # Maximum parts to split (method, host, path, query_params)
@@ -43,6 +47,10 @@ class MemoryBackend(BaseCacheBackend):
43
47
  with contextlib.suppress(RuntimeError):
44
48
  # No event loop yet; will be created on first async operation
45
49
  self._cleanup_task = asyncio.create_task(self._cleanup_task_impl())
50
+ logger.debug(
51
+ "Started memory backend cleanup task (interval=%s)",
52
+ self.cleanup_interval,
53
+ )
46
54
 
47
55
  def start_cleanup(self) -> None:
48
56
  """Start the cleanup task if it's not already running.
@@ -56,6 +64,7 @@ class MemoryBackend(BaseCacheBackend):
56
64
  if self._cleanup_task is not None:
57
65
  self._cleanup_task.cancel()
58
66
  self._cleanup_task = None
67
+ logger.debug("Stopped memory backend cleanup task")
59
68
 
60
69
  async def get(self, key: str) -> ETagContent | None:
61
70
  """Retrieve a cached response.
@@ -69,10 +78,13 @@ class MemoryBackend(BaseCacheBackend):
69
78
  cached_item = self.cache.get(key)
70
79
  if cached_item:
71
80
  if cached_item.expiry is None or cached_item.expiry > time.time():
81
+ logger.debug("Memory cache HIT; key=%s", key)
72
82
  return cached_item.value
73
83
  # Entry has expired; clean it up
74
84
  del self.cache[key]
85
+ logger.debug("Memory cache EXPIRED; key=%s removed", key)
75
86
  return None
87
+ logger.debug("Memory cache MISS; key=%s", key)
76
88
  return None
77
89
 
78
90
  async def set(self, key: str, value: ETagContent, ttl: int | None = None) -> None:
@@ -86,16 +98,19 @@ class MemoryBackend(BaseCacheBackend):
86
98
  async with self.lock:
87
99
  expiry = time.time() + ttl if ttl is not None else None
88
100
  self.cache[key] = CacheItem(value=value, expiry=expiry)
101
+ logger.debug("Memory cache SET; key=%s ttl=%s", key, ttl)
89
102
 
90
103
  async def delete(self, key: str) -> None:
91
104
  """Remove a response from the cache."""
92
105
  async with self.lock:
93
106
  self.cache.pop(key, None)
107
+ logger.debug("Memory cache DELETE; key=%s", key)
94
108
 
95
109
  async def clear(self) -> None:
96
110
  """Clear all cached responses."""
97
111
  async with self.lock:
98
112
  self.cache.clear()
113
+ logger.debug("Memory cache CLEAR; all entries removed")
99
114
 
100
115
  async def clear_path(self, path: str, include_params: bool = False) -> int:
101
116
  """Clear cached responses for a specific path.
@@ -115,8 +130,8 @@ class MemoryBackend(BaseCacheBackend):
115
130
  async with self.lock:
116
131
  keys_to_delete = []
117
132
  for key in self.cache:
118
- # Keys are formatted as: method:host:path:query_params
119
- parts = key.split(":", _MAX_KEY_PARTS)
133
+ # Keys are formatted as: method|||host|||path|||query_params
134
+ parts = key.split(CACHE_KEY_SEPARATOR, _MAX_KEY_PARTS)
120
135
  if len(parts) >= _MIN_KEY_PARTS:
121
136
  cache_path = parts[2]
122
137
  has_params = len(parts) > _MIN_KEY_PARTS
@@ -127,6 +142,12 @@ class MemoryBackend(BaseCacheBackend):
127
142
  for key in keys_to_delete:
128
143
  del self.cache[key]
129
144
 
145
+ logger.debug(
146
+ "Memory cache CLEAR_PATH; path=%s include_params=%s removed=%s",
147
+ path,
148
+ include_params,
149
+ cleared_count,
150
+ )
130
151
  return cleared_count
131
152
 
132
153
  async def clear_pattern(self, pattern: str) -> int:
@@ -145,8 +166,8 @@ class MemoryBackend(BaseCacheBackend):
145
166
  async with self.lock:
146
167
  keys_to_delete = []
147
168
  for key in self.cache:
148
- # Extract path component (method:host:path:query_params)
149
- parts = key.split(":", _MAX_KEY_PARTS)
169
+ # Extract path component (method|||host|||path|||query_params)
170
+ parts = key.split(CACHE_KEY_SEPARATOR, _MAX_KEY_PARTS)
150
171
  if len(parts) >= _MIN_KEY_PARTS:
151
172
  cache_path = parts[2]
152
173
  if fnmatch.fnmatch(cache_path, pattern):
@@ -156,6 +177,9 @@ class MemoryBackend(BaseCacheBackend):
156
177
  for key in keys_to_delete:
157
178
  del self.cache[key]
158
179
 
180
+ logger.debug(
181
+ "Memory cache CLEAR_PATTERN; pattern=%s removed=%s", pattern, cleared_count
182
+ )
159
183
  return cleared_count
160
184
 
161
185
  async def get_all_keys(self) -> list[str]:
@@ -196,3 +220,7 @@ class MemoryBackend(BaseCacheBackend):
196
220
  ]
197
221
  for key in expired_keys:
198
222
  self.cache.pop(key, None)
223
+ if expired_keys:
224
+ logger.debug(
225
+ "Memory cache CLEANUP; expired removed=%s", len(expired_keys)
226
+ )
@@ -1,13 +1,16 @@
1
1
  """Redis cache backend implementation."""
2
2
 
3
+ import logging
3
4
  from typing import TYPE_CHECKING
4
5
  from typing import Any
5
6
  from typing import Literal
6
7
 
7
- from fastapi_cachex.backends.base import BaseCacheBackend
8
8
  from fastapi_cachex.exceptions import CacheXError
9
+ from fastapi_cachex.types import CACHE_KEY_SEPARATOR
9
10
  from fastapi_cachex.types import ETagContent
10
11
 
12
+ from .base import BaseCacheBackend
13
+
11
14
  if TYPE_CHECKING:
12
15
  from redis.asyncio import Redis as AsyncRedis
13
16
 
@@ -17,6 +20,8 @@ try:
17
20
  except ImportError: # pragma: no cover
18
21
  import json # type: ignore[no-redef] # pragma: no cover
19
22
 
23
+ logger = logging.getLogger(__name__)
24
+
20
25
  # Default Redis key prefix for fastapi-cachex
21
26
  DEFAULT_REDIS_PREFIX = "fastapi_cachex:"
22
27
 
@@ -116,7 +121,9 @@ class AsyncRedisCacheBackend(BaseCacheBackend):
116
121
  async def get(self, key: str) -> ETagContent | None:
117
122
  """Retrieve a cached response."""
118
123
  result = await self.client.get(self._make_key(key))
119
- return self._deserialize(result)
124
+ value = self._deserialize(result)
125
+ logger.debug("Redis %s; key=%s", "HIT" if value else "MISS", key)
126
+ return value
120
127
 
121
128
  async def set(self, key: str, value: ETagContent, ttl: int | None = None) -> None:
122
129
  """Store a response in the cache."""
@@ -126,10 +133,12 @@ class AsyncRedisCacheBackend(BaseCacheBackend):
126
133
  await self.client.setex(prefixed_key, ttl, serialized)
127
134
  else:
128
135
  await self.client.set(prefixed_key, serialized)
136
+ logger.debug("Redis SET; key=%s ttl=%s", key, ttl)
129
137
 
130
138
  async def delete(self, key: str) -> None:
131
139
  """Remove a response from the cache."""
132
140
  await self.client.delete(self._make_key(key))
141
+ logger.debug("Redis DELETE; key=%s", key)
133
142
 
134
143
  async def clear(self) -> None:
135
144
  """Clear all cached responses for this namespace.
@@ -160,6 +169,7 @@ class AsyncRedisCacheBackend(BaseCacheBackend):
160
169
  batch = keys_to_delete[i : i + batch_size]
161
170
  if batch:
162
171
  await self.client.delete(*batch)
172
+ logger.debug("Redis CLEAR; removed=%s", len(keys_to_delete))
163
173
 
164
174
  async def clear_path(self, path: str, include_params: bool = False) -> int:
165
175
  """Clear cached responses for a specific path.
@@ -175,11 +185,13 @@ class AsyncRedisCacheBackend(BaseCacheBackend):
175
185
  """
176
186
  # Pattern includes the HTTP method, host, and path components
177
187
  if include_params:
178
- # Clear all variations: *:path:*
179
- pattern = f"{self.key_prefix}*:{path}:*"
188
+ # Clear all variations: *|||path|||*
189
+ pattern = (
190
+ f"{self.key_prefix}*{CACHE_KEY_SEPARATOR}{path}{CACHE_KEY_SEPARATOR}*"
191
+ )
180
192
  else:
181
- # Clear only exact path (no query params): *:path
182
- pattern = f"{self.key_prefix}*:{path}"
193
+ # Clear only exact path (no query params): *|||path
194
+ pattern = f"{self.key_prefix}*{CACHE_KEY_SEPARATOR}{path}"
183
195
 
184
196
  cursor = 0
185
197
  batch_size = 100
@@ -206,6 +218,12 @@ class AsyncRedisCacheBackend(BaseCacheBackend):
206
218
  deleted = await self.client.delete(*batch)
207
219
  cleared_count += deleted
208
220
 
221
+ logger.debug(
222
+ "Redis CLEAR_PATH; path=%s include_params=%s removed=%s",
223
+ path,
224
+ include_params,
225
+ cleared_count,
226
+ )
209
227
  return cleared_count
210
228
 
211
229
  async def clear_pattern(self, pattern: str) -> int:
@@ -250,6 +268,9 @@ class AsyncRedisCacheBackend(BaseCacheBackend):
250
268
  deleted = await self.client.delete(*batch)
251
269
  cleared_count += deleted
252
270
 
271
+ logger.debug(
272
+ "Redis CLEAR_PATTERN; pattern=%s removed=%s", full_pattern, cleared_count
273
+ )
253
274
  return cleared_count
254
275
 
255
276
  async def get_all_keys(self) -> list[str]:
@@ -275,6 +296,7 @@ class AsyncRedisCacheBackend(BaseCacheBackend):
275
296
  if cursor == 0:
276
297
  break
277
298
 
299
+ logger.debug("Redis GET_ALL_KEYS; count=%s", len(all_keys))
278
300
  return all_keys
279
301
 
280
302
  async def get_cache_data(self) -> dict[str, tuple[ETagContent, float | None]]:
@@ -297,4 +319,5 @@ class AsyncRedisCacheBackend(BaseCacheBackend):
297
319
  if value is not None:
298
320
  cache_data[original_key] = (value, None)
299
321
 
322
+ logger.debug("Redis GET_CACHE_DATA; keys=%s", len(cache_data))
300
323
  return cache_data