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
@@ -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
@@ -0,0 +1,10 @@
1
+ -- KEYS[1] - lock name
2
+ -- ARGS[1] - token
3
+ -- return 1 if the lock was released, otherwise 0
4
+
5
+ local token = redis.call('get', KEYS[1])
6
+ if not token or token ~= ARGV[1] then
7
+ return 0
8
+ end
9
+ redis.call('del', KEYS[1])
10
+ return 1
@@ -0,0 +1,5 @@
1
+ """
2
+ coredis.response
3
+ ----------------
4
+ Types & callbacks to handle redis responses
5
+ """