coredis 5.5.0__cp314-cp314-macosx_10_13_x86_64.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.
- 22fe76227e35f92ab5c3__mypyc.cpython-314-darwin.so +0 -0
- coredis/__init__.py +42 -0
- coredis/_enum.py +42 -0
- coredis/_json.py +11 -0
- coredis/_packer.cpython-314-darwin.so +0 -0
- coredis/_packer.py +71 -0
- coredis/_protocols.py +50 -0
- coredis/_py_311_typing.py +20 -0
- coredis/_py_312_typing.py +17 -0
- coredis/_sidecar.py +114 -0
- coredis/_utils.cpython-314-darwin.so +0 -0
- coredis/_utils.py +440 -0
- coredis/_version.py +34 -0
- coredis/_version.pyi +1 -0
- coredis/cache.py +801 -0
- coredis/client/__init__.py +6 -0
- coredis/client/basic.py +1240 -0
- coredis/client/cluster.py +1265 -0
- coredis/commands/__init__.py +64 -0
- coredis/commands/_key_spec.py +517 -0
- coredis/commands/_utils.py +108 -0
- coredis/commands/_validators.py +159 -0
- coredis/commands/_wrappers.py +175 -0
- coredis/commands/bitfield.py +110 -0
- coredis/commands/constants.py +662 -0
- coredis/commands/core.py +8484 -0
- coredis/commands/function.py +408 -0
- coredis/commands/monitor.py +168 -0
- coredis/commands/pubsub.py +905 -0
- coredis/commands/request.py +108 -0
- coredis/commands/script.py +296 -0
- coredis/commands/sentinel.py +246 -0
- coredis/config.py +50 -0
- coredis/connection.py +906 -0
- coredis/constants.cpython-314-darwin.so +0 -0
- coredis/constants.py +37 -0
- coredis/credentials.py +45 -0
- coredis/exceptions.py +360 -0
- coredis/experimental/__init__.py +1 -0
- coredis/globals.py +23 -0
- coredis/modules/__init__.py +121 -0
- coredis/modules/autocomplete.py +138 -0
- coredis/modules/base.py +262 -0
- coredis/modules/filters.py +1319 -0
- coredis/modules/graph.py +362 -0
- coredis/modules/json.py +691 -0
- coredis/modules/response/__init__.py +0 -0
- coredis/modules/response/_callbacks/__init__.py +0 -0
- coredis/modules/response/_callbacks/autocomplete.py +42 -0
- coredis/modules/response/_callbacks/graph.py +237 -0
- coredis/modules/response/_callbacks/json.py +21 -0
- coredis/modules/response/_callbacks/search.py +221 -0
- coredis/modules/response/_callbacks/timeseries.py +158 -0
- coredis/modules/response/types.py +179 -0
- coredis/modules/search.py +1089 -0
- coredis/modules/timeseries.py +1139 -0
- coredis/parser.cpython-314-darwin.so +0 -0
- coredis/parser.py +344 -0
- coredis/pipeline.py +1225 -0
- coredis/pool/__init__.py +11 -0
- coredis/pool/basic.py +453 -0
- coredis/pool/cluster.py +517 -0
- coredis/pool/nodemanager.py +340 -0
- coredis/py.typed +0 -0
- coredis/recipes/__init__.py +0 -0
- coredis/recipes/credentials/__init__.py +5 -0
- coredis/recipes/credentials/iam_provider.py +63 -0
- coredis/recipes/locks/__init__.py +5 -0
- coredis/recipes/locks/extend.lua +17 -0
- coredis/recipes/locks/lua_lock.py +281 -0
- coredis/recipes/locks/release.lua +10 -0
- coredis/response/__init__.py +5 -0
- coredis/response/_callbacks/__init__.py +538 -0
- coredis/response/_callbacks/acl.py +32 -0
- coredis/response/_callbacks/cluster.py +183 -0
- coredis/response/_callbacks/command.py +86 -0
- coredis/response/_callbacks/connection.py +31 -0
- coredis/response/_callbacks/geo.py +58 -0
- coredis/response/_callbacks/hash.py +85 -0
- coredis/response/_callbacks/keys.py +59 -0
- coredis/response/_callbacks/module.py +33 -0
- coredis/response/_callbacks/script.py +85 -0
- coredis/response/_callbacks/sentinel.py +179 -0
- coredis/response/_callbacks/server.py +241 -0
- coredis/response/_callbacks/sets.py +44 -0
- coredis/response/_callbacks/sorted_set.py +204 -0
- coredis/response/_callbacks/streams.py +185 -0
- coredis/response/_callbacks/strings.py +70 -0
- coredis/response/_callbacks/vector_sets.py +159 -0
- coredis/response/_utils.py +33 -0
- coredis/response/types.py +416 -0
- coredis/retry.py +233 -0
- coredis/sentinel.py +477 -0
- coredis/stream.py +369 -0
- coredis/tokens.py +2286 -0
- coredis/typing.py +593 -0
- coredis-5.5.0.dist-info/METADATA +211 -0
- coredis-5.5.0.dist-info/RECORD +100 -0
- coredis-5.5.0.dist-info/WHEEL +6 -0
- coredis-5.5.0.dist-info/licenses/LICENSE +23 -0
|
Binary file
|
coredis/__init__.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
coredis
|
|
3
|
+
-------
|
|
4
|
+
|
|
5
|
+
coredis is an async redis client with support for redis server,
|
|
6
|
+
cluster & sentinel.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from coredis._version import __version__
|
|
12
|
+
from coredis.client import Redis, RedisCluster
|
|
13
|
+
from coredis.config import Config
|
|
14
|
+
from coredis.connection import (
|
|
15
|
+
BaseConnection,
|
|
16
|
+
ClusterConnection,
|
|
17
|
+
Connection,
|
|
18
|
+
UnixDomainSocketConnection,
|
|
19
|
+
)
|
|
20
|
+
from coredis.pool import (
|
|
21
|
+
BlockingClusterConnectionPool,
|
|
22
|
+
BlockingConnectionPool,
|
|
23
|
+
ClusterConnectionPool,
|
|
24
|
+
ConnectionPool,
|
|
25
|
+
)
|
|
26
|
+
from coredis.tokens import PureToken
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"Config",
|
|
30
|
+
"Redis",
|
|
31
|
+
"RedisCluster",
|
|
32
|
+
"BaseConnection",
|
|
33
|
+
"Connection",
|
|
34
|
+
"UnixDomainSocketConnection",
|
|
35
|
+
"ClusterConnection",
|
|
36
|
+
"BlockingConnectionPool",
|
|
37
|
+
"ConnectionPool",
|
|
38
|
+
"BlockingClusterConnectionPool",
|
|
39
|
+
"ClusterConnectionPool",
|
|
40
|
+
"PureToken",
|
|
41
|
+
"__version__",
|
|
42
|
+
]
|
coredis/_enum.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import enum
|
|
4
|
+
|
|
5
|
+
from coredis.typing import StringT
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@enum.unique
|
|
9
|
+
class CaseAndEncodingInsensitiveEnum(bytes, enum.Enum):
|
|
10
|
+
__decoded: set[StringT]
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def variants(self) -> set[StringT]:
|
|
14
|
+
if not hasattr(self, "__decoded"):
|
|
15
|
+
decoded = str(self)
|
|
16
|
+
self.__decoded = {
|
|
17
|
+
self.value.lower(), # type: ignore
|
|
18
|
+
self.value, # type: ignore
|
|
19
|
+
decoded.lower(),
|
|
20
|
+
decoded.upper(),
|
|
21
|
+
}
|
|
22
|
+
return self.__decoded
|
|
23
|
+
|
|
24
|
+
def __eq__(self, other: object) -> bool:
|
|
25
|
+
"""
|
|
26
|
+
Since redis tokens are case insensitive allow mixed case
|
|
27
|
+
Additionally allow strings to be passed in instead of
|
|
28
|
+
bytes.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
if other:
|
|
32
|
+
if isinstance(other, self.__class__):
|
|
33
|
+
return bool(self.value == other.value)
|
|
34
|
+
else:
|
|
35
|
+
return other in self.variants
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
def __str__(self) -> str:
|
|
39
|
+
return self.decode("latin-1")
|
|
40
|
+
|
|
41
|
+
def __hash__(self) -> int:
|
|
42
|
+
return hash(self.value)
|
coredis/_json.py
ADDED
|
Binary file
|
coredis/_packer.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from coredis.constants import SYM_CRLF, SYM_DOLLAR, SYM_EMPTY, SYM_STAR
|
|
4
|
+
from coredis.typing import RedisValueT
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Packer:
|
|
8
|
+
def __init__(self, encoding: str):
|
|
9
|
+
self.encoding = encoding
|
|
10
|
+
|
|
11
|
+
def encode(self, value: RedisValueT) -> bytes:
|
|
12
|
+
"""Returns a bytestring representation of the value"""
|
|
13
|
+
if isinstance(value, str):
|
|
14
|
+
return value.encode(self.encoding)
|
|
15
|
+
elif isinstance(value, int):
|
|
16
|
+
return b"%d" % value
|
|
17
|
+
elif isinstance(value, float):
|
|
18
|
+
return b"%.15g" % value
|
|
19
|
+
return value
|
|
20
|
+
|
|
21
|
+
def pack_command(self, command: bytes, *args: RedisValueT) -> list[bytes]:
|
|
22
|
+
"Pack a series of arguments into the Redis protocol"
|
|
23
|
+
output: list[bytes] = []
|
|
24
|
+
# the client might have included 1 or more literal arguments in
|
|
25
|
+
# the command name, e.g., 'CONFIG GET'. The Redis server expects these
|
|
26
|
+
# arguments to be sent separately, so split the first argument
|
|
27
|
+
# manually. All of these arguements get wrapped in the Token class
|
|
28
|
+
# to prevent them from being encoded.
|
|
29
|
+
cleaned_args = args
|
|
30
|
+
if b" " in command:
|
|
31
|
+
cleaned_args = tuple(s for s in command.split()) + cleaned_args
|
|
32
|
+
else:
|
|
33
|
+
cleaned_args = (command,) + cleaned_args
|
|
34
|
+
|
|
35
|
+
buff = SYM_EMPTY.join((SYM_STAR, b"%d" % len(cleaned_args), SYM_CRLF))
|
|
36
|
+
|
|
37
|
+
for arg in cleaned_args:
|
|
38
|
+
if not isinstance(arg, bytes):
|
|
39
|
+
arg = self.encode(arg)
|
|
40
|
+
# to avoid large string mallocs, chunk the command into the
|
|
41
|
+
# output list if we're sending large values
|
|
42
|
+
|
|
43
|
+
if len(buff) > 6000 or len(arg) > 6000:
|
|
44
|
+
buff = SYM_EMPTY.join((buff, SYM_DOLLAR, b"%d" % len(arg), SYM_CRLF))
|
|
45
|
+
output.append(buff)
|
|
46
|
+
output.append(arg)
|
|
47
|
+
buff = SYM_CRLF
|
|
48
|
+
else:
|
|
49
|
+
buff = SYM_EMPTY.join((buff, SYM_DOLLAR, b"%d" % len(arg), SYM_CRLF, arg, SYM_CRLF))
|
|
50
|
+
output.append(buff)
|
|
51
|
+
return output
|
|
52
|
+
|
|
53
|
+
def pack_commands(self, commands: list[tuple[RedisValueT, ...]]) -> list[bytes]:
|
|
54
|
+
output: list[bytes] = []
|
|
55
|
+
command_arguments: list[bytes] = []
|
|
56
|
+
buffer_length = 0
|
|
57
|
+
|
|
58
|
+
for cmd in commands:
|
|
59
|
+
for chunk in self.pack_command(self.encode(cmd[0]), *cmd[1:]):
|
|
60
|
+
command_arguments.append(chunk)
|
|
61
|
+
buffer_length += len(chunk)
|
|
62
|
+
|
|
63
|
+
if buffer_length > 6000:
|
|
64
|
+
output.append(SYM_EMPTY.join(command_arguments))
|
|
65
|
+
buffer_length = 0
|
|
66
|
+
command_arguments = []
|
|
67
|
+
|
|
68
|
+
if command_arguments:
|
|
69
|
+
output.append(SYM_EMPTY.join(command_arguments))
|
|
70
|
+
|
|
71
|
+
return output
|
coredis/_protocols.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from typing_extensions import runtime_checkable
|
|
6
|
+
|
|
7
|
+
from coredis.response._callbacks import NoopCallback
|
|
8
|
+
from coredis.typing import (
|
|
9
|
+
TYPE_CHECKING,
|
|
10
|
+
Awaitable,
|
|
11
|
+
Callable,
|
|
12
|
+
ExecutionParameters,
|
|
13
|
+
Protocol,
|
|
14
|
+
R,
|
|
15
|
+
RedisCommandP,
|
|
16
|
+
ResponseType,
|
|
17
|
+
TypeVar,
|
|
18
|
+
Unpack,
|
|
19
|
+
ValueT,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
T_co = TypeVar("T_co", covariant=True)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from coredis.commands import CommandRequest
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AbstractExecutor(Protocol):
|
|
30
|
+
def execute_command(
|
|
31
|
+
self,
|
|
32
|
+
command: RedisCommandP,
|
|
33
|
+
callback: Callable[..., R] = NoopCallback(),
|
|
34
|
+
**options: Unpack[ExecutionParameters],
|
|
35
|
+
) -> Awaitable[R]: ...
|
|
36
|
+
|
|
37
|
+
def create_request(
|
|
38
|
+
self,
|
|
39
|
+
name: bytes,
|
|
40
|
+
*arguments: ValueT,
|
|
41
|
+
callback: Callable[..., R],
|
|
42
|
+
execution_parameters: ExecutionParameters | None = None,
|
|
43
|
+
) -> CommandRequest[R]: ...
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@runtime_checkable
|
|
47
|
+
class ConnectionP(Protocol):
|
|
48
|
+
decode_responses: bool
|
|
49
|
+
encoding: str
|
|
50
|
+
push_messages: asyncio.Queue[ResponseType]
|
|
@@ -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/_sidecar.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import time
|
|
5
|
+
import weakref
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
from coredis.connection import BaseConnection, Connection
|
|
9
|
+
from coredis.exceptions import ConnectionError
|
|
10
|
+
from coredis.typing import ResponseType, TypeVar
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
import coredis.client
|
|
14
|
+
|
|
15
|
+
SidecarT = TypeVar("SidecarT", bound="Sidecar")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Sidecar:
|
|
19
|
+
"""
|
|
20
|
+
A sidecar to a redis client that reserves a single connection
|
|
21
|
+
and moves any responses from the socket to a FIFO queue
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self, push_message_types: set[bytes], health_check_interval_seconds: int = 5
|
|
26
|
+
) -> None:
|
|
27
|
+
self._client: weakref.ReferenceType[coredis.client.Client[Any]] | None = None
|
|
28
|
+
self.messages: asyncio.Queue[ResponseType] = asyncio.Queue()
|
|
29
|
+
self.connection: Connection | None = None
|
|
30
|
+
self.client_id: int | None = None
|
|
31
|
+
self.read_task: asyncio.Task[None] | None = None
|
|
32
|
+
self.push_message_types = push_message_types
|
|
33
|
+
self.health_check_interval = health_check_interval_seconds
|
|
34
|
+
self.health_check_task: asyncio.Task[None] | None = None
|
|
35
|
+
self.last_checkin: float = 0
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def client(self) -> coredis.client.Client[Any] | None:
|
|
39
|
+
if self._client:
|
|
40
|
+
return self._client()
|
|
41
|
+
return None # noqa
|
|
42
|
+
|
|
43
|
+
async def start(self: SidecarT, client: coredis.client.Client[Any]) -> SidecarT:
|
|
44
|
+
self._client = weakref.ref(client, lambda *_: self.stop())
|
|
45
|
+
if not self.connection and self.client:
|
|
46
|
+
self.connection = await self.client.connection_pool.get_connection()
|
|
47
|
+
self.connection.register_connect_callback(self.on_reconnect)
|
|
48
|
+
await self.connection.connect()
|
|
49
|
+
if self.connection.tracking_client_id: # noqa
|
|
50
|
+
await self.connection.update_tracking_client(False)
|
|
51
|
+
if not self.read_task or self.read_task.done():
|
|
52
|
+
self.read_task = asyncio.create_task(self.__read_loop())
|
|
53
|
+
if not self.health_check_task or self.health_check_task.done():
|
|
54
|
+
self.health_check_task = asyncio.create_task(self.__health_check())
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
def process_message(self, message: ResponseType) -> tuple[ResponseType, ...]:
|
|
58
|
+
return (message,) # noqa
|
|
59
|
+
|
|
60
|
+
def stop(self) -> None:
|
|
61
|
+
try:
|
|
62
|
+
asyncio.get_running_loop()
|
|
63
|
+
if self.read_task and not self.read_task.done():
|
|
64
|
+
self.read_task.cancel()
|
|
65
|
+
if self.health_check_task and not self.health_check_task.done():
|
|
66
|
+
self.health_check_task.cancel()
|
|
67
|
+
except RuntimeError:
|
|
68
|
+
pass
|
|
69
|
+
if self.connection:
|
|
70
|
+
self.connection.disconnect()
|
|
71
|
+
if self.client and self.connection: # noqa
|
|
72
|
+
self.client.connection_pool.release(self.connection)
|
|
73
|
+
self.connection = None
|
|
74
|
+
self.client_id = None
|
|
75
|
+
|
|
76
|
+
def __del__(self) -> None:
|
|
77
|
+
self.stop()
|
|
78
|
+
|
|
79
|
+
async def on_reconnect(self, connection: BaseConnection) -> None:
|
|
80
|
+
self.client_id = connection.client_id
|
|
81
|
+
self.last_checkin = time.monotonic()
|
|
82
|
+
|
|
83
|
+
async def __health_check(self) -> None:
|
|
84
|
+
while True:
|
|
85
|
+
try:
|
|
86
|
+
if self.connection:
|
|
87
|
+
await self.connection.send_command(b"PING")
|
|
88
|
+
await asyncio.sleep(self.health_check_interval)
|
|
89
|
+
except asyncio.CancelledError:
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
async def __read_loop(self) -> None:
|
|
93
|
+
while self.connection:
|
|
94
|
+
try:
|
|
95
|
+
response = await self.connection.fetch_push_message(
|
|
96
|
+
decode=False, push_message_types=self.push_message_types
|
|
97
|
+
)
|
|
98
|
+
self.last_checkin = time.monotonic()
|
|
99
|
+
if response == b"PONG" or b"pong" in response: # type: ignore
|
|
100
|
+
continue
|
|
101
|
+
for m in self.process_message(response):
|
|
102
|
+
self.messages.put_nowait(m)
|
|
103
|
+
except asyncio.CancelledError:
|
|
104
|
+
break
|
|
105
|
+
except ConnectionError:
|
|
106
|
+
if self.client and self.connection:
|
|
107
|
+
self.client.connection_pool.release(self.connection)
|
|
108
|
+
self.connection = None
|
|
109
|
+
|
|
110
|
+
if self.client:
|
|
111
|
+
asyncio.get_running_loop().call_soon(
|
|
112
|
+
asyncio.create_task, self.start(self.client)
|
|
113
|
+
)
|
|
114
|
+
break
|
|
Binary file
|