coredis 4.24.0__py3-none-any.whl → 5.0.0rc1__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 coredis might be problematic. Click here for more details.

Files changed (77) hide show
  1. coredis/__init__.py +1 -3
  2. coredis/_packer.py +10 -10
  3. coredis/_protocols.py +23 -32
  4. coredis/_py_311_typing.py +20 -0
  5. coredis/_py_312_typing.py +17 -0
  6. coredis/_utils.py +49 -51
  7. coredis/_version.py +3 -3
  8. coredis/cache.py +57 -82
  9. coredis/client/__init__.py +1 -2
  10. coredis/client/basic.py +129 -56
  11. coredis/client/cluster.py +147 -70
  12. coredis/commands/__init__.py +27 -7
  13. coredis/commands/_key_spec.py +11 -10
  14. coredis/commands/_utils.py +1 -1
  15. coredis/commands/_validators.py +30 -20
  16. coredis/commands/_wrappers.py +19 -99
  17. coredis/commands/bitfield.py +10 -2
  18. coredis/commands/constants.py +20 -3
  19. coredis/commands/core.py +1627 -1246
  20. coredis/commands/function.py +21 -19
  21. coredis/commands/monitor.py +0 -71
  22. coredis/commands/pubsub.py +7 -142
  23. coredis/commands/request.py +108 -0
  24. coredis/commands/script.py +9 -9
  25. coredis/commands/sentinel.py +60 -49
  26. coredis/connection.py +14 -15
  27. coredis/exceptions.py +2 -2
  28. coredis/experimental/__init__.py +0 -4
  29. coredis/globals.py +3 -0
  30. coredis/modules/autocomplete.py +28 -30
  31. coredis/modules/base.py +15 -31
  32. coredis/modules/filters.py +269 -245
  33. coredis/modules/graph.py +61 -62
  34. coredis/modules/json.py +172 -140
  35. coredis/modules/response/_callbacks/autocomplete.py +5 -4
  36. coredis/modules/response/_callbacks/graph.py +34 -29
  37. coredis/modules/response/_callbacks/json.py +5 -3
  38. coredis/modules/response/_callbacks/search.py +49 -53
  39. coredis/modules/response/_callbacks/timeseries.py +18 -30
  40. coredis/modules/response/types.py +1 -5
  41. coredis/modules/search.py +186 -169
  42. coredis/modules/timeseries.py +184 -164
  43. coredis/parser.py +6 -19
  44. coredis/pipeline.py +391 -422
  45. coredis/pool/basic.py +7 -7
  46. coredis/pool/cluster.py +3 -3
  47. coredis/pool/nodemanager.py +10 -3
  48. coredis/response/_callbacks/__init__.py +76 -57
  49. coredis/response/_callbacks/acl.py +0 -3
  50. coredis/response/_callbacks/cluster.py +25 -16
  51. coredis/response/_callbacks/command.py +8 -6
  52. coredis/response/_callbacks/connection.py +4 -3
  53. coredis/response/_callbacks/geo.py +17 -13
  54. coredis/response/_callbacks/hash.py +13 -11
  55. coredis/response/_callbacks/keys.py +9 -5
  56. coredis/response/_callbacks/module.py +2 -3
  57. coredis/response/_callbacks/script.py +6 -8
  58. coredis/response/_callbacks/sentinel.py +21 -17
  59. coredis/response/_callbacks/server.py +36 -14
  60. coredis/response/_callbacks/sets.py +3 -4
  61. coredis/response/_callbacks/sorted_set.py +27 -24
  62. coredis/response/_callbacks/streams.py +22 -13
  63. coredis/response/_callbacks/strings.py +7 -6
  64. coredis/response/_callbacks/vector_sets.py +126 -0
  65. coredis/response/types.py +13 -4
  66. coredis/sentinel.py +1 -1
  67. coredis/stream.py +4 -3
  68. coredis/tokens.py +343 -16
  69. coredis/typing.py +432 -79
  70. {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/METADATA +4 -5
  71. coredis-5.0.0rc1.dist-info/RECORD +95 -0
  72. coredis/client/keydb.py +0 -336
  73. coredis/pipeline.pyi +0 -2103
  74. coredis-4.24.0.dist-info/RECORD +0 -93
  75. {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/WHEEL +0 -0
  76. {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/licenses/LICENSE +0 -0
  77. {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/top_level.txt +0 -0
coredis/__init__.py CHANGED
@@ -10,7 +10,7 @@ from __future__ import annotations
10
10
 
11
11
  from typing import cast
12
12
 
13
- from coredis.client import KeyDB, KeyDBCluster, Redis, RedisCluster
13
+ from coredis.client import Redis, RedisCluster
14
14
  from coredis.config import Config
15
15
  from coredis.connection import (
16
16
  BaseConnection,
@@ -30,8 +30,6 @@ from . import _version
30
30
 
31
31
  __all__ = [
32
32
  "Config",
33
- "KeyDB",
34
- "KeyDBCluster",
35
33
  "Redis",
36
34
  "RedisCluster",
37
35
  "BaseConnection",
coredis/_packer.py CHANGED
@@ -1,14 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from coredis.constants import SYM_CRLF, SYM_DOLLAR, SYM_EMPTY, SYM_STAR
4
- from coredis.typing import ValueT
4
+ from coredis.typing import RedisValueT
5
5
 
6
6
 
7
7
  class Packer:
8
8
  def __init__(self, encoding: str):
9
9
  self.encoding = encoding
10
10
 
11
- def encode(self, value: ValueT) -> bytes:
11
+ def encode(self, value: RedisValueT) -> bytes:
12
12
  """Returns a bytestring representation of the value"""
13
13
  if isinstance(value, str):
14
14
  return value.encode(self.encoding)
@@ -18,7 +18,7 @@ class Packer:
18
18
  return b"%.15g" % value
19
19
  return value
20
20
 
21
- def pack_command(self, command: bytes, *args: ValueT) -> list[bytes]:
21
+ def pack_command(self, command: bytes, *args: RedisValueT) -> list[bytes]:
22
22
  "Pack a series of arguments into the Redis protocol"
23
23
  output: list[bytes] = []
24
24
  # the client might have included 1 or more literal arguments in
@@ -50,22 +50,22 @@ class Packer:
50
50
  output.append(buff)
51
51
  return output
52
52
 
53
- def pack_commands(self, commands: list[tuple[ValueT, ...]]) -> list[bytes]:
53
+ def pack_commands(self, commands: list[tuple[RedisValueT, ...]]) -> list[bytes]:
54
54
  output: list[bytes] = []
55
- pieces: list[bytes] = []
55
+ command_arguments: list[bytes] = []
56
56
  buffer_length = 0
57
57
 
58
58
  for cmd in commands:
59
59
  for chunk in self.pack_command(self.encode(cmd[0]), *cmd[1:]):
60
- pieces.append(chunk)
60
+ command_arguments.append(chunk)
61
61
  buffer_length += len(chunk)
62
62
 
63
63
  if buffer_length > 6000:
64
- output.append(SYM_EMPTY.join(pieces))
64
+ output.append(SYM_EMPTY.join(command_arguments))
65
65
  buffer_length = 0
66
- pieces = []
66
+ command_arguments = []
67
67
 
68
- if pieces:
69
- output.append(SYM_EMPTY.join(pieces))
68
+ if command_arguments:
69
+ output.append(SYM_EMPTY.join(command_arguments))
70
70
 
71
71
  return output
coredis/_protocols.py CHANGED
@@ -1,43 +1,50 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
- from types import TracebackType
5
4
 
6
5
  from typing_extensions import runtime_checkable
7
6
 
7
+ from coredis.response._callbacks import NoopCallback
8
8
  from coredis.typing import (
9
+ TYPE_CHECKING,
9
10
  Awaitable,
10
11
  Callable,
12
+ ExecutionParameters,
11
13
  KeyT,
12
14
  Parameters,
13
15
  Protocol,
14
16
  R,
17
+ RedisCommandP,
18
+ RedisValueT,
15
19
  ResponseType,
16
20
  StringT,
17
21
  TypeVar,
22
+ Unpack,
18
23
  ValueT,
19
24
  )
20
25
 
21
26
  T_co = TypeVar("T_co", covariant=True)
22
27
 
23
28
 
29
+ if TYPE_CHECKING:
30
+ from coredis.commands import CommandRequest
31
+
32
+
24
33
  class AbstractExecutor(Protocol):
25
- async def execute_command(
34
+ def execute_command(
26
35
  self,
27
- command: bytes,
28
- *args: ValueT,
29
- callback: Callable[..., R] = ...,
30
- **options: ValueT | None,
31
- ) -> R: ...
32
-
36
+ command: RedisCommandP,
37
+ callback: Callable[..., R] = NoopCallback(),
38
+ **options: Unpack[ExecutionParameters],
39
+ ) -> Awaitable[R]: ...
33
40
 
34
- @runtime_checkable
35
- class SupportsPipeline(Protocol): # noqa
36
- async def pipeline(
41
+ def create_request(
37
42
  self,
38
- transaction: bool | None = True,
39
- watches: Parameters[StringT] | None = None,
40
- ) -> SupportsWatch: ...
43
+ name: bytes,
44
+ *arguments: ValueT,
45
+ callback: Callable[..., R],
46
+ execution_parameters: ExecutionParameters | None = None,
47
+ ) -> CommandRequest[R]: ...
41
48
 
42
49
 
43
50
  @runtime_checkable
@@ -46,35 +53,19 @@ class SupportsScript(Protocol[T_co]): # noqa
46
53
  self,
47
54
  sha1: StringT,
48
55
  keys: Parameters[KeyT] | None = ...,
49
- args: Parameters[ValueT] | None = ...,
56
+ args: Parameters[RedisValueT] | None = ...,
50
57
  ) -> ResponseType: ...
51
58
 
52
59
  async def evalsha_ro(
53
60
  self,
54
61
  sha1: StringT,
55
62
  keys: Parameters[KeyT] | None = ...,
56
- args: Parameters[ValueT] | None = ...,
63
+ args: Parameters[RedisValueT] | None = ...,
57
64
  ) -> ResponseType: ...
58
65
 
59
66
  async def script_load(self, script: StringT) -> T_co: ...
60
67
 
61
68
 
62
- @runtime_checkable
63
- class SupportsWatch(Protocol): # noqa
64
- async def __aenter__(self) -> SupportsWatch: ...
65
-
66
- async def __aexit__(
67
- self,
68
- exc_type: type[BaseException] | None,
69
- exc_value: BaseException | None,
70
- traceback: TracebackType | None,
71
- ) -> Awaitable[bool | None]: ...
72
-
73
- async def watch(self, *keys: KeyT) -> bool: ...
74
-
75
- async def execute(self, raise_on_error: bool = True) -> tuple[object, ...]: ...
76
-
77
-
78
69
  @runtime_checkable
79
70
  class ConnectionP(Protocol):
80
71
  decode_responses: bool
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Hashable
4
+ from typing import Any, TypeAlias
5
+
6
+ from .typing import MutableSet, RedisError, ResponsePrimitive
7
+
8
+ #: Represents the total structure of any response for any redis command.
9
+ ResponseType: TypeAlias = (
10
+ ResponsePrimitive
11
+ | list[Any]
12
+ | MutableSet[Hashable]
13
+ | dict[
14
+ Hashable,
15
+ Any,
16
+ ]
17
+ | RedisError
18
+ )
19
+ #: Type alias for valid python types that can be represented as json
20
+ JsonType: TypeAlias = str | int | float | bool | dict[str, Any] | list[Any] | None
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Hashable
4
+
5
+ from .typing import MutableSet, RedisError, ResponsePrimitive
6
+
7
+ #: Type alias for valid python types that can be represented as json
8
+ type JsonType = str | int | float | bool | dict[str, JsonType] | list[JsonType] | None
9
+
10
+ #: Represents the total structure of any response for any redis command.
11
+ type ResponseType = (
12
+ ResponsePrimitive
13
+ | RedisError
14
+ | list[ResponseType]
15
+ | MutableSet[Hashable]
16
+ | dict[Hashable, ResponseType]
17
+ )
coredis/_utils.py CHANGED
@@ -1,14 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import enum
4
+ from collections import UserDict
4
5
  from typing import Any
5
6
 
6
- from wrapt import ObjectProxy
7
-
8
7
  from coredis.typing import (
9
8
  Hashable,
10
9
  Iterable,
11
10
  Mapping,
11
+ MutableMapping,
12
12
  ResponseType,
13
13
  StringT,
14
14
  TypeVar,
@@ -18,61 +18,59 @@ T = TypeVar("T")
18
18
  U = TypeVar("U")
19
19
 
20
20
 
21
- class EncodingInsensitiveDict(ObjectProxy): # type: ignore
21
+ class EncodingInsensitiveDict(UserDict[Any, Any]):
22
22
  def __init__(
23
23
  self,
24
- dict: Mapping[Any, Any] | ResponseType | None = None,
24
+ initial: MutableMapping[Any, Any] | None = None,
25
25
  encoding: str = "utf-8",
26
26
  ):
27
- d = dict or {}
28
- super().__init__(d)
29
- self._self_encoding = encoding
30
-
31
- def __getitem__(self, item: StringT) -> Any:
32
- if isinstance(item, str) and item not in self.__wrapped__:
33
- return self.__wrapped__.get(
34
- item, self.__wrapped__.get(item.encode(self._self_encoding))
35
- )
36
- elif isinstance(item, bytes) and item not in self.__wrapped__:
37
- return self.__wrapped__.get(
38
- item, self.__wrapped__.get(item.decode(self._self_encoding))
39
- )
40
- return self.__wrapped__[item]
41
-
42
- def get(self, item: StringT, default: object | None = None) -> Any:
43
- return self.__getitem__(item) or default
44
-
45
- def pop(self, item: StringT, default: object | None = None) -> Any:
46
- if item in self.__wrapped__:
47
- return self.__wrapped__.pop(item)
48
- if isinstance(item, str):
49
- return self.__wrapped__.pop(item.encode(self._self_encoding), default)
50
-
51
- def clear(self) -> None:
52
- self.__wrapped__.clear()
53
-
54
- def update(self, updates: Mapping[Any, Any]) -> None:
55
- self.__wrapped__.update(updates)
56
-
57
- def __setitem__(self, item: StringT, value: object) -> None:
58
- if item in self.__wrapped__:
59
- self.__wrapped__[item] = value
60
- elif isinstance(item, str) and item.encode(self._self_encoding) in self.__wrapped__:
61
- self.__wrapped__[item.encode(self._self_encoding)] = value
62
- elif isinstance(item, bytes) and item.decode(self._self_encoding) in self.__wrapped__:
63
- self.__wrapped__[item.decode(self._self_encoding)] = value
64
- else:
65
- self.__wrapped__[item] = value
66
-
67
- def __contains__(self, key: StringT) -> bool:
27
+ self._encoding = encoding
28
+ super().__init__(initial or {})
29
+
30
+ def _alt_key(self, key: StringT) -> StringT:
68
31
  if isinstance(key, str):
69
- return key in self.__wrapped__ or key.encode(self._self_encoding) in self.__wrapped__
32
+ try:
33
+ byte_alt = key.encode(self._encoding)
34
+ if byte_alt in self.data:
35
+ return byte_alt
36
+ except UnicodeEncodeError:
37
+ pass
70
38
  elif isinstance(key, bytes):
71
- return key in self.__wrapped__ or key.decode(self._self_encoding) in self.__wrapped__
72
- return key in self.__wrapped__
73
-
74
- def __repr__(self) -> str:
75
- return repr(self.__wrapped__)
39
+ try:
40
+ str_alt = key.decode(self._encoding)
41
+ if str_alt in self.data:
42
+ return str_alt
43
+ except UnicodeDecodeError:
44
+ pass
45
+ return key
46
+
47
+ def __getitem__(self, key: StringT) -> Any:
48
+ if key in self.data:
49
+ return self.data[key]
50
+ alt = self._alt_key(key)
51
+ if alt in self.data:
52
+ return self.data[alt]
53
+ raise KeyError(key)
54
+
55
+ def __setitem__(self, key: StringT, value: Any) -> None:
56
+ alt = self._alt_key(key)
57
+ self.data[alt] = value
58
+
59
+ def __delitem__(self, key: StringT) -> None:
60
+ alt = self._alt_key(key)
61
+ del self.data[alt]
62
+
63
+ def __contains__(self, key: Any) -> bool:
64
+ return key in self.data or self._alt_key(key) in self.data
65
+
66
+ def get(self, key: StringT, default: Any = None) -> Any:
67
+ return self.data.get(key, self.data.get(self._alt_key(key), default))
68
+
69
+ def pop(self, key: StringT, default: Any = None) -> Any:
70
+ if key in self.data:
71
+ return self.data.pop(key)
72
+ alt = self._alt_key(key)
73
+ return self.data.pop(alt, default)
76
74
 
77
75
 
78
76
  @enum.unique
coredis/_version.py CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-07-05T19:34:23-0700",
11
+ "date": "2025-07-07T11:41:52-0700",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "cd4583e1ad0eb6d8ca23e80203ab230efe1e3b2e",
15
- "version": "4.24.0"
14
+ "full-revisionid": "e2034feabddbed8c1a549ccc53976ca41d724a00",
15
+ "version": "5.0.0rc1"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
coredis/cache.py CHANGED
@@ -18,11 +18,9 @@ from coredis.typing import (
18
18
  Literal,
19
19
  ModuleType,
20
20
  OrderedDict,
21
- Protocol,
21
+ RedisValueT,
22
22
  ResponseType,
23
23
  TypeVar,
24
- ValueT,
25
- runtime_checkable,
26
24
  )
27
25
 
28
26
  asizeof: ModuleType | None = None
@@ -73,16 +71,16 @@ class CacheStats:
73
71
  counter.clear()
74
72
  counter[b"__coredis__internal__stats__total"] = total
75
73
 
76
- def hit(self, key: ValueT) -> None:
74
+ def hit(self, key: RedisValueT) -> None:
77
75
  self.hits[b(key)] += 1
78
76
 
79
- def miss(self, key: ValueT) -> None:
77
+ def miss(self, key: RedisValueT) -> None:
80
78
  self.misses[b(key)] += 1
81
79
 
82
- def invalidate(self, key: ValueT) -> None:
80
+ def invalidate(self, key: RedisValueT) -> None:
83
81
  self.invalidations[b(key)] += 1
84
82
 
85
- def mark_dirty(self, key: ValueT) -> None:
83
+ def mark_dirty(self, key: RedisValueT) -> None:
86
84
  self.dirty[b(key)] += 1
87
85
 
88
86
  @property
@@ -135,47 +133,28 @@ class AbstractCache(ABC):
135
133
  ...
136
134
 
137
135
  @abstractmethod
138
- def get(self, command: bytes, key: bytes, *args: ValueT) -> ResponseType:
136
+ def get(self, command: bytes, key: RedisValueT, *args: RedisValueT) -> ResponseType:
139
137
  """
140
138
  Fetch the cached response for command/key/args combination
141
139
  """
142
140
  ...
143
141
 
144
142
  @abstractmethod
145
- def put(self, command: bytes, key: bytes, *args: ValueT, value: ResponseType) -> None:
143
+ def put(
144
+ self, command: bytes, key: RedisValueT, *args: RedisValueT, value: ResponseType
145
+ ) -> None:
146
146
  """
147
147
  Cache the response for command/key/args combination
148
148
  """
149
149
  ...
150
150
 
151
151
  @abstractmethod
152
- def invalidate(self, *keys: ValueT) -> None:
152
+ def invalidate(self, *keys: RedisValueT) -> None:
153
153
  """
154
154
  Invalidate any cached entries for the provided keys
155
155
  """
156
156
  ...
157
157
 
158
- @abstractmethod
159
- def reset(self) -> None:
160
- """
161
- Reset the cache
162
- """
163
- ...
164
-
165
- @abstractmethod
166
- def shutdown(self) -> None:
167
- """
168
- Explicitly shutdown the cache
169
- """
170
- ...
171
-
172
-
173
- @runtime_checkable
174
- class SupportsStats(Protocol):
175
- """
176
- Protocol of a cache that provides cache statistics
177
- """
178
-
179
158
  @property
180
159
  @abstractmethod
181
160
  def stats(self) -> CacheStats:
@@ -184,17 +163,6 @@ class SupportsStats(Protocol):
184
163
  """
185
164
  ...
186
165
 
187
-
188
- @runtime_checkable
189
- class SupportsSampling(Protocol):
190
- """
191
- If a cache implements :class:`SupportsSampling`, methods that support
192
- caching will sample the response from the cache and test it against an uncached
193
- response from the server based on the confidence returned by :attr:`confidence`.
194
- The outcome of the validation will be fed back to the cache using :meth:`feedback`
195
- and in the case that there was no match, the uncached response will be returned.
196
- """
197
-
198
166
  @property
199
167
  @abstractmethod
200
168
  def confidence(self) -> float:
@@ -206,23 +174,13 @@ class SupportsSampling(Protocol):
206
174
  ...
207
175
 
208
176
  @abstractmethod
209
- def feedback(self, command: bytes, key: bytes, *args: ValueT, match: bool) -> None:
177
+ def feedback(self, command: bytes, key: RedisValueT, *args: RedisValueT, match: bool) -> None:
210
178
  """
211
179
  Provide feedback about a key as having either a match or drift from the actual
212
180
  server side value
213
181
  """
214
182
  ...
215
183
 
216
-
217
- @runtime_checkable
218
- class SupportsClientTracking(Protocol):
219
- """
220
- If a cache implements :class:`SupportsClientTracking`, the :class:`~coredis.Redis`
221
- and :class:`~coredis.RedisCluster` clients will ensure that the client
222
- returned by :meth:`get_client_id` is set using the :meth:`~coredis.Redis.client_tracking`
223
- command on any connection returned by the clients.
224
- """
225
-
226
184
  @abstractmethod
227
185
  def get_client_id(self, connection: BaseConnection) -> int | None:
228
186
  """
@@ -232,6 +190,20 @@ class SupportsClientTracking(Protocol):
232
190
  """
233
191
  ...
234
192
 
193
+ @abstractmethod
194
+ def reset(self) -> None:
195
+ """
196
+ Reset the cache
197
+ """
198
+ ...
199
+
200
+ @abstractmethod
201
+ def shutdown(self) -> None:
202
+ """
203
+ Explicitly shutdown the cache
204
+ """
205
+ ...
206
+
235
207
 
236
208
  ET = TypeVar("ET")
237
209
 
@@ -276,7 +248,7 @@ class LRUCache(Generic[ET]):
276
248
  def clear(self) -> None:
277
249
  self.__cache.clear()
278
250
 
279
- def popitem(self) -> bool:
251
+ def popitem(self) -> tuple[Any, Any] | None:
280
252
  """
281
253
  Recursively remove the oldest entry. If
282
254
  the oldest entry is another LRUCache trigger
@@ -287,14 +259,14 @@ class LRUCache(Generic[ET]):
287
259
  oldest = next(iter(self.__cache))
288
260
  item = self.__cache[oldest]
289
261
  except StopIteration:
290
- return False
262
+ return None
291
263
 
292
264
  if isinstance(item, LRUCache):
293
- if item.popitem():
294
- return True
295
- self.__cache.popitem(last=False)
296
-
297
- return True
265
+ if popped := item.popitem():
266
+ return popped
267
+ if entry := self.__cache.popitem(last=False):
268
+ return entry
269
+ return None
298
270
 
299
271
  def shrink(self) -> None:
300
272
  """
@@ -304,11 +276,11 @@ class LRUCache(Generic[ET]):
304
276
  """
305
277
 
306
278
  if self.max_bytes > 0 and asizeof is not None:
307
- while asizeof.asizeof(self.__cache) > self.max_bytes:
308
- if not self.popitem():
309
- # nothing left to remove
310
-
279
+ cur_size = asizeof.asizeof(self.__cache)
280
+ while cur_size > self.max_bytes:
281
+ if (popped := self.popitem()) is None:
311
282
  return
283
+ cur_size -= asizeof.asizeof(popped[0]) + asizeof.asizeof(popped[1])
312
284
 
313
285
  def __repr__(self) -> str:
314
286
  if asizeof is not None:
@@ -316,7 +288,7 @@ class LRUCache(Generic[ET]):
316
288
  f"LruCache<max_items={self.max_items}, "
317
289
  f"current_items={len(self.__cache)}, "
318
290
  f"max_bytes={self.max_bytes}, "
319
- f"current_size_bytes={asizeof.asizeof(self)}>"
291
+ f"current_size_bytes={asizeof.asizeof(self.__cache)}>"
320
292
  )
321
293
  else:
322
294
  return f"LruCache<max_items={self.max_items}, current_items={len(self.__cache)}, "
@@ -329,9 +301,6 @@ class LRUCache(Generic[ET]):
329
301
  class NodeTrackingCache(
330
302
  Sidecar,
331
303
  AbstractCache,
332
- SupportsStats,
333
- SupportsSampling,
334
- SupportsClientTracking,
335
304
  ):
336
305
  """
337
306
  An LRU cache that uses server assisted client caching
@@ -392,7 +361,7 @@ class NodeTrackingCache(
392
361
  def stats(self) -> CacheStats:
393
362
  return self.__stats
394
363
 
395
- def get(self, command: bytes, key: bytes, *args: ValueT) -> ResponseType:
364
+ def get(self, command: bytes, key: RedisValueT, *args: RedisValueT) -> ResponseType:
396
365
  try:
397
366
  cached = self.__cache.get(b(key)).get(command).get(make_hashable(*args))
398
367
  self.__stats.hit(key)
@@ -402,17 +371,19 @@ class NodeTrackingCache(
402
371
  self.__stats.miss(key)
403
372
  raise
404
373
 
405
- def put(self, command: bytes, key: bytes, *args: ValueT, value: ResponseType) -> None:
374
+ def put(
375
+ self, command: bytes, key: RedisValueT, *args: RedisValueT, value: ResponseType
376
+ ) -> None:
406
377
  self.__cache.setdefault(b(key), LRUCache()).setdefault(command, LRUCache()).insert(
407
378
  make_hashable(*args), value
408
379
  )
409
380
 
410
- def invalidate(self, *keys: ValueT) -> None:
381
+ def invalidate(self, *keys: RedisValueT) -> None:
411
382
  for key in keys:
412
383
  self.__stats.invalidate(key)
413
384
  self.__cache.remove(b(key))
414
385
 
415
- def feedback(self, command: bytes, key: bytes, *args: ValueT, match: bool) -> None:
386
+ def feedback(self, command: bytes, key: RedisValueT, *args: RedisValueT, match: bool) -> None:
416
387
  if not match:
417
388
  self.__stats.mark_dirty(key)
418
389
  self.invalidate(key)
@@ -509,7 +480,7 @@ class NodeTrackingCache(
509
480
  break
510
481
 
511
482
 
512
- class ClusterTrackingCache(AbstractCache, SupportsStats, SupportsSampling, SupportsClientTracking):
483
+ class ClusterTrackingCache(AbstractCache):
513
484
  """
514
485
  An LRU cache for redis cluster that uses server assisted client caching
515
486
  to ensure local cache entries are invalidated if any operations are performed
@@ -616,7 +587,7 @@ class ClusterTrackingCache(AbstractCache, SupportsStats, SupportsSampling, Suppo
616
587
  except KeyError:
617
588
  return None
618
589
 
619
- def get(self, command: bytes, key: bytes, *args: ValueT) -> ResponseType:
590
+ def get(self, command: bytes, key: RedisValueT, *args: RedisValueT) -> ResponseType:
620
591
  try:
621
592
  cached = self.__cache.get(b(key)).get(command).get(make_hashable(*args))
622
593
  self.__stats.hit(key)
@@ -626,17 +597,19 @@ class ClusterTrackingCache(AbstractCache, SupportsStats, SupportsSampling, Suppo
626
597
  self.__stats.miss(key)
627
598
  raise
628
599
 
629
- def put(self, command: bytes, key: bytes, *args: ValueT, value: ResponseType) -> None:
600
+ def put(
601
+ self, command: bytes, key: RedisValueT, *args: RedisValueT, value: ResponseType
602
+ ) -> None:
630
603
  self.__cache.setdefault(b(key), LRUCache()).setdefault(command, LRUCache()).insert(
631
604
  make_hashable(*args), value
632
605
  )
633
606
 
634
- def invalidate(self, *keys: ValueT) -> None:
607
+ def invalidate(self, *keys: RedisValueT) -> None:
635
608
  for key in keys:
636
609
  self.__stats.invalidate(key)
637
610
  self.__cache.remove(b(key))
638
611
 
639
- def feedback(self, command: bytes, key: bytes, *args: ValueT, match: bool) -> None:
612
+ def feedback(self, command: bytes, key: RedisValueT, *args: RedisValueT, match: bool) -> None:
640
613
  if not match:
641
614
  self.__stats.mark_dirty(key)
642
615
  self.invalidate(key)
@@ -663,7 +636,7 @@ class ClusterTrackingCache(AbstractCache, SupportsStats, SupportsSampling, Suppo
663
636
  self.shutdown()
664
637
 
665
638
 
666
- class TrackingCache(AbstractCache, SupportsStats, SupportsSampling, SupportsClientTracking):
639
+ class TrackingCache(AbstractCache):
667
640
  """
668
641
  An LRU cache that uses server assisted client caching to ensure local cache entries
669
642
  are invalidated if any operations are performed on the keys by another client.
@@ -772,20 +745,22 @@ class TrackingCache(AbstractCache, SupportsStats, SupportsSampling, SupportsClie
772
745
 
773
746
  return None
774
747
 
775
- def get(self, command: bytes, key: bytes, *args: ValueT) -> ResponseType:
748
+ def get(self, command: bytes, key: RedisValueT, *args: RedisValueT) -> ResponseType:
776
749
  assert self.instance
777
750
 
778
751
  return self.instance.get(command, key, *args)
779
752
 
780
- def put(self, command: bytes, key: bytes, *args: ValueT, value: ResponseType) -> None:
753
+ def put(
754
+ self, command: bytes, key: RedisValueT, *args: RedisValueT, value: ResponseType
755
+ ) -> None:
781
756
  if self.instance:
782
757
  self.instance.put(command, key, *args, value=value)
783
758
 
784
- def invalidate(self, *keys: ValueT) -> None:
759
+ def invalidate(self, *keys: RedisValueT) -> None:
785
760
  if self.instance:
786
761
  self.instance.invalidate(*keys)
787
762
 
788
- def feedback(self, command: bytes, key: bytes, *args: ValueT, match: bool) -> None:
763
+ def feedback(self, command: bytes, key: RedisValueT, *args: RedisValueT, match: bool) -> None:
789
764
  if self.instance:
790
765
  self.instance.feedback(command, key, *args, match=match)
791
766
 
@@ -2,6 +2,5 @@ from __future__ import annotations
2
2
 
3
3
  from .basic import Client, Redis
4
4
  from .cluster import RedisCluster
5
- from .keydb import KeyDB, KeyDBCluster
6
5
 
7
- __all__ = ["Client", "Redis", "RedisCluster", "KeyDB", "KeyDBCluster"]
6
+ __all__ = ["Client", "Redis", "RedisCluster"]