coredis 5.5.0__cp313-cp313-macosx_11_0_arm64.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.
Files changed (100) hide show
  1. 22fe76227e35f92ab5c3__mypyc.cpython-313-darwin.so +0 -0
  2. coredis/__init__.py +42 -0
  3. coredis/_enum.py +42 -0
  4. coredis/_json.py +11 -0
  5. coredis/_packer.cpython-313-darwin.so +0 -0
  6. coredis/_packer.py +71 -0
  7. coredis/_protocols.py +50 -0
  8. coredis/_py_311_typing.py +20 -0
  9. coredis/_py_312_typing.py +17 -0
  10. coredis/_sidecar.py +114 -0
  11. coredis/_utils.cpython-313-darwin.so +0 -0
  12. coredis/_utils.py +440 -0
  13. coredis/_version.py +34 -0
  14. coredis/_version.pyi +1 -0
  15. coredis/cache.py +801 -0
  16. coredis/client/__init__.py +6 -0
  17. coredis/client/basic.py +1240 -0
  18. coredis/client/cluster.py +1265 -0
  19. coredis/commands/__init__.py +64 -0
  20. coredis/commands/_key_spec.py +517 -0
  21. coredis/commands/_utils.py +108 -0
  22. coredis/commands/_validators.py +159 -0
  23. coredis/commands/_wrappers.py +175 -0
  24. coredis/commands/bitfield.py +110 -0
  25. coredis/commands/constants.py +662 -0
  26. coredis/commands/core.py +8484 -0
  27. coredis/commands/function.py +408 -0
  28. coredis/commands/monitor.py +168 -0
  29. coredis/commands/pubsub.py +905 -0
  30. coredis/commands/request.py +108 -0
  31. coredis/commands/script.py +296 -0
  32. coredis/commands/sentinel.py +246 -0
  33. coredis/config.py +50 -0
  34. coredis/connection.py +906 -0
  35. coredis/constants.cpython-313-darwin.so +0 -0
  36. coredis/constants.py +37 -0
  37. coredis/credentials.py +45 -0
  38. coredis/exceptions.py +360 -0
  39. coredis/experimental/__init__.py +1 -0
  40. coredis/globals.py +23 -0
  41. coredis/modules/__init__.py +121 -0
  42. coredis/modules/autocomplete.py +138 -0
  43. coredis/modules/base.py +262 -0
  44. coredis/modules/filters.py +1319 -0
  45. coredis/modules/graph.py +362 -0
  46. coredis/modules/json.py +691 -0
  47. coredis/modules/response/__init__.py +0 -0
  48. coredis/modules/response/_callbacks/__init__.py +0 -0
  49. coredis/modules/response/_callbacks/autocomplete.py +42 -0
  50. coredis/modules/response/_callbacks/graph.py +237 -0
  51. coredis/modules/response/_callbacks/json.py +21 -0
  52. coredis/modules/response/_callbacks/search.py +221 -0
  53. coredis/modules/response/_callbacks/timeseries.py +158 -0
  54. coredis/modules/response/types.py +179 -0
  55. coredis/modules/search.py +1089 -0
  56. coredis/modules/timeseries.py +1139 -0
  57. coredis/parser.cpython-313-darwin.so +0 -0
  58. coredis/parser.py +344 -0
  59. coredis/pipeline.py +1225 -0
  60. coredis/pool/__init__.py +11 -0
  61. coredis/pool/basic.py +453 -0
  62. coredis/pool/cluster.py +517 -0
  63. coredis/pool/nodemanager.py +340 -0
  64. coredis/py.typed +0 -0
  65. coredis/recipes/__init__.py +0 -0
  66. coredis/recipes/credentials/__init__.py +5 -0
  67. coredis/recipes/credentials/iam_provider.py +63 -0
  68. coredis/recipes/locks/__init__.py +5 -0
  69. coredis/recipes/locks/extend.lua +17 -0
  70. coredis/recipes/locks/lua_lock.py +281 -0
  71. coredis/recipes/locks/release.lua +10 -0
  72. coredis/response/__init__.py +5 -0
  73. coredis/response/_callbacks/__init__.py +538 -0
  74. coredis/response/_callbacks/acl.py +32 -0
  75. coredis/response/_callbacks/cluster.py +183 -0
  76. coredis/response/_callbacks/command.py +86 -0
  77. coredis/response/_callbacks/connection.py +31 -0
  78. coredis/response/_callbacks/geo.py +58 -0
  79. coredis/response/_callbacks/hash.py +85 -0
  80. coredis/response/_callbacks/keys.py +59 -0
  81. coredis/response/_callbacks/module.py +33 -0
  82. coredis/response/_callbacks/script.py +85 -0
  83. coredis/response/_callbacks/sentinel.py +179 -0
  84. coredis/response/_callbacks/server.py +241 -0
  85. coredis/response/_callbacks/sets.py +44 -0
  86. coredis/response/_callbacks/sorted_set.py +204 -0
  87. coredis/response/_callbacks/streams.py +185 -0
  88. coredis/response/_callbacks/strings.py +70 -0
  89. coredis/response/_callbacks/vector_sets.py +159 -0
  90. coredis/response/_utils.py +33 -0
  91. coredis/response/types.py +416 -0
  92. coredis/retry.py +233 -0
  93. coredis/sentinel.py +477 -0
  94. coredis/stream.py +369 -0
  95. coredis/tokens.py +2286 -0
  96. coredis/typing.py +593 -0
  97. coredis-5.5.0.dist-info/METADATA +211 -0
  98. coredis-5.5.0.dist-info/RECORD +100 -0
  99. coredis-5.5.0.dist-info/WHEEL +6 -0
  100. coredis-5.5.0.dist-info/licenses/LICENSE +23 -0
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
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from types import ModuleType
4
+
5
+ json: ModuleType
6
+ try:
7
+ import orjson as json # type: ignore
8
+ except ImportError:
9
+ import json
10
+
11
+ __all__ = ["json"]
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