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
Binary file
coredis/parser.py ADDED
@@ -0,0 +1,344 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from collections.abc import Hashable
5
+ from io import BytesIO
6
+ from typing import cast
7
+
8
+ from coredis._protocols import ConnectionP
9
+ from coredis._utils import b
10
+ from coredis.constants import SYM_CRLF, RESPDataType
11
+ from coredis.exceptions import (
12
+ AskError,
13
+ AuthenticationFailureError,
14
+ AuthenticationRequiredError,
15
+ AuthorizationError,
16
+ BusyLoadingError,
17
+ ClusterCrossSlotError,
18
+ ClusterDownError,
19
+ ConnectionError,
20
+ ExecAbortError,
21
+ InvalidResponse,
22
+ MovedError,
23
+ NoKeyError,
24
+ NoScriptError,
25
+ NotBusyError,
26
+ ProtocolError,
27
+ ReadOnlyError,
28
+ RedisError,
29
+ ResponseError,
30
+ StreamConsumerGroupError,
31
+ StreamDuplicateConsumerGroupError,
32
+ TryAgainError,
33
+ UnblockedError,
34
+ UnknownCommandError,
35
+ WrongTypeError,
36
+ )
37
+ from coredis.typing import (
38
+ Final,
39
+ MutableSet,
40
+ NamedTuple,
41
+ ResponsePrimitive,
42
+ ResponseType,
43
+ )
44
+
45
+
46
+ class NotEnoughData:
47
+ pass
48
+
49
+
50
+ NOT_ENOUGH_DATA: Final[NotEnoughData] = NotEnoughData()
51
+
52
+
53
+ class RESPNode:
54
+ __slots__ = ("depth", "key", "node_type")
55
+ depth: int
56
+ key: Hashable
57
+ node_type: int
58
+
59
+ def __init__(
60
+ self,
61
+ depth: int,
62
+ node_type: int,
63
+ key: (ResponsePrimitive | tuple[ResponsePrimitive, ...] | frozenset[ResponsePrimitive]),
64
+ ):
65
+ self.depth = depth
66
+ self.node_type = node_type
67
+ self.key = key
68
+
69
+ def append(self, item: ResponseType) -> None:
70
+ raise NotImplementedError()
71
+
72
+ def ensure_hashable(self, item: ResponseType) -> Hashable:
73
+ if isinstance(item, (int, float, bool, str, bytes)):
74
+ return item
75
+ elif isinstance(item, set):
76
+ return frozenset(self.ensure_hashable(cast(ResponseType, i)) for i in item)
77
+ elif isinstance(item, list):
78
+ return tuple(self.ensure_hashable(i) for i in item)
79
+ elif isinstance(item, dict):
80
+ return tuple(
81
+ (cast(ResponsePrimitive, k), self.ensure_hashable(v)) for k, v in item.items()
82
+ )
83
+ return item # noqa
84
+
85
+
86
+ class ListNode(RESPNode):
87
+ __slots__ = ("container",)
88
+
89
+ def __init__(self, depth: int, node_type: int) -> None:
90
+ self.container: list[ResponseType] = []
91
+ super().__init__(depth, node_type, None)
92
+
93
+ def append(self, item: ResponseType) -> None:
94
+ self.depth -= 1
95
+ self.container.append(item)
96
+
97
+
98
+ class DictNode(RESPNode):
99
+ __slots__ = ("container", "key")
100
+
101
+ def __init__(self, depth: int) -> None:
102
+ self.container: dict[Hashable, ResponseType] = {}
103
+ super().__init__(depth * 2, RESPDataType.MAP, None)
104
+
105
+ def append(self, item: ResponseType) -> None:
106
+ self.depth -= 1
107
+ if not self.key:
108
+ self.key = self.ensure_hashable(item)
109
+ else:
110
+ self.container[self.key] = item
111
+ self.key = None
112
+
113
+
114
+ class SetNode(RESPNode):
115
+ __slots__ = ("container",)
116
+
117
+ def __init__(self, depth: int) -> None:
118
+ self.container: MutableSet[Hashable] = set()
119
+ super().__init__(depth, RESPDataType.SET, None)
120
+
121
+ def append(
122
+ self,
123
+ item: ResponseType,
124
+ ) -> None:
125
+ self.depth -= 1
126
+ self.container.add(self.ensure_hashable(item))
127
+
128
+
129
+ class UnpackedResponse(NamedTuple):
130
+ response_type: int
131
+ response: ResponseType
132
+
133
+
134
+ class Parser:
135
+ """
136
+ Interface between a connection and Unpacker
137
+ """
138
+
139
+ EXCEPTION_CLASSES: dict[str, type[RedisError] | dict[str, type[RedisError]]] = {
140
+ "ASK": AskError,
141
+ "BUSYGROUP": StreamDuplicateConsumerGroupError,
142
+ "CLUSTERDOWN": ClusterDownError,
143
+ "CROSSSLOT": ClusterCrossSlotError,
144
+ "ERR": {
145
+ "max number of clients reached": ConnectionError,
146
+ "unknown command": UnknownCommandError,
147
+ "unknown subcommand": UnknownCommandError,
148
+ "sub command not supported": UnknownCommandError,
149
+ },
150
+ "EXECABORT": ExecAbortError,
151
+ "LOADING": BusyLoadingError,
152
+ "NOSCRIPT": NoScriptError,
153
+ "MOVED": MovedError,
154
+ "NOAUTH": AuthenticationRequiredError,
155
+ "NOGROUP": StreamConsumerGroupError,
156
+ "NOKEY": NoKeyError,
157
+ "NOPERM": AuthorizationError,
158
+ "NOPROTO": ProtocolError,
159
+ "NOTBUSY": NotBusyError,
160
+ "READONLY": ReadOnlyError,
161
+ "TRYAGAIN": TryAgainError,
162
+ "UNBLOCKED": UnblockedError,
163
+ "WRONGPASS": AuthenticationFailureError,
164
+ "WRONGTYPE": WrongTypeError,
165
+ }
166
+
167
+ def __init__(self) -> None:
168
+ self.push_messages: asyncio.Queue[ResponseType] | None = None
169
+ self.localbuffer: BytesIO = BytesIO(b"")
170
+ self.bytes_read: int = 0
171
+ self.bytes_written: int = 0
172
+ self.nodes: list[ListNode | SetNode | DictNode] = []
173
+
174
+ def feed(self, data: bytes) -> None:
175
+ self.localbuffer.seek(self.bytes_written)
176
+ self.bytes_written += self.localbuffer.write(data)
177
+ self.localbuffer.seek(self.bytes_read)
178
+
179
+ def on_connect(self, connection: ConnectionP) -> None:
180
+ """Called when the stream connects"""
181
+ self.push_messages = connection.push_messages
182
+
183
+ def on_disconnect(self) -> None:
184
+ """Called when the stream disconnects"""
185
+ if not self.localbuffer.closed:
186
+ self.localbuffer.seek(0)
187
+ self.localbuffer.truncate()
188
+ self.bytes_read = self.bytes_written = 0
189
+ self.nodes.clear()
190
+
191
+ def can_read(self) -> bool:
192
+ return (self.bytes_written - self.bytes_read) > 0
193
+
194
+ def try_decode(self, data: bytes, encoding: str) -> bytes | str:
195
+ try:
196
+ return data.decode(encoding)
197
+ except ValueError:
198
+ return data
199
+
200
+ def get_response(
201
+ self,
202
+ decode: bool,
203
+ encoding: str | None = None,
204
+ push_message_types: set[bytes] | None = None,
205
+ ) -> NotEnoughData | ResponseType:
206
+ """
207
+
208
+ :param decode: Whether to decode simple or bulk strings
209
+ :param push_message_types: the push message types to return if they
210
+ arrive. If a message arrives that does not match the filter, it will
211
+ be put on the :data:`~coredis.connection.BaseConnection.push_messages`
212
+ queue
213
+ :return: The next available parsed response read from the connection.
214
+ If there is not enough data on the wire a ``NotEnoughData`` instance
215
+ will be returned.
216
+ """
217
+ while True:
218
+ response = self.parse(decode, encoding)
219
+ if isinstance(response, NotEnoughData):
220
+ return response
221
+ else:
222
+ if response and response.response_type == RESPDataType.PUSH:
223
+ assert isinstance(response.response, list)
224
+ assert self.push_messages
225
+ if not push_message_types or b(response.response[0]) not in push_message_types:
226
+ self.push_messages.put_nowait(response.response)
227
+ continue
228
+ else:
229
+ break
230
+ else:
231
+ break
232
+ return response.response if response else None
233
+
234
+ def parse(
235
+ self,
236
+ decode_bytes: bool,
237
+ encoding: str | None,
238
+ ) -> UnpackedResponse | None | NotEnoughData:
239
+ parsed: UnpackedResponse | None = None
240
+
241
+ while True:
242
+ data = self.localbuffer.readline()
243
+ if not data[-2::] == SYM_CRLF:
244
+ return NOT_ENOUGH_DATA
245
+ data_len = len(data)
246
+ self.bytes_read += data_len
247
+ marker, chunk = data[0], data[1:-2]
248
+ response: ResponseType = None
249
+ if marker == RESPDataType.SIMPLE_STRING:
250
+ response = chunk
251
+ if decode_bytes and encoding:
252
+ response = self.try_decode(response, encoding)
253
+ elif marker == RESPDataType.BULK_STRING or marker == RESPDataType.VERBATIM:
254
+ length = int(chunk)
255
+ if length == -1:
256
+ response = None
257
+ else:
258
+ if (self.bytes_written - self.bytes_read) < length + 2:
259
+ self.bytes_read -= data_len
260
+ return NOT_ENOUGH_DATA
261
+ data = self.localbuffer.read(length + 2)
262
+ self.bytes_read += length + 2
263
+ response = data[:-2]
264
+ if marker == RESPDataType.VERBATIM:
265
+ if response[:3] != b"txt":
266
+ raise InvalidResponse(
267
+ f"Unexpected verbatim string of type {response[:3]!r}"
268
+ )
269
+ response = response[4:]
270
+ if decode_bytes and encoding:
271
+ response = self.try_decode(response, encoding)
272
+ elif marker in [RESPDataType.INT, RESPDataType.BIGNUMBER]:
273
+ response = int(chunk)
274
+ elif marker == RESPDataType.DOUBLE:
275
+ response = float(chunk)
276
+ elif marker == RESPDataType.NONE:
277
+ response = None
278
+ elif marker == RESPDataType.BOOLEAN:
279
+ response = chunk[0] == ord(b"t")
280
+ elif (
281
+ marker == RESPDataType.ARRAY
282
+ or marker == RESPDataType.PUSH
283
+ or marker == RESPDataType.MAP
284
+ or marker == RESPDataType.SET
285
+ ):
286
+ length = int(chunk)
287
+ if length >= 0:
288
+ if marker in {RESPDataType.ARRAY, RESPDataType.PUSH}:
289
+ self.nodes.append(ListNode(length, marker))
290
+ elif marker == RESPDataType.MAP:
291
+ self.nodes.append(DictNode(length))
292
+ else:
293
+ self.nodes.append(SetNode(length))
294
+ if length > 0:
295
+ continue
296
+ elif marker == RESPDataType.ERROR:
297
+ response = cast(ResponseType, self.parse_error(bytes(chunk).decode()))
298
+ else:
299
+ raise InvalidResponse(f"Protocol Error: {chr(marker)}, {bytes(chunk)!r}")
300
+
301
+ if self.nodes:
302
+ if self.nodes[-1].depth > 0:
303
+ self.nodes[-1].append(response)
304
+ if self.nodes[-1].depth > 1:
305
+ continue
306
+
307
+ while len(self.nodes) > 1 and self.nodes[-1].depth == 0:
308
+ self.nodes[-2].append(self.nodes[-1].container)
309
+ self.nodes.pop()
310
+
311
+ if len(self.nodes) == 1 and self.nodes[-1].depth == 0:
312
+ node = self.nodes.pop()
313
+ parsed = UnpackedResponse(node.node_type, node.container)
314
+ break
315
+ if not self.nodes:
316
+ parsed = UnpackedResponse(marker, response)
317
+ break
318
+
319
+ if self.bytes_read == self.bytes_written:
320
+ self.localbuffer.seek(0)
321
+ self.localbuffer.truncate()
322
+ self.bytes_read = self.bytes_written = 0
323
+ return parsed
324
+
325
+ def parse_error(self, response: str) -> RedisError:
326
+ """
327
+ Parse an error response
328
+
329
+ :meta private:
330
+ """
331
+ error_code = response.split(" ")[0]
332
+ if error_code in self.EXCEPTION_CLASSES:
333
+ response = response[len(error_code) + 1 :]
334
+ exception_class = self.EXCEPTION_CLASSES[error_code]
335
+
336
+ if isinstance(exception_class, dict):
337
+ options = exception_class.items()
338
+ exception_class = ResponseError
339
+ for err, exc in options:
340
+ if response.lower().startswith(err):
341
+ exception_class = exc
342
+ break
343
+ return exception_class(response)
344
+ return ResponseError(response)