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,281 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import contextvars
|
|
5
|
+
import importlib.resources
|
|
6
|
+
import math
|
|
7
|
+
import time
|
|
8
|
+
import uuid
|
|
9
|
+
import warnings
|
|
10
|
+
from types import TracebackType
|
|
11
|
+
from typing import cast
|
|
12
|
+
|
|
13
|
+
from coredis.client import Redis, RedisCluster
|
|
14
|
+
from coredis.commands import Script
|
|
15
|
+
from coredis.exceptions import (
|
|
16
|
+
LockAcquisitionError,
|
|
17
|
+
LockError,
|
|
18
|
+
LockExtensionError,
|
|
19
|
+
LockReleaseError,
|
|
20
|
+
ReplicationError,
|
|
21
|
+
)
|
|
22
|
+
from coredis.tokens import PureToken
|
|
23
|
+
from coredis.typing import AnyStr, Generic, KeyT, StringT
|
|
24
|
+
|
|
25
|
+
with warnings.catch_warnings():
|
|
26
|
+
warnings.simplefilter("ignore", DeprecationWarning)
|
|
27
|
+
EXTEND_SCRIPT = Script(script=importlib.resources.read_text(__package__, "extend.lua"))
|
|
28
|
+
RELEASE_SCRIPT = Script(script=importlib.resources.read_text(__package__, "release.lua"))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class LuaLock(Generic[AnyStr]):
|
|
32
|
+
"""
|
|
33
|
+
A shared, distributed Lock using LUA scripts.
|
|
34
|
+
|
|
35
|
+
The lock can be used with both :class:`coredis.Redis`
|
|
36
|
+
and :class:`coredis.RedisCluster` either explicitly or as an async context
|
|
37
|
+
manager::
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
import asyncio
|
|
41
|
+
import coredis
|
|
42
|
+
from coredis.exceptions import LockError
|
|
43
|
+
from coredis.recipes.locks import LuaLock
|
|
44
|
+
|
|
45
|
+
async def test():
|
|
46
|
+
client = coredis.Redis()
|
|
47
|
+
async with LuaLock(client, "mylock", timeout=1.0):
|
|
48
|
+
# do stuff
|
|
49
|
+
await asyncio.sleep(0.5)
|
|
50
|
+
# lock is implictly released when the context manager exits
|
|
51
|
+
try:
|
|
52
|
+
async with LuaLock(client, "mylock", timeout=1.0):
|
|
53
|
+
# do stuff that takes too long
|
|
54
|
+
await asyncio.sleep(1)
|
|
55
|
+
# lock will raise upon exiting the context manager
|
|
56
|
+
except LockError as err:
|
|
57
|
+
# roll back stuff
|
|
58
|
+
print(f"Expected error: {err}")
|
|
59
|
+
lock = LuaLock(client, "mylock", timeout=1.0)
|
|
60
|
+
await lock.acquire()
|
|
61
|
+
# do stuff
|
|
62
|
+
await asyncio.sleep(0.5)
|
|
63
|
+
# do more stuff
|
|
64
|
+
await lock.extend(1.0)
|
|
65
|
+
await lock.release()
|
|
66
|
+
|
|
67
|
+
asyncio.run(test())
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
@RELEASE_SCRIPT.wraps(client_arg="client")
|
|
72
|
+
async def lua_release( # type: ignore[empty-body]
|
|
73
|
+
cls,
|
|
74
|
+
client: Redis[AnyStr] | RedisCluster[AnyStr],
|
|
75
|
+
name: KeyT,
|
|
76
|
+
expected_token: StringT,
|
|
77
|
+
) -> int: ...
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
@EXTEND_SCRIPT.wraps(client_arg="client")
|
|
81
|
+
async def lua_extend( # type: ignore[empty-body]
|
|
82
|
+
cls,
|
|
83
|
+
client: Redis[AnyStr] | RedisCluster[AnyStr],
|
|
84
|
+
name: KeyT,
|
|
85
|
+
expected_token: StringT,
|
|
86
|
+
additional_time: int,
|
|
87
|
+
) -> int: ...
|
|
88
|
+
|
|
89
|
+
local: contextvars.ContextVar[StringT | None]
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
client: Redis[AnyStr] | RedisCluster[AnyStr],
|
|
94
|
+
name: StringT,
|
|
95
|
+
timeout: float | None = None,
|
|
96
|
+
sleep: float = 0.1,
|
|
97
|
+
blocking: bool = True,
|
|
98
|
+
blocking_timeout: float | None = None,
|
|
99
|
+
):
|
|
100
|
+
"""
|
|
101
|
+
:param timeout: indicates a maximum life for the lock.
|
|
102
|
+
By default, it will remain locked until :meth:`release` is called.
|
|
103
|
+
``timeout`` can be specified as a float or integer, both representing
|
|
104
|
+
the number of seconds to wait.
|
|
105
|
+
|
|
106
|
+
:param sleep: indicates the amount of time to sleep per loop iteration
|
|
107
|
+
when the lock is in blocking mode and another client is currently
|
|
108
|
+
holding the lock.
|
|
109
|
+
|
|
110
|
+
:param blocking: indicates whether calling :meth:`acquire` should block until
|
|
111
|
+
the lock has been acquired or to fail immediately, causing :meth:`acquire`
|
|
112
|
+
to return ``False`` and the lock not being acquired. Defaults to ``True``.
|
|
113
|
+
|
|
114
|
+
:param blocking_timeout: indicates the maximum amount of time in seconds to
|
|
115
|
+
spend trying to acquire the lock. A value of ``None`` indicates
|
|
116
|
+
continue trying forever. ``blocking_timeout`` can be specified as a
|
|
117
|
+
:class:`float` or :class:`int`, both representing the number of seconds to wait.
|
|
118
|
+
"""
|
|
119
|
+
self.client: Redis[AnyStr] | RedisCluster[AnyStr] = client
|
|
120
|
+
self.name = name
|
|
121
|
+
self.timeout = timeout
|
|
122
|
+
self.sleep = sleep
|
|
123
|
+
self.blocking = blocking
|
|
124
|
+
self.blocking_timeout = blocking_timeout
|
|
125
|
+
self.local = contextvars.ContextVar[StringT | None]("token", default=None)
|
|
126
|
+
|
|
127
|
+
if self.timeout and self.sleep > self.timeout:
|
|
128
|
+
raise LockError("'sleep' must be less than 'timeout'")
|
|
129
|
+
|
|
130
|
+
async def __aenter__(
|
|
131
|
+
self,
|
|
132
|
+
) -> LuaLock[AnyStr]:
|
|
133
|
+
if await self.acquire():
|
|
134
|
+
return self
|
|
135
|
+
raise LockAcquisitionError("Could not acquire lock")
|
|
136
|
+
|
|
137
|
+
async def __aexit__(
|
|
138
|
+
self,
|
|
139
|
+
exc_type: type[BaseException] | None,
|
|
140
|
+
exc_value: BaseException | None,
|
|
141
|
+
traceback: TracebackType | None,
|
|
142
|
+
) -> None:
|
|
143
|
+
await self.release()
|
|
144
|
+
|
|
145
|
+
async def acquire(
|
|
146
|
+
self,
|
|
147
|
+
) -> bool:
|
|
148
|
+
"""
|
|
149
|
+
Use :rediscommand:`SET` with the ``NX`` option
|
|
150
|
+
to acquire a lock. If the lock is being used with a cluster client
|
|
151
|
+
the :meth:`coredis.RedisCluster.ensure_replication` context manager
|
|
152
|
+
will be used to ensure that the command was replicated to atleast
|
|
153
|
+
half the replicas of the shard where the lock would be acquired.
|
|
154
|
+
|
|
155
|
+
:raises: :exc:`~coredis.exceptions.LockError`
|
|
156
|
+
"""
|
|
157
|
+
token = uuid.uuid1().hex
|
|
158
|
+
blocking = self.blocking
|
|
159
|
+
blocking_timeout = self.blocking_timeout
|
|
160
|
+
stop_trying_at = None
|
|
161
|
+
|
|
162
|
+
if blocking_timeout is not None:
|
|
163
|
+
stop_trying_at = time.time() + blocking_timeout
|
|
164
|
+
|
|
165
|
+
while True:
|
|
166
|
+
if await self.__acquire(token, stop_trying_at):
|
|
167
|
+
self.local.set(token)
|
|
168
|
+
|
|
169
|
+
return True
|
|
170
|
+
|
|
171
|
+
if not blocking:
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
if stop_trying_at is not None and time.time() > stop_trying_at:
|
|
175
|
+
return False
|
|
176
|
+
await asyncio.sleep(self.sleep)
|
|
177
|
+
|
|
178
|
+
async def release(self) -> None:
|
|
179
|
+
"""
|
|
180
|
+
Releases the already acquired lock
|
|
181
|
+
|
|
182
|
+
:raises: :exc:`~coredis.exceptions.LockReleaseError`
|
|
183
|
+
"""
|
|
184
|
+
expected_token = self.local.get()
|
|
185
|
+
|
|
186
|
+
if expected_token is None:
|
|
187
|
+
raise LockReleaseError("Cannot release an unlocked lock")
|
|
188
|
+
self.local.set(None)
|
|
189
|
+
await self.__release(expected_token)
|
|
190
|
+
|
|
191
|
+
async def extend(self, additional_time: float) -> bool:
|
|
192
|
+
"""
|
|
193
|
+
Adds more time to an already acquired lock.
|
|
194
|
+
|
|
195
|
+
:param additional_time: can be specified as an integer or a float, both
|
|
196
|
+
representing the number of seconds to add.
|
|
197
|
+
|
|
198
|
+
:raises: :exc:`~coredis.exceptions.LockExtensionError`
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
if self.local.get() is None:
|
|
202
|
+
raise LockExtensionError("Cannot extend an unlocked lock")
|
|
203
|
+
|
|
204
|
+
if self.timeout is None:
|
|
205
|
+
raise LockExtensionError("Cannot extend a lock with no timeout")
|
|
206
|
+
|
|
207
|
+
return await self.__extend(additional_time)
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def replication_factor(self) -> int:
|
|
211
|
+
"""
|
|
212
|
+
Number of replicas the lock needs to replicate to, to be
|
|
213
|
+
considered acquired.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
if isinstance(self.client, RedisCluster):
|
|
217
|
+
return math.ceil(self.client.num_replicas_per_shard / 2)
|
|
218
|
+
|
|
219
|
+
return 0
|
|
220
|
+
|
|
221
|
+
async def __acquire(self, token: StringT, stop_trying_at: float | None) -> bool:
|
|
222
|
+
if isinstance(self.client, RedisCluster):
|
|
223
|
+
try:
|
|
224
|
+
replication_wait = (
|
|
225
|
+
1000 * (max(0, stop_trying_at - time.time()))
|
|
226
|
+
if stop_trying_at is not None
|
|
227
|
+
else 100
|
|
228
|
+
)
|
|
229
|
+
with self.client.ensure_replication(
|
|
230
|
+
self.replication_factor, timeout_ms=int(replication_wait)
|
|
231
|
+
):
|
|
232
|
+
return await self.client.set(
|
|
233
|
+
self.name,
|
|
234
|
+
token,
|
|
235
|
+
condition=PureToken.NX,
|
|
236
|
+
px=int(self.timeout * 1000) if self.timeout else None,
|
|
237
|
+
)
|
|
238
|
+
except ReplicationError:
|
|
239
|
+
warnings.warn(
|
|
240
|
+
f"Unable to ensure lock {self.name!r} was replicated "
|
|
241
|
+
f"to {self.replication_factor} replicas",
|
|
242
|
+
category=RuntimeWarning,
|
|
243
|
+
)
|
|
244
|
+
await self.client.delete([self.name])
|
|
245
|
+
|
|
246
|
+
return False
|
|
247
|
+
else:
|
|
248
|
+
return await self.client.set(
|
|
249
|
+
self.name,
|
|
250
|
+
token,
|
|
251
|
+
condition=PureToken.NX,
|
|
252
|
+
px=int(self.timeout * 1000) if self.timeout else None,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
async def __release(self, expected_token: StringT) -> None:
|
|
256
|
+
if not bool(
|
|
257
|
+
await self.lua_release(
|
|
258
|
+
self.client,
|
|
259
|
+
self.name,
|
|
260
|
+
expected_token,
|
|
261
|
+
)
|
|
262
|
+
):
|
|
263
|
+
raise LockReleaseError("Cannot release a lock that's no longer owned")
|
|
264
|
+
|
|
265
|
+
async def __extend(self, additional_time: float) -> bool:
|
|
266
|
+
additional_time = int(additional_time * 1000)
|
|
267
|
+
|
|
268
|
+
if additional_time < 0:
|
|
269
|
+
return True
|
|
270
|
+
|
|
271
|
+
if not bool(
|
|
272
|
+
await self.lua_extend(
|
|
273
|
+
self.client,
|
|
274
|
+
self.name,
|
|
275
|
+
cast(AnyStr, self.local.get()),
|
|
276
|
+
additional_time,
|
|
277
|
+
)
|
|
278
|
+
):
|
|
279
|
+
raise LockExtensionError("Cannot extend a lock that's no longer owned")
|
|
280
|
+
|
|
281
|
+
return True
|