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.
- coredis/__init__.py +1 -3
- coredis/_packer.py +10 -10
- coredis/_protocols.py +23 -32
- coredis/_py_311_typing.py +20 -0
- coredis/_py_312_typing.py +17 -0
- coredis/_utils.py +49 -51
- coredis/_version.py +3 -3
- coredis/cache.py +57 -82
- coredis/client/__init__.py +1 -2
- coredis/client/basic.py +129 -56
- coredis/client/cluster.py +147 -70
- coredis/commands/__init__.py +27 -7
- coredis/commands/_key_spec.py +11 -10
- coredis/commands/_utils.py +1 -1
- coredis/commands/_validators.py +30 -20
- coredis/commands/_wrappers.py +19 -99
- coredis/commands/bitfield.py +10 -2
- coredis/commands/constants.py +20 -3
- coredis/commands/core.py +1627 -1246
- coredis/commands/function.py +21 -19
- coredis/commands/monitor.py +0 -71
- coredis/commands/pubsub.py +7 -142
- coredis/commands/request.py +108 -0
- coredis/commands/script.py +9 -9
- coredis/commands/sentinel.py +60 -49
- coredis/connection.py +14 -15
- coredis/exceptions.py +2 -2
- coredis/experimental/__init__.py +0 -4
- coredis/globals.py +3 -0
- coredis/modules/autocomplete.py +28 -30
- coredis/modules/base.py +15 -31
- coredis/modules/filters.py +269 -245
- coredis/modules/graph.py +61 -62
- coredis/modules/json.py +172 -140
- coredis/modules/response/_callbacks/autocomplete.py +5 -4
- coredis/modules/response/_callbacks/graph.py +34 -29
- coredis/modules/response/_callbacks/json.py +5 -3
- coredis/modules/response/_callbacks/search.py +49 -53
- coredis/modules/response/_callbacks/timeseries.py +18 -30
- coredis/modules/response/types.py +1 -5
- coredis/modules/search.py +186 -169
- coredis/modules/timeseries.py +184 -164
- coredis/parser.py +6 -19
- coredis/pipeline.py +391 -422
- coredis/pool/basic.py +7 -7
- coredis/pool/cluster.py +3 -3
- coredis/pool/nodemanager.py +10 -3
- coredis/response/_callbacks/__init__.py +76 -57
- coredis/response/_callbacks/acl.py +0 -3
- coredis/response/_callbacks/cluster.py +25 -16
- coredis/response/_callbacks/command.py +8 -6
- coredis/response/_callbacks/connection.py +4 -3
- coredis/response/_callbacks/geo.py +17 -13
- coredis/response/_callbacks/hash.py +13 -11
- coredis/response/_callbacks/keys.py +9 -5
- coredis/response/_callbacks/module.py +2 -3
- coredis/response/_callbacks/script.py +6 -8
- coredis/response/_callbacks/sentinel.py +21 -17
- coredis/response/_callbacks/server.py +36 -14
- coredis/response/_callbacks/sets.py +3 -4
- coredis/response/_callbacks/sorted_set.py +27 -24
- coredis/response/_callbacks/streams.py +22 -13
- coredis/response/_callbacks/strings.py +7 -6
- coredis/response/_callbacks/vector_sets.py +126 -0
- coredis/response/types.py +13 -4
- coredis/sentinel.py +1 -1
- coredis/stream.py +4 -3
- coredis/tokens.py +343 -16
- coredis/typing.py +432 -79
- {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/METADATA +4 -5
- coredis-5.0.0rc1.dist-info/RECORD +95 -0
- coredis/client/keydb.py +0 -336
- coredis/pipeline.pyi +0 -2103
- coredis-4.24.0.dist-info/RECORD +0 -93
- {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/WHEEL +0 -0
- {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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
|
|
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:
|
|
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:
|
|
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[
|
|
53
|
+
def pack_commands(self, commands: list[tuple[RedisValueT, ...]]) -> list[bytes]:
|
|
54
54
|
output: list[bytes] = []
|
|
55
|
-
|
|
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
|
-
|
|
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(
|
|
64
|
+
output.append(SYM_EMPTY.join(command_arguments))
|
|
65
65
|
buffer_length = 0
|
|
66
|
-
|
|
66
|
+
command_arguments = []
|
|
67
67
|
|
|
68
|
-
if
|
|
69
|
-
output.append(SYM_EMPTY.join(
|
|
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
|
-
|
|
34
|
+
def execute_command(
|
|
26
35
|
self,
|
|
27
|
-
command:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
) -> R: ...
|
|
32
|
-
|
|
36
|
+
command: RedisCommandP,
|
|
37
|
+
callback: Callable[..., R] = NoopCallback(),
|
|
38
|
+
**options: Unpack[ExecutionParameters],
|
|
39
|
+
) -> Awaitable[R]: ...
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
class SupportsPipeline(Protocol): # noqa
|
|
36
|
-
async def pipeline(
|
|
41
|
+
def create_request(
|
|
37
42
|
self,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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[
|
|
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[
|
|
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(
|
|
21
|
+
class EncodingInsensitiveDict(UserDict[Any, Any]):
|
|
22
22
|
def __init__(
|
|
23
23
|
self,
|
|
24
|
-
|
|
24
|
+
initial: MutableMapping[Any, Any] | None = None,
|
|
25
25
|
encoding: str = "utf-8",
|
|
26
26
|
):
|
|
27
|
-
|
|
28
|
-
super().__init__(
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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-
|
|
11
|
+
"date": "2025-07-07T11:41:52-0700",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "
|
|
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
|
-
|
|
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:
|
|
74
|
+
def hit(self, key: RedisValueT) -> None:
|
|
77
75
|
self.hits[b(key)] += 1
|
|
78
76
|
|
|
79
|
-
def miss(self, key:
|
|
77
|
+
def miss(self, key: RedisValueT) -> None:
|
|
80
78
|
self.misses[b(key)] += 1
|
|
81
79
|
|
|
82
|
-
def invalidate(self, key:
|
|
80
|
+
def invalidate(self, key: RedisValueT) -> None:
|
|
83
81
|
self.invalidations[b(key)] += 1
|
|
84
82
|
|
|
85
|
-
def mark_dirty(self, key:
|
|
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:
|
|
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(
|
|
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:
|
|
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:
|
|
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) ->
|
|
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
|
|
262
|
+
return None
|
|
291
263
|
|
|
292
264
|
if isinstance(item, LRUCache):
|
|
293
|
-
if item.popitem():
|
|
294
|
-
return
|
|
295
|
-
self.__cache.popitem(last=False)
|
|
296
|
-
|
|
297
|
-
return
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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:
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
|
coredis/client/__init__.py
CHANGED
|
@@ -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"
|
|
6
|
+
__all__ = ["Client", "Redis", "RedisCluster"]
|