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.
- 22fe76227e35f92ab5c3__mypyc.cpython-313-darwin.so +0 -0
- coredis/__init__.py +42 -0
- coredis/_enum.py +42 -0
- coredis/_json.py +11 -0
- coredis/_packer.cpython-313-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-313-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-313-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-313-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
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
from typing import Any, cast
|
|
5
|
+
|
|
6
|
+
from coredis._protocols import AbstractExecutor
|
|
7
|
+
from coredis.typing import (
|
|
8
|
+
Awaitable,
|
|
9
|
+
Callable,
|
|
10
|
+
ExecutionParameters,
|
|
11
|
+
Generator,
|
|
12
|
+
Serializable,
|
|
13
|
+
TypeAdapter,
|
|
14
|
+
TypeVar,
|
|
15
|
+
ValueT,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
#: Covariant type used for generalizing :class:`~coredis.command.CommandRequest`
|
|
19
|
+
CommandResponseT = TypeVar("CommandResponseT", covariant=True)
|
|
20
|
+
|
|
21
|
+
TransformedResponse = TypeVar("TransformedResponse")
|
|
22
|
+
empty_adapter = TypeAdapter()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CommandRequest(Awaitable[CommandResponseT]):
|
|
26
|
+
response: Awaitable[CommandResponseT]
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
client: AbstractExecutor,
|
|
31
|
+
name: bytes,
|
|
32
|
+
*arguments: ValueT,
|
|
33
|
+
callback: Callable[..., CommandResponseT],
|
|
34
|
+
execution_parameters: ExecutionParameters | None = None,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""
|
|
37
|
+
The default command request object which is returned by all
|
|
38
|
+
methods mirroring redis commands.
|
|
39
|
+
|
|
40
|
+
:param client: The instance of the :class:`coredis.Redis` that
|
|
41
|
+
will be used to call :meth:`~coredis.Redis.execute_command`
|
|
42
|
+
:param name: The name of the command
|
|
43
|
+
:param arguments: All arguments (in redis format) to be passed to the command
|
|
44
|
+
:param callback: The callback to be used to transform the RESP response
|
|
45
|
+
:param execution_parameters: Any additional parameters to be passed to
|
|
46
|
+
:meth:`coredis.Redis.execute_command`
|
|
47
|
+
"""
|
|
48
|
+
self.name = name
|
|
49
|
+
self.callback = callback
|
|
50
|
+
self.execution_parameters = execution_parameters or {}
|
|
51
|
+
self.client: AbstractExecutor = client
|
|
52
|
+
self.arguments = tuple(
|
|
53
|
+
self.type_adapter.serialize(k) if isinstance(k, Serializable) else k for k in arguments
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def run(self) -> Awaitable[CommandResponseT]:
|
|
57
|
+
if not hasattr(self, "response"):
|
|
58
|
+
self.response = self.client.execute_command(
|
|
59
|
+
self, self.callback, **self.execution_parameters
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return self.response
|
|
63
|
+
|
|
64
|
+
def transform(
|
|
65
|
+
self, transformer: type[TransformedResponse]
|
|
66
|
+
) -> CommandRequest[TransformedResponse]:
|
|
67
|
+
"""
|
|
68
|
+
:param transformer: A type that was registered with the client
|
|
69
|
+
using :meth:`~coredis.typing.TypeAdapter.register_deserializer`
|
|
70
|
+
or decorated by :meth:`~coredis.typing.TypeAdapter.deserializer`
|
|
71
|
+
|
|
72
|
+
:return: a command request object that when awaited will return the
|
|
73
|
+
transformed response
|
|
74
|
+
|
|
75
|
+
For example when used with a redis command::
|
|
76
|
+
|
|
77
|
+
client = coredis.Redis(....)
|
|
78
|
+
@client.type_adapter.deserializer
|
|
79
|
+
def _(value: bytes) -> int:
|
|
80
|
+
return int(value)
|
|
81
|
+
|
|
82
|
+
await client.set("fubar", 1)
|
|
83
|
+
raw: bytes = await client.get("fubar")
|
|
84
|
+
int_value: int = await client.get("fubar").transform(int)
|
|
85
|
+
"""
|
|
86
|
+
transform_func = functools.partial(
|
|
87
|
+
self.type_adapter.deserialize,
|
|
88
|
+
return_type=transformer,
|
|
89
|
+
)
|
|
90
|
+
return cast(type[CommandRequest[TransformedResponse]], self.__class__)(
|
|
91
|
+
self.client,
|
|
92
|
+
self.name,
|
|
93
|
+
*self.arguments,
|
|
94
|
+
callback=lambda resp, **kwargs: transform_func(self.callback(resp, **kwargs)),
|
|
95
|
+
execution_parameters=self.execution_parameters,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def type_adapter(self) -> TypeAdapter:
|
|
100
|
+
from coredis.client import Client
|
|
101
|
+
|
|
102
|
+
if isinstance(self.client, Client):
|
|
103
|
+
return self.client.type_adapter
|
|
104
|
+
|
|
105
|
+
return empty_adapter
|
|
106
|
+
|
|
107
|
+
def __await__(self) -> Generator[Any, Any, CommandResponseT]:
|
|
108
|
+
return self.run().__await__()
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import hashlib
|
|
5
|
+
import inspect
|
|
6
|
+
import itertools
|
|
7
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
8
|
+
|
|
9
|
+
from deprecated.sphinx import versionadded
|
|
10
|
+
|
|
11
|
+
from coredis._utils import b
|
|
12
|
+
from coredis.exceptions import NoScriptError
|
|
13
|
+
from coredis.retry import ConstantRetryPolicy, retryable
|
|
14
|
+
from coredis.typing import (
|
|
15
|
+
AnyStr,
|
|
16
|
+
Awaitable,
|
|
17
|
+
Callable,
|
|
18
|
+
Generic,
|
|
19
|
+
KeyT,
|
|
20
|
+
P,
|
|
21
|
+
Parameters,
|
|
22
|
+
R,
|
|
23
|
+
RedisValueT,
|
|
24
|
+
ResponseType,
|
|
25
|
+
StringT,
|
|
26
|
+
ValueT,
|
|
27
|
+
add_runtime_checks,
|
|
28
|
+
safe_beartype,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
import coredis.client
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Script(Generic[AnyStr]):
|
|
36
|
+
"""
|
|
37
|
+
An executable Lua script object returned by :meth:`coredis.Redis.register_script`.
|
|
38
|
+
Instances of the class are callable and take arguments in the same shape
|
|
39
|
+
as :meth:`coredis.Redis.evalsha` or :meth:`coredis.Redis.eval`
|
|
40
|
+
(i.e a list of :paramref:`~__call__.keys` and :paramref:`~__call__.args`).
|
|
41
|
+
|
|
42
|
+
Example::
|
|
43
|
+
|
|
44
|
+
client = coredis.Redis()
|
|
45
|
+
await client.set("test", "co")
|
|
46
|
+
concat = client.register_script("return redis.call('GET', KEYS[1]) + ARGV[1]")
|
|
47
|
+
assert await concat(['test'], ['redis']) == "coredis"
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
#: SHA of this script once it's registered with the redis server
|
|
51
|
+
sha: AnyStr
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
registered_client: coredis.client.Client[AnyStr] | None = None,
|
|
56
|
+
script: StringT | None = None,
|
|
57
|
+
readonly: bool = False,
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
:param client: The client to use for executing the lua script. If
|
|
61
|
+
this is ``None`` the client will have to be provided when invoking
|
|
62
|
+
the script using :meth:`__call__` with the :paramref:`__call__.client`
|
|
63
|
+
parameter.
|
|
64
|
+
:param script: The lua script that will be used by :meth:`__call__`
|
|
65
|
+
:param readonly: If ``True`` the script will be called with
|
|
66
|
+
:meth:`coredis.Redis.evalsha_ro` instead of :meth:`coredis.Redis.evalsha`
|
|
67
|
+
"""
|
|
68
|
+
self.registered_client: coredis.client.Client[AnyStr] | None = registered_client
|
|
69
|
+
self.script: StringT
|
|
70
|
+
if not script:
|
|
71
|
+
raise RuntimeError("No script provided")
|
|
72
|
+
self.script = script
|
|
73
|
+
self.sha = hashlib.sha1(b(script)).hexdigest() # type: ignore
|
|
74
|
+
self.readonly = readonly
|
|
75
|
+
|
|
76
|
+
def __call__(
|
|
77
|
+
self,
|
|
78
|
+
keys: Parameters[KeyT] | None = None,
|
|
79
|
+
args: Parameters[ValueT] | None = None,
|
|
80
|
+
client: coredis.client.Client[AnyStr] | None = None,
|
|
81
|
+
readonly: bool | None = None,
|
|
82
|
+
) -> Awaitable[ResponseType]:
|
|
83
|
+
"""
|
|
84
|
+
Executes the script registered in :paramref:`Script.script` using
|
|
85
|
+
:meth:`coredis.Redis.evalsha`. Additionally, if the script was not yet
|
|
86
|
+
registered on the instance, it will automatically do that as well
|
|
87
|
+
and cache the sha at :data:`Script.sha`
|
|
88
|
+
|
|
89
|
+
:param keys: The keys this script will reference
|
|
90
|
+
:param args: The arguments expected by the script
|
|
91
|
+
:param client: The redis client to use instead of :paramref:`Script.client`
|
|
92
|
+
:param readonly: If ``True`` forces the script to be called with
|
|
93
|
+
:meth:`coredis.Redis.evalsha_ro`
|
|
94
|
+
"""
|
|
95
|
+
from coredis.pipeline import Pipeline
|
|
96
|
+
|
|
97
|
+
if client is None:
|
|
98
|
+
client = self.registered_client
|
|
99
|
+
if not client:
|
|
100
|
+
raise RuntimeError(
|
|
101
|
+
"This instance is not bound to a redis client."
|
|
102
|
+
"Please provide a valid instance to execute the script with"
|
|
103
|
+
)
|
|
104
|
+
if readonly is None:
|
|
105
|
+
readonly = self.readonly
|
|
106
|
+
|
|
107
|
+
method = client.evalsha_ro if readonly else client.evalsha
|
|
108
|
+
|
|
109
|
+
# make sure the Redis server knows about the script
|
|
110
|
+
if isinstance(client, Pipeline):
|
|
111
|
+
# make sure this script is good to go on pipeline
|
|
112
|
+
cast(Pipeline[AnyStr], client).scripts.add(self)
|
|
113
|
+
return method(self.sha, keys=keys, args=args)
|
|
114
|
+
else:
|
|
115
|
+
return retryable(
|
|
116
|
+
ConstantRetryPolicy((NoScriptError,), 1, 0),
|
|
117
|
+
failure_hook=lambda _: client.script_load(self.script),
|
|
118
|
+
)(method)(self.sha, keys=keys, args=args)
|
|
119
|
+
|
|
120
|
+
async def execute(
|
|
121
|
+
self,
|
|
122
|
+
keys: Parameters[KeyT] | None = None,
|
|
123
|
+
args: Parameters[ValueT] | None = None,
|
|
124
|
+
client: coredis.client.Client[AnyStr] | None = None,
|
|
125
|
+
readonly: bool | None = None,
|
|
126
|
+
) -> ResponseType:
|
|
127
|
+
"""
|
|
128
|
+
Executes the script registered in :paramref:`Script.script`
|
|
129
|
+
|
|
130
|
+
:meta private:
|
|
131
|
+
"""
|
|
132
|
+
return await self(keys, args, client, readonly)
|
|
133
|
+
|
|
134
|
+
@versionadded(version="3.5.0")
|
|
135
|
+
def wraps(
|
|
136
|
+
self,
|
|
137
|
+
key_spec: list[str] | None = None,
|
|
138
|
+
param_is_key: Callable[[inspect.Parameter], bool] = lambda p: (
|
|
139
|
+
p.annotation in {"KeyT", KeyT}
|
|
140
|
+
),
|
|
141
|
+
client_arg: str | None = None,
|
|
142
|
+
runtime_checks: bool = False,
|
|
143
|
+
readonly: bool | None = None,
|
|
144
|
+
) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]:
|
|
145
|
+
"""
|
|
146
|
+
Decorator for wrapping a regular python function, method or classmethod
|
|
147
|
+
signature with a :class:`~coredis.commands.script.Script`. This allows
|
|
148
|
+
exposing a strict signature instead of that which :meth:`__call__` provides.
|
|
149
|
+
The callable being decorated should **not** have an implementation as
|
|
150
|
+
it will never be called.
|
|
151
|
+
|
|
152
|
+
The main objective of the decorator is to allow you to have strict (and type safe)
|
|
153
|
+
signatures for wrappers for lua scripts. Internally the decorator separates
|
|
154
|
+
``keys`` from ``args`` before calling :meth:`coredis.Redis.evalsha`. Mapping the
|
|
155
|
+
decorated methods arguments to key providers is done either by using :paramref:`key_spec`
|
|
156
|
+
or :paramref:`param_is_key`. All other paramters of the decorated function are assumed
|
|
157
|
+
to be ``args`` consumed by the lua script.
|
|
158
|
+
|
|
159
|
+
By default the decorated method is bound to the :class:`coredis.client.Redis`
|
|
160
|
+
or :class:`coredis.client.RedisCluster` instance that the :class:`Script` instance
|
|
161
|
+
was instantiated with. This may however not be the instance you want to eventually
|
|
162
|
+
execute the method with. For such scenarios the decorated method can accept an additional
|
|
163
|
+
parameter which has the name declared by :paramref:`client_arg`).
|
|
164
|
+
|
|
165
|
+
The following example executes the script with the ``client`` instance used
|
|
166
|
+
to register the script. The ``key`` parameter is detected as a key provider
|
|
167
|
+
as it is annotated with the :data:`coredis.typing.KeyT` type, and ``value`` is
|
|
168
|
+
passed to redis as an ``arg``::
|
|
169
|
+
|
|
170
|
+
import coredis
|
|
171
|
+
from coredis.typing import KeyT, RedisValueT
|
|
172
|
+
from typing import List
|
|
173
|
+
|
|
174
|
+
client = coredis.Redis()
|
|
175
|
+
@client.register_script("return {KEYS[1], ARGV[1]}").wraps()
|
|
176
|
+
async def echo_key_value(key: KeyT, value: RedisValueT) -> List[RedisValueT]: ...
|
|
177
|
+
|
|
178
|
+
k, v = await echo_key_value("co", "redis")
|
|
179
|
+
# (b"co", b"redis")
|
|
180
|
+
|
|
181
|
+
Alternatively, the following example builds a class method that requires
|
|
182
|
+
the ``client`` to be passed in explicitly::
|
|
183
|
+
|
|
184
|
+
from coredis import Redis
|
|
185
|
+
from coredis.commands import Script
|
|
186
|
+
|
|
187
|
+
class ScriptProvider:
|
|
188
|
+
@classmethod
|
|
189
|
+
@Script(script="return KEYS[1]").wraps(
|
|
190
|
+
key_spec=["key"],
|
|
191
|
+
client_arg="client"
|
|
192
|
+
)
|
|
193
|
+
def echo_key(cls, client, key): ...
|
|
194
|
+
|
|
195
|
+
@classmethod
|
|
196
|
+
@Script(script="return ARGS[1]").wraps(
|
|
197
|
+
client_arg="client"
|
|
198
|
+
)
|
|
199
|
+
def echo_arg(cls, client, value): ...
|
|
200
|
+
|
|
201
|
+
echoed = await ScriptProvider.echo_key(Redis(), "coredis")
|
|
202
|
+
# b"coredis"
|
|
203
|
+
echoed = await ScriptProvider.echo_value(Redis(), "coredis")
|
|
204
|
+
# b"coredis"
|
|
205
|
+
|
|
206
|
+
:param key_spec: list of parameters of the decorated method that will
|
|
207
|
+
be passed as the :paramref:`keys` argument to :meth:`__call__`. If provided
|
|
208
|
+
this parameter takes precedence over using :paramref:`param_is_key` to determine if
|
|
209
|
+
a parameter is a key provider.
|
|
210
|
+
:param param_is_key: a callable that accepts a single argument of type
|
|
211
|
+
:class:`inspect.Parameter` and returns ``True`` if the parameter points
|
|
212
|
+
to a key that should be appended to the :paramref:`__call__.keys` argument
|
|
213
|
+
of :meth:`__call__`. The default implementation marks a parameter as a key
|
|
214
|
+
provider if it is of type :data:`coredis.typing.KeyT` and is only used if
|
|
215
|
+
:paramref:`key_spec` is ``None``.
|
|
216
|
+
:param client_arg: The parameter of the decorator that will contain a client instance
|
|
217
|
+
to be used to execute the script.
|
|
218
|
+
:param runtime_checks: Whether to enable runtime type checking of input arguments
|
|
219
|
+
and return values. (requires :pypi:`beartype`). If :data:`False` the function will
|
|
220
|
+
still get runtime type checking if the environment configuration ``COREDIS_RUNTIME_CHECKS``
|
|
221
|
+
is set - for details see :ref:`handbook/typing:runtime type checking`.
|
|
222
|
+
:param readonly: If ``True`` forces this script to be called with
|
|
223
|
+
:meth:`coredis.Redis.evalsha_ro`
|
|
224
|
+
|
|
225
|
+
:return: A function that has a signature mirroring the decorated function.
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
def wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[R]]:
|
|
229
|
+
sig = inspect.signature(func)
|
|
230
|
+
first_arg = list(sig.parameters.keys())[0]
|
|
231
|
+
runtime_check_wrapper = add_runtime_checks if not runtime_checks else safe_beartype
|
|
232
|
+
script_instance = self
|
|
233
|
+
key_params = (
|
|
234
|
+
key_spec if key_spec else [n for n, p in sig.parameters.items() if param_is_key(p)]
|
|
235
|
+
)
|
|
236
|
+
arg_fetch: dict[str, Callable[..., Parameters[Any]]] = {
|
|
237
|
+
n: (
|
|
238
|
+
(lambda v: [v])
|
|
239
|
+
if p.kind
|
|
240
|
+
in {
|
|
241
|
+
inspect.Parameter.POSITIONAL_ONLY,
|
|
242
|
+
inspect.Parameter.KEYWORD_ONLY,
|
|
243
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
244
|
+
}
|
|
245
|
+
else (
|
|
246
|
+
(lambda v: list(itertools.chain.from_iterable(v.items())))
|
|
247
|
+
if p.kind == inspect.Parameter.VAR_KEYWORD
|
|
248
|
+
else lambda v: list(v)
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
for n, p in sig.parameters.items()
|
|
252
|
+
}
|
|
253
|
+
if first_arg in {"self", "cls"}:
|
|
254
|
+
arg_fetch.pop(first_arg)
|
|
255
|
+
if client_arg:
|
|
256
|
+
arg_fetch.pop(client_arg)
|
|
257
|
+
|
|
258
|
+
def split_args(
|
|
259
|
+
bound_arguments: inspect.BoundArguments,
|
|
260
|
+
) -> tuple[
|
|
261
|
+
Parameters[KeyT],
|
|
262
|
+
Parameters[RedisValueT],
|
|
263
|
+
coredis.client.Client[AnyStr] | None,
|
|
264
|
+
]:
|
|
265
|
+
bound_arguments.apply_defaults()
|
|
266
|
+
arguments = bound_arguments.arguments
|
|
267
|
+
keys: list[KeyT] = []
|
|
268
|
+
args: list[RedisValueT] = []
|
|
269
|
+
for name in sig.parameters:
|
|
270
|
+
if name not in arg_fetch:
|
|
271
|
+
continue
|
|
272
|
+
value = arg_fetch[name](arguments[name])
|
|
273
|
+
if name in key_params:
|
|
274
|
+
keys.extend(value)
|
|
275
|
+
else:
|
|
276
|
+
args.extend(value)
|
|
277
|
+
return (
|
|
278
|
+
keys,
|
|
279
|
+
args,
|
|
280
|
+
arguments.get(client_arg) if client_arg else None,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
@runtime_check_wrapper
|
|
284
|
+
@functools.wraps(func)
|
|
285
|
+
async def __inner(
|
|
286
|
+
*args: P.args,
|
|
287
|
+
**kwargs: P.kwargs,
|
|
288
|
+
) -> R:
|
|
289
|
+
keys, arguments, client = split_args(sig.bind(*args, **kwargs))
|
|
290
|
+
# TODO: atleast lie with a cast.
|
|
291
|
+
# mypy doesn't like the cast
|
|
292
|
+
return await script_instance(keys, arguments, client, readonly) # type: ignore
|
|
293
|
+
|
|
294
|
+
return __inner
|
|
295
|
+
|
|
296
|
+
return wrapper
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from coredis.response._callbacks import (
|
|
4
|
+
AnyStrCallback,
|
|
5
|
+
DictCallback,
|
|
6
|
+
IntCallback,
|
|
7
|
+
SimpleStringCallback,
|
|
8
|
+
)
|
|
9
|
+
from coredis.response._callbacks.sentinel import (
|
|
10
|
+
GetPrimaryCallback,
|
|
11
|
+
PrimariesCallback,
|
|
12
|
+
PrimaryCallback,
|
|
13
|
+
SentinelInfoCallback,
|
|
14
|
+
SentinelsStateCallback,
|
|
15
|
+
)
|
|
16
|
+
from coredis.typing import (
|
|
17
|
+
AnyStr,
|
|
18
|
+
RedisValueT,
|
|
19
|
+
ResponsePrimitive,
|
|
20
|
+
StringT,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from . import CommandMixin
|
|
24
|
+
from ._wrappers import redis_command
|
|
25
|
+
from .constants import CommandName
|
|
26
|
+
from .request import CommandRequest
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SentinelCommands(CommandMixin[AnyStr]):
|
|
30
|
+
@redis_command(
|
|
31
|
+
CommandName.SENTINEL_CKQUORUM,
|
|
32
|
+
)
|
|
33
|
+
def sentinel_ckquorum(self, service_name: StringT) -> CommandRequest[bool]:
|
|
34
|
+
return CommandRequest(
|
|
35
|
+
self,
|
|
36
|
+
CommandName.SENTINEL_CKQUORUM,
|
|
37
|
+
service_name,
|
|
38
|
+
callback=SimpleStringCallback(prefix_match=True),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@redis_command(CommandName.SENTINEL_CONFIG_GET, version_introduced="6.2.0")
|
|
42
|
+
def sentinel_config_get(self, name: RedisValueT) -> CommandRequest[dict[AnyStr, AnyStr]]:
|
|
43
|
+
"""
|
|
44
|
+
Get the current value of a global Sentinel configuration parameter.
|
|
45
|
+
The specified name may be a wildcard, similar to :meth:`config_get`
|
|
46
|
+
"""
|
|
47
|
+
return CommandRequest(
|
|
48
|
+
self,
|
|
49
|
+
CommandName.SENTINEL_CONFIG_GET,
|
|
50
|
+
name,
|
|
51
|
+
callback=DictCallback[AnyStr, AnyStr](),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
@redis_command(CommandName.SENTINEL_CONFIG_SET, version_introduced="6.2")
|
|
55
|
+
def sentinel_config_set(self, name: RedisValueT, value: RedisValueT) -> CommandRequest[bool]:
|
|
56
|
+
"""
|
|
57
|
+
Set the value of a global Sentinel configuration parameter
|
|
58
|
+
"""
|
|
59
|
+
return CommandRequest(
|
|
60
|
+
self,
|
|
61
|
+
CommandName.SENTINEL_CONFIG_SET,
|
|
62
|
+
name,
|
|
63
|
+
value,
|
|
64
|
+
callback=SimpleStringCallback(),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@redis_command(
|
|
68
|
+
CommandName.SENTINEL_GET_MASTER_ADDR_BY_NAME,
|
|
69
|
+
)
|
|
70
|
+
def sentinel_get_master_addr_by_name(
|
|
71
|
+
self, service_name: StringT
|
|
72
|
+
) -> CommandRequest[tuple[str, int] | None]:
|
|
73
|
+
"""
|
|
74
|
+
Returns a (host, port) pair for the given :paramref:`service_name`
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
return CommandRequest(
|
|
78
|
+
self,
|
|
79
|
+
CommandName.SENTINEL_GET_MASTER_ADDR_BY_NAME,
|
|
80
|
+
service_name,
|
|
81
|
+
callback=GetPrimaryCallback(),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@redis_command(
|
|
85
|
+
CommandName.SENTINEL_FAILOVER,
|
|
86
|
+
)
|
|
87
|
+
def sentinel_failover(self, service_name: StringT) -> CommandRequest[bool]:
|
|
88
|
+
"""
|
|
89
|
+
Force a failover as if the master was not reachable, and without asking
|
|
90
|
+
for agreement to other Sentinels
|
|
91
|
+
"""
|
|
92
|
+
return CommandRequest(
|
|
93
|
+
self, CommandName.SENTINEL_FAILOVER, service_name, callback=SimpleStringCallback()
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
@redis_command(CommandName.SENTINEL_FLUSHCONFIG)
|
|
97
|
+
def sentinel_flushconfig(self) -> CommandRequest[bool]:
|
|
98
|
+
"""
|
|
99
|
+
Force Sentinel to rewrite its configuration on disk, including the current Sentinel state.
|
|
100
|
+
"""
|
|
101
|
+
return CommandRequest(
|
|
102
|
+
self, CommandName.SENTINEL_FLUSHCONFIG, callback=SimpleStringCallback()
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
@redis_command(CommandName.SENTINEL_INFO_CACHE)
|
|
106
|
+
def sentinel_infocache(
|
|
107
|
+
self, *nodenames: StringT
|
|
108
|
+
) -> CommandRequest[dict[AnyStr, dict[int, dict[str, ResponsePrimitive]]]]:
|
|
109
|
+
"""
|
|
110
|
+
Return cached INFO output from masters and replicas.
|
|
111
|
+
"""
|
|
112
|
+
return CommandRequest(
|
|
113
|
+
self,
|
|
114
|
+
CommandName.SENTINEL_INFO_CACHE,
|
|
115
|
+
*nodenames,
|
|
116
|
+
callback=SentinelInfoCallback[AnyStr](),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@redis_command(
|
|
120
|
+
CommandName.SENTINEL_MASTER,
|
|
121
|
+
)
|
|
122
|
+
def sentinel_master(
|
|
123
|
+
self, service_name: StringT
|
|
124
|
+
) -> CommandRequest[dict[str, ResponsePrimitive]]:
|
|
125
|
+
"""Returns a dictionary containing the specified masters state."""
|
|
126
|
+
|
|
127
|
+
return CommandRequest(
|
|
128
|
+
self, CommandName.SENTINEL_MASTER, service_name, callback=PrimaryCallback()
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
@redis_command(
|
|
132
|
+
CommandName.SENTINEL_MASTERS,
|
|
133
|
+
)
|
|
134
|
+
def sentinel_masters(self) -> CommandRequest[dict[str, dict[str, ResponsePrimitive]]]:
|
|
135
|
+
"""Returns a list of dictionaries containing each master's state."""
|
|
136
|
+
|
|
137
|
+
return CommandRequest(self, CommandName.SENTINEL_MASTERS, callback=PrimariesCallback())
|
|
138
|
+
|
|
139
|
+
@redis_command(
|
|
140
|
+
CommandName.SENTINEL_MONITOR,
|
|
141
|
+
)
|
|
142
|
+
def sentinel_monitor(
|
|
143
|
+
self, name: RedisValueT, ip: RedisValueT, port: int, quorum: int
|
|
144
|
+
) -> CommandRequest[bool]:
|
|
145
|
+
"""Adds a new master to Sentinel to be monitored"""
|
|
146
|
+
|
|
147
|
+
return CommandRequest(
|
|
148
|
+
self,
|
|
149
|
+
CommandName.SENTINEL_MONITOR,
|
|
150
|
+
name,
|
|
151
|
+
ip,
|
|
152
|
+
port,
|
|
153
|
+
quorum,
|
|
154
|
+
callback=SimpleStringCallback(),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
@redis_command(CommandName.SENTINEL_MYID, version_introduced="6.2.0")
|
|
158
|
+
def sentinel_myid(self) -> CommandRequest[AnyStr]:
|
|
159
|
+
"""Return the ID of the Sentinel instance"""
|
|
160
|
+
|
|
161
|
+
return CommandRequest(self, CommandName.SENTINEL_MYID, callback=AnyStrCallback[AnyStr]())
|
|
162
|
+
|
|
163
|
+
@redis_command(
|
|
164
|
+
CommandName.SENTINEL_REMOVE,
|
|
165
|
+
)
|
|
166
|
+
def sentinel_remove(self, name: RedisValueT) -> CommandRequest[bool]:
|
|
167
|
+
"""Removes a master from Sentinel's monitoring"""
|
|
168
|
+
|
|
169
|
+
return CommandRequest(
|
|
170
|
+
self, CommandName.SENTINEL_REMOVE, name, callback=SimpleStringCallback()
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
@redis_command(
|
|
174
|
+
CommandName.SENTINEL_SENTINELS,
|
|
175
|
+
)
|
|
176
|
+
def sentinel_sentinels(
|
|
177
|
+
self, service_name: StringT
|
|
178
|
+
) -> CommandRequest[tuple[dict[str, ResponsePrimitive], ...]]:
|
|
179
|
+
"""Returns a list of sentinels for :paramref:`service_name`"""
|
|
180
|
+
|
|
181
|
+
return CommandRequest(
|
|
182
|
+
self,
|
|
183
|
+
CommandName.SENTINEL_SENTINELS,
|
|
184
|
+
service_name,
|
|
185
|
+
callback=SentinelsStateCallback(),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
@redis_command(
|
|
189
|
+
CommandName.SENTINEL_SET,
|
|
190
|
+
)
|
|
191
|
+
def sentinel_set(
|
|
192
|
+
self, name: RedisValueT, option: RedisValueT, value: RedisValueT
|
|
193
|
+
) -> CommandRequest[bool]:
|
|
194
|
+
"""Sets Sentinel monitoring parameters for a given master"""
|
|
195
|
+
|
|
196
|
+
return CommandRequest(
|
|
197
|
+
self,
|
|
198
|
+
CommandName.SENTINEL_SET,
|
|
199
|
+
name,
|
|
200
|
+
option,
|
|
201
|
+
value,
|
|
202
|
+
callback=SimpleStringCallback(),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
@redis_command(
|
|
206
|
+
CommandName.SENTINEL_SLAVES,
|
|
207
|
+
)
|
|
208
|
+
def sentinel_slaves(
|
|
209
|
+
self, service_name: StringT
|
|
210
|
+
) -> CommandRequest[tuple[dict[str, ResponsePrimitive], ...]]:
|
|
211
|
+
"""Returns a list of slaves for paramref:`service_name`"""
|
|
212
|
+
|
|
213
|
+
return CommandRequest(
|
|
214
|
+
self, CommandName.SENTINEL_SLAVES, service_name, callback=SentinelsStateCallback()
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
@redis_command(
|
|
218
|
+
CommandName.SENTINEL_REPLICAS,
|
|
219
|
+
)
|
|
220
|
+
def sentinel_replicas(
|
|
221
|
+
self, service_name: StringT
|
|
222
|
+
) -> CommandRequest[tuple[dict[str, ResponsePrimitive], ...]]:
|
|
223
|
+
"""Returns a list of replicas for :paramref:`service_name`"""
|
|
224
|
+
|
|
225
|
+
return CommandRequest(
|
|
226
|
+
self,
|
|
227
|
+
CommandName.SENTINEL_REPLICAS,
|
|
228
|
+
service_name,
|
|
229
|
+
callback=SentinelsStateCallback(),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
@redis_command(CommandName.SENTINEL_RESET)
|
|
233
|
+
def sentinel_reset(self, pattern: StringT) -> CommandRequest[int]:
|
|
234
|
+
"""
|
|
235
|
+
Reset all the masters with matching name.
|
|
236
|
+
The pattern argument is a glob-style pattern.
|
|
237
|
+
The reset process clears any previous state in a master (including a
|
|
238
|
+
failover in progress), and removes every replica and sentinel already
|
|
239
|
+
discovered and associated with the master.
|
|
240
|
+
"""
|
|
241
|
+
return CommandRequest(
|
|
242
|
+
self,
|
|
243
|
+
CommandName.SENTINEL_RESET,
|
|
244
|
+
pattern,
|
|
245
|
+
callback=IntCallback(),
|
|
246
|
+
)
|
coredis/config.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class __Config:
|
|
7
|
+
def __init__(self) -> None:
|
|
8
|
+
self.__optimized: bool = False
|
|
9
|
+
|
|
10
|
+
@property
|
|
11
|
+
def runtime_checks(self) -> bool:
|
|
12
|
+
"""
|
|
13
|
+
Whether runtime type checks are to be enabled.
|
|
14
|
+
Can be enabled by setting the environment variable ``COREDIS_RUNTIME_CHECKS`` to ``true``
|
|
15
|
+
"""
|
|
16
|
+
return os.environ.get("COREDIS_RUNTIME_CHECKS", "").lower() in [
|
|
17
|
+
"1",
|
|
18
|
+
"true",
|
|
19
|
+
"t",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def optimized(self) -> bool:
|
|
24
|
+
"""
|
|
25
|
+
When ``optimized`` is ``True`` most runtime validations will be disabled.
|
|
26
|
+
This can be enabled in any of the following ways:
|
|
27
|
+
|
|
28
|
+
- By running python in optimized mode using the ``-O`` flag
|
|
29
|
+
- By setting the environment variable ``COREDIS_OPTIMIZED`` to ``true``
|
|
30
|
+
- By explicitly setting ``coredis.Config.optimized = True``
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
return (
|
|
34
|
+
not __debug__
|
|
35
|
+
or os.environ.get("COREDIS_OPTIMIZED", "").lower()
|
|
36
|
+
in [
|
|
37
|
+
"1",
|
|
38
|
+
"true",
|
|
39
|
+
"t",
|
|
40
|
+
]
|
|
41
|
+
or self.__optimized
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@optimized.setter
|
|
45
|
+
def optimized(self, value: bool) -> None:
|
|
46
|
+
self.__optimized = value
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
#: Used to configure global behaviors of the coredis library
|
|
50
|
+
Config = __Config()
|