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,538 @@
|
|
|
1
|
+
"""
|
|
2
|
+
coredis.response.callbacks
|
|
3
|
+
--------------------------
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import datetime
|
|
9
|
+
import itertools
|
|
10
|
+
from abc import ABC, ABCMeta, abstractmethod
|
|
11
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
12
|
+
|
|
13
|
+
from coredis._utils import b, nativestr
|
|
14
|
+
from coredis.exceptions import ClusterResponseError, ResponseError
|
|
15
|
+
from coredis.typing import (
|
|
16
|
+
AnyStr,
|
|
17
|
+
Callable,
|
|
18
|
+
Generic,
|
|
19
|
+
Hashable,
|
|
20
|
+
Iterable,
|
|
21
|
+
Literal,
|
|
22
|
+
Mapping,
|
|
23
|
+
ParamSpec,
|
|
24
|
+
Protocol,
|
|
25
|
+
RedisValueT,
|
|
26
|
+
ResponsePrimitive,
|
|
27
|
+
ResponseType,
|
|
28
|
+
Sequence,
|
|
29
|
+
StringT,
|
|
30
|
+
TypeVar,
|
|
31
|
+
add_runtime_checks,
|
|
32
|
+
runtime_checkable,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
R = TypeVar("R")
|
|
36
|
+
S = TypeVar("S")
|
|
37
|
+
T = TypeVar("T")
|
|
38
|
+
P = ParamSpec("P")
|
|
39
|
+
CR_co = TypeVar("CR_co", covariant=True)
|
|
40
|
+
CK_co = TypeVar("CK_co", covariant=True)
|
|
41
|
+
|
|
42
|
+
RESP = TypeVar("RESP")
|
|
43
|
+
RESP3 = TypeVar("RESP3")
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from coredis.client import Client
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ResponseCallbackMeta(ABCMeta):
|
|
50
|
+
def __new__(
|
|
51
|
+
cls, name: str, bases: tuple[type, ...], namespace: dict[str, str]
|
|
52
|
+
) -> ResponseCallbackMeta:
|
|
53
|
+
kls = super().__new__(cls, name, bases, namespace)
|
|
54
|
+
setattr(kls, "transform", add_runtime_checks(getattr(kls, "transform")))
|
|
55
|
+
setattr(kls, "transform_3", add_runtime_checks(getattr(kls, "transform_3")))
|
|
56
|
+
return kls
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ClusterCallbackMeta(ABCMeta):
|
|
60
|
+
def __new__(
|
|
61
|
+
cls, name: str, bases: tuple[type, ...], namespace: dict[str, str]
|
|
62
|
+
) -> ClusterCallbackMeta:
|
|
63
|
+
kls = super().__new__(cls, name, bases, namespace)
|
|
64
|
+
setattr(kls, "combine", add_runtime_checks(getattr(kls, "combine")))
|
|
65
|
+
setattr(kls, "combine_3", add_runtime_checks(getattr(kls, "combine_3")))
|
|
66
|
+
return kls
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ResponseCallback(ABC, Generic[RESP, RESP3, R], metaclass=ResponseCallbackMeta):
|
|
70
|
+
version: Literal[2, 3]
|
|
71
|
+
|
|
72
|
+
def __init__(self, **options: Any) -> None:
|
|
73
|
+
self.options = options
|
|
74
|
+
|
|
75
|
+
def __call__(
|
|
76
|
+
self,
|
|
77
|
+
response: RESP | RESP3 | ResponseError,
|
|
78
|
+
version: Literal[2, 3] = 2,
|
|
79
|
+
) -> R:
|
|
80
|
+
self.version = version
|
|
81
|
+
if isinstance(response, ResponseError):
|
|
82
|
+
exc_to_response = self.handle_exception(response)
|
|
83
|
+
if exc_to_response:
|
|
84
|
+
return exc_to_response
|
|
85
|
+
if version == 3:
|
|
86
|
+
return self.transform_3(cast(RESP3, response))
|
|
87
|
+
return self.transform(cast(RESP, response))
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
def transform(self, response: RESP) -> R:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
def transform_3(self, response: RESP3) -> R:
|
|
94
|
+
return self.transform(cast(RESP, response))
|
|
95
|
+
|
|
96
|
+
def handle_exception(self, exc: BaseException) -> R | None:
|
|
97
|
+
return exc # type: ignore
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@runtime_checkable
|
|
101
|
+
class AsyncPreProcessingCallback(Protocol):
|
|
102
|
+
async def pre_process(self, client: Client[Any], response: ResponseType) -> None: ...
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class NoopCallback(ResponseCallback[R, R, R]):
|
|
106
|
+
def transform(self, response: R) -> R:
|
|
107
|
+
return response
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ClusterMultiNodeCallback(ABC, Generic[R], metaclass=ClusterCallbackMeta):
|
|
111
|
+
def __call__(
|
|
112
|
+
self,
|
|
113
|
+
responses: Mapping[str, R | ResponseError],
|
|
114
|
+
version: int = 2,
|
|
115
|
+
) -> R:
|
|
116
|
+
if version == 3:
|
|
117
|
+
return self.combine_3(responses)
|
|
118
|
+
return self.combine(responses)
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
@abstractmethod
|
|
122
|
+
def response_policy(self) -> str: ...
|
|
123
|
+
|
|
124
|
+
@abstractmethod
|
|
125
|
+
def combine(self, responses: Mapping[str, R], **options: Any) -> R:
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
def combine_3(self, responses: Mapping[str, R], **options: Any) -> R:
|
|
129
|
+
return self.combine(responses, **options)
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def raise_any(cls, values: Iterable[R]) -> None:
|
|
133
|
+
for value in values:
|
|
134
|
+
if isinstance(value, BaseException):
|
|
135
|
+
raise value
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class ClusterBoolCombine(ClusterMultiNodeCallback[bool]):
|
|
139
|
+
def __init__(self, any: bool = False):
|
|
140
|
+
self.any = any
|
|
141
|
+
|
|
142
|
+
def combine(self, responses: Mapping[str, bool], **options: Any) -> bool:
|
|
143
|
+
values = tuple(responses.values())
|
|
144
|
+
self.raise_any(values)
|
|
145
|
+
assert (isinstance(value, bool) for value in values)
|
|
146
|
+
return any(values) if self.any else all(values)
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def response_policy(self) -> str:
|
|
150
|
+
return (
|
|
151
|
+
"success if any shards responded ``True``"
|
|
152
|
+
if self.any
|
|
153
|
+
else "success if all shards responded ``True``"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class ClusterAlignedBoolsCombine(ClusterMultiNodeCallback[tuple[bool, ...]]):
|
|
158
|
+
def combine(
|
|
159
|
+
self, responses: Mapping[str, tuple[bool, ...]], **options: Any
|
|
160
|
+
) -> tuple[bool, ...]:
|
|
161
|
+
return tuple(all(k) for k in zip(*responses.values()))
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def response_policy(self) -> str:
|
|
165
|
+
return "the logical AND of all responses"
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class ClusterEnsureConsistent(ClusterMultiNodeCallback[R | None]):
|
|
169
|
+
def __init__(self, ensure_consistent: bool = True):
|
|
170
|
+
self.ensure_consistent = ensure_consistent
|
|
171
|
+
|
|
172
|
+
def combine(self, responses: Mapping[str, R | None], **options: Any) -> R | None:
|
|
173
|
+
values = tuple(responses.values())
|
|
174
|
+
self.raise_any(values)
|
|
175
|
+
if self.ensure_consistent and len(set(values)) != 1:
|
|
176
|
+
raise ClusterResponseError("Inconsistent response from cluster nodes")
|
|
177
|
+
elif values:
|
|
178
|
+
return values[0]
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def response_policy(self) -> str:
|
|
183
|
+
return (
|
|
184
|
+
"the response from any shard if all responses are consistent"
|
|
185
|
+
if self.ensure_consistent
|
|
186
|
+
else "first response"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class ClusterFirstNonException(ClusterMultiNodeCallback[R | None]):
|
|
191
|
+
def combine(self, responses: Mapping[str, R | None], **options: Any) -> R | None:
|
|
192
|
+
for r in responses.values():
|
|
193
|
+
if not isinstance(r, BaseException):
|
|
194
|
+
return r
|
|
195
|
+
for r in responses.values():
|
|
196
|
+
if isinstance(r, BaseException):
|
|
197
|
+
raise r
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def response_policy(self) -> str:
|
|
201
|
+
return "the first response that is not an error"
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class ClusterMergeSets(ClusterMultiNodeCallback[set[R]]):
|
|
205
|
+
def combine(self, responses: Mapping[str, set[R]], **options: Any) -> set[R]:
|
|
206
|
+
self.raise_any(responses.values())
|
|
207
|
+
return set(itertools.chain(*responses.values()))
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def response_policy(self) -> str:
|
|
211
|
+
return "the union of the results"
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class ClusterSum(ClusterMultiNodeCallback[int]):
|
|
215
|
+
def combine(self, responses: Mapping[str, int | ResponseError], **options: Any) -> int:
|
|
216
|
+
self.raise_any(responses.values())
|
|
217
|
+
return sum(responses.values())
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def response_policy(self) -> str:
|
|
221
|
+
return "the sum of results"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class ClusterMergeMapping(ClusterMultiNodeCallback[dict[CK_co, CR_co]]):
|
|
225
|
+
def __init__(self, value_combine: Callable[[Iterable[CR_co]], CR_co]) -> None:
|
|
226
|
+
self.value_combine = value_combine
|
|
227
|
+
|
|
228
|
+
def combine(
|
|
229
|
+
self, responses: Mapping[str, dict[CK_co, CR_co]], **options: Any
|
|
230
|
+
) -> dict[CK_co, CR_co]:
|
|
231
|
+
self.raise_any(responses.values())
|
|
232
|
+
response: dict[CK_co, CR_co] = {}
|
|
233
|
+
for key in set(itertools.chain(*responses.values())):
|
|
234
|
+
values = list(responses[n][key] for n in responses if key in responses[n])
|
|
235
|
+
response[key] = self.value_combine(values)
|
|
236
|
+
return response
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def response_policy(self) -> str:
|
|
240
|
+
return "the merged mapping"
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class ClusterConcatenateTuples(ClusterMultiNodeCallback[tuple[R, ...]]):
|
|
244
|
+
def combine(self, responses: Mapping[str, tuple[R, ...]], **options: Any) -> tuple[R, ...]:
|
|
245
|
+
self.raise_any(responses.values())
|
|
246
|
+
return tuple(itertools.chain(*responses.values()))
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def response_policy(self) -> str:
|
|
250
|
+
return "the concatenations of the results"
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class SimpleStringCallback(ResponseCallback[StringT | None, StringT | None, bool]):
|
|
254
|
+
def __init__(
|
|
255
|
+
self,
|
|
256
|
+
raise_on_error: type[Exception] | None = None,
|
|
257
|
+
prefix_match: bool = False,
|
|
258
|
+
ok_values: set[str] = {"OK"},
|
|
259
|
+
**options: Any,
|
|
260
|
+
):
|
|
261
|
+
self.raise_on_error = raise_on_error
|
|
262
|
+
self.prefix_match = prefix_match
|
|
263
|
+
self.ok_values = {b(v) for v in ok_values}
|
|
264
|
+
super().__init__(**options)
|
|
265
|
+
|
|
266
|
+
def transform(self, response: StringT | None, **options: Any) -> bool:
|
|
267
|
+
if response:
|
|
268
|
+
if not self.prefix_match:
|
|
269
|
+
success = b(response) in self.ok_values
|
|
270
|
+
else:
|
|
271
|
+
success = any(b(response).startswith(ok) for ok in self.ok_values)
|
|
272
|
+
else:
|
|
273
|
+
success = False
|
|
274
|
+
if not success and self.raise_on_error:
|
|
275
|
+
raise self.raise_on_error(response)
|
|
276
|
+
return success
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class IntCallback(ResponseCallback[int, int, int]):
|
|
280
|
+
def transform(self, response: ResponsePrimitive, **options: Any) -> int:
|
|
281
|
+
if isinstance(response, int):
|
|
282
|
+
return response
|
|
283
|
+
raise ValueError(f"Unable to map {response!r} to int")
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class AnyStrCallback(ResponseCallback[StringT, StringT, AnyStr]):
|
|
287
|
+
def transform(self, response: StringT, **options: Any) -> AnyStr:
|
|
288
|
+
if isinstance(response, (bytes, str)):
|
|
289
|
+
return cast(AnyStr, response)
|
|
290
|
+
|
|
291
|
+
raise ValueError(f"Unable to map {response!r} to AnyStr")
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class FloatCallback(ResponseCallback[StringT | int | float, StringT | int | float, float]):
|
|
295
|
+
def transform(self, response: ResponseType, **options: Any) -> float:
|
|
296
|
+
if isinstance(response, float):
|
|
297
|
+
return response
|
|
298
|
+
if isinstance(response, (int, bytes, str)):
|
|
299
|
+
return float(response)
|
|
300
|
+
|
|
301
|
+
raise ValueError(f"Unable to map {response} to float")
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class BoolCallback(ResponseCallback[int | bool, int | bool, bool]):
|
|
305
|
+
def transform(self, response: ResponseType, **options: Any) -> bool:
|
|
306
|
+
if isinstance(response, bool):
|
|
307
|
+
return response
|
|
308
|
+
return bool(response)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class SimpleStringOrIntCallback(ResponseCallback[RedisValueT, RedisValueT, bool | int]):
|
|
312
|
+
def transform(self, response: RedisValueT, **options: Any) -> bool | int:
|
|
313
|
+
if isinstance(response, (int, bool)):
|
|
314
|
+
return response
|
|
315
|
+
elif isinstance(response, (str, bytes)):
|
|
316
|
+
return SimpleStringCallback()(response)
|
|
317
|
+
raise ValueError(f"Unable to map {response!r} to bool")
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class TupleCallback(ResponseCallback[list[ResponseType], list[ResponseType], tuple[CR_co, ...]]):
|
|
321
|
+
def transform(self, response: ResponseType, **options: Any) -> tuple[CR_co, ...]:
|
|
322
|
+
if isinstance(response, list):
|
|
323
|
+
return cast(tuple[CR_co, ...], tuple(response))
|
|
324
|
+
raise ValueError(f"Unable to map {response!r} to tuple")
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class ItemOrTupleCallback(
|
|
328
|
+
ResponseCallback[
|
|
329
|
+
list[ResponseType] | ResponsePrimitive,
|
|
330
|
+
list[ResponseType] | ResponsePrimitive,
|
|
331
|
+
tuple[CR_co, ...] | CR_co,
|
|
332
|
+
]
|
|
333
|
+
):
|
|
334
|
+
def transform(
|
|
335
|
+
self, response: list[ResponseType] | ResponsePrimitive, **options: Any
|
|
336
|
+
) -> tuple[CR_co, ...] | CR_co:
|
|
337
|
+
if isinstance(response, list):
|
|
338
|
+
return cast(tuple[CR_co, ...], tuple(response))
|
|
339
|
+
return cast(CR_co, response)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class MixedTupleCallback(ResponseCallback[list[ResponseType], list[ResponseType], tuple[R, S]]):
|
|
343
|
+
def transform(self, response: ResponseType, **options: Any) -> tuple[R, S]:
|
|
344
|
+
if isinstance(response, list):
|
|
345
|
+
return cast(tuple[R, S], tuple(response))
|
|
346
|
+
raise ValueError(f"Unable to map {response!r} to tuple")
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
class ListCallback(ResponseCallback[list[ResponseType], list[ResponseType], list[CR_co]]):
|
|
350
|
+
def transform(self, response: list[ResponseType], **options: Any) -> list[CR_co]:
|
|
351
|
+
return cast(list[CR_co], response)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class DateTimeCallback(ResponseCallback[int | float, int | float, datetime.datetime]):
|
|
355
|
+
def transform(
|
|
356
|
+
self,
|
|
357
|
+
response: int | float,
|
|
358
|
+
) -> datetime.datetime:
|
|
359
|
+
ts = float(response) if not isinstance(response, float) else response
|
|
360
|
+
if self.options.get("unit") == "milliseconds":
|
|
361
|
+
ts = ts / 1000.0
|
|
362
|
+
return datetime.datetime.fromtimestamp(ts)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class DictCallback(
|
|
366
|
+
ResponseCallback[
|
|
367
|
+
Sequence[ResponseType] | dict[ResponsePrimitive, ResponseType],
|
|
368
|
+
Sequence[ResponseType] | dict[ResponsePrimitive, ResponseType],
|
|
369
|
+
dict[CK_co, CR_co],
|
|
370
|
+
]
|
|
371
|
+
):
|
|
372
|
+
def __init__(
|
|
373
|
+
self,
|
|
374
|
+
flat: bool = True,
|
|
375
|
+
recursive: list[str] | None = None,
|
|
376
|
+
**options: Any,
|
|
377
|
+
):
|
|
378
|
+
self.flat = flat
|
|
379
|
+
self.recursive = recursive or []
|
|
380
|
+
super().__init__(**options)
|
|
381
|
+
|
|
382
|
+
def transform(
|
|
383
|
+
self,
|
|
384
|
+
response: Sequence[ResponseType] | dict[ResponsePrimitive, ResponseType],
|
|
385
|
+
**options: Any,
|
|
386
|
+
) -> dict[CK_co, CR_co]:
|
|
387
|
+
if isinstance(response, list):
|
|
388
|
+
if self.flat:
|
|
389
|
+
if self.recursive:
|
|
390
|
+
return cast(dict[CK_co, CR_co], self.recursive_transformer(response))
|
|
391
|
+
else:
|
|
392
|
+
it = iter(response)
|
|
393
|
+
return cast(dict[CK_co, CR_co], dict(zip(it, it)))
|
|
394
|
+
else:
|
|
395
|
+
return dict(r for r in response)
|
|
396
|
+
raise ValueError(f"Unable to map {response!r} to mapping")
|
|
397
|
+
|
|
398
|
+
def transform_3(
|
|
399
|
+
self,
|
|
400
|
+
response: Sequence[ResponseType] | dict[ResponsePrimitive, ResponseType],
|
|
401
|
+
**options: Any,
|
|
402
|
+
) -> dict[CK_co, CR_co]:
|
|
403
|
+
if isinstance(response, dict):
|
|
404
|
+
return cast(dict[CK_co, CR_co], response)
|
|
405
|
+
return self.transform(response, **options)
|
|
406
|
+
|
|
407
|
+
def recursive_transformer(
|
|
408
|
+
self, item: Sequence[ResponseType] | dict[ResponsePrimitive, ResponseType]
|
|
409
|
+
) -> dict[CK_co, CR_co] | list[CK_co] | list[CR_co] | tuple[CK_co, ...] | tuple[CR_co, ...]:
|
|
410
|
+
if isinstance(item, (list, tuple)):
|
|
411
|
+
if all(isinstance(k, Hashable) for k in item[::2]):
|
|
412
|
+
dct = []
|
|
413
|
+
for i in range(0, 2 * (len(item) // 2), 2):
|
|
414
|
+
key, value = item[i], item[i + 1]
|
|
415
|
+
value = (
|
|
416
|
+
self.recursive_transformer(value)
|
|
417
|
+
if (nativestr(key) in self.recursive)
|
|
418
|
+
else value
|
|
419
|
+
)
|
|
420
|
+
dct.append((key, value))
|
|
421
|
+
return dict(dct)
|
|
422
|
+
else:
|
|
423
|
+
caster = list if isinstance(item, list) else tuple
|
|
424
|
+
return caster(self.recursive_transformer(i) for i in item)
|
|
425
|
+
else:
|
|
426
|
+
return item
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class SetCallback(
|
|
430
|
+
ResponseCallback[
|
|
431
|
+
list[ResponsePrimitive],
|
|
432
|
+
set[ResponsePrimitive],
|
|
433
|
+
set[CR_co],
|
|
434
|
+
]
|
|
435
|
+
):
|
|
436
|
+
def transform(
|
|
437
|
+
self,
|
|
438
|
+
response: list[ResponsePrimitive] | set[ResponsePrimitive],
|
|
439
|
+
**options: Any,
|
|
440
|
+
) -> set[CR_co]:
|
|
441
|
+
if isinstance(response, list):
|
|
442
|
+
return cast(set[CR_co], set(response))
|
|
443
|
+
raise ValueError(f"Unable to map {response} to set")
|
|
444
|
+
|
|
445
|
+
def transform_3(
|
|
446
|
+
self,
|
|
447
|
+
response: list[ResponsePrimitive] | set[ResponsePrimitive],
|
|
448
|
+
**options: Any,
|
|
449
|
+
) -> set[CR_co]:
|
|
450
|
+
if isinstance(response, set):
|
|
451
|
+
return cast(set[CR_co], response)
|
|
452
|
+
else:
|
|
453
|
+
return self.transform(response)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class OneOrManyCallback(
|
|
457
|
+
ResponseCallback[
|
|
458
|
+
CR_co | list[CR_co | None] | None,
|
|
459
|
+
CR_co | list[CR_co | None] | None,
|
|
460
|
+
CR_co | list[CR_co | None] | None,
|
|
461
|
+
]
|
|
462
|
+
):
|
|
463
|
+
def transform(
|
|
464
|
+
self,
|
|
465
|
+
response: CR_co | list[CR_co | None] | None,
|
|
466
|
+
**options: Any,
|
|
467
|
+
) -> CR_co | list[CR_co | None] | None:
|
|
468
|
+
return response
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
class BoolsCallback(ResponseCallback[ResponseType, ResponseType, tuple[bool, ...]]):
|
|
472
|
+
def transform(self, response: ResponseType, **options: Any) -> tuple[bool, ...]:
|
|
473
|
+
if isinstance(response, list):
|
|
474
|
+
return tuple(BoolCallback()(r) for r in response)
|
|
475
|
+
return ()
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
class FloatsCallback(ResponseCallback[ResponseType, ResponseType, tuple[float, ...]]):
|
|
479
|
+
def transform(self, response: ResponseType, **options: Any) -> tuple[float, ...]:
|
|
480
|
+
if isinstance(response, list):
|
|
481
|
+
return tuple(FloatCallback()(r) for r in response)
|
|
482
|
+
return ()
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
class OptionalFloatCallback(
|
|
486
|
+
ResponseCallback[
|
|
487
|
+
StringT | int | float | None,
|
|
488
|
+
StringT | int | float | None,
|
|
489
|
+
float | None,
|
|
490
|
+
]
|
|
491
|
+
):
|
|
492
|
+
def transform(
|
|
493
|
+
self,
|
|
494
|
+
response: StringT | int | float | None,
|
|
495
|
+
**options: Any,
|
|
496
|
+
) -> float | None:
|
|
497
|
+
if response is None:
|
|
498
|
+
return None
|
|
499
|
+
return FloatCallback()(response)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
class OptionalIntCallback(ResponseCallback[int | None, int | None, int | None]):
|
|
503
|
+
def transform(self, response: int | None, **options: Any) -> int | None:
|
|
504
|
+
if response is None:
|
|
505
|
+
return None
|
|
506
|
+
if isinstance(response, int):
|
|
507
|
+
return response
|
|
508
|
+
raise ValueError(f"Unable to map {response} to int")
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
class OptionalAnyStrCallback(
|
|
512
|
+
ResponseCallback[
|
|
513
|
+
StringT | None,
|
|
514
|
+
AnyStr | None,
|
|
515
|
+
AnyStr | None,
|
|
516
|
+
]
|
|
517
|
+
):
|
|
518
|
+
def transform(self, response: StringT | None, **options: Any) -> AnyStr | None:
|
|
519
|
+
if response is None:
|
|
520
|
+
return None
|
|
521
|
+
if isinstance(response, (bytes, str)):
|
|
522
|
+
return cast(AnyStr, response)
|
|
523
|
+
raise ValueError(f"Unable to map {response} to AnyStr")
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
class OptionalListCallback(
|
|
527
|
+
ResponseCallback[list[ResponseType], list[ResponseType], list[CR_co] | None]
|
|
528
|
+
):
|
|
529
|
+
def transform(self, response: ResponseType, **options: Any) -> list[CR_co] | None:
|
|
530
|
+
return cast(list[CR_co], response)
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
class FirstValueCallback(ResponseCallback[list[CR_co], list[CR_co], CR_co]):
|
|
534
|
+
def transform(self, response: list[CR_co], **options: Any) -> CR_co:
|
|
535
|
+
if response:
|
|
536
|
+
return response[0]
|
|
537
|
+
else:
|
|
538
|
+
raise ValueError("Empty response list")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from coredis.response._callbacks import DictCallback, ResponseCallback
|
|
4
|
+
from coredis.typing import (
|
|
5
|
+
AnyStr,
|
|
6
|
+
ResponsePrimitive,
|
|
7
|
+
Sequence,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ACLLogCallback(
|
|
12
|
+
ResponseCallback[
|
|
13
|
+
list[Sequence[ResponsePrimitive] | None],
|
|
14
|
+
list[dict[AnyStr, ResponsePrimitive] | None],
|
|
15
|
+
tuple[dict[AnyStr, ResponsePrimitive] | None, ...],
|
|
16
|
+
]
|
|
17
|
+
):
|
|
18
|
+
def transform(
|
|
19
|
+
self,
|
|
20
|
+
response: list[Sequence[ResponsePrimitive] | None],
|
|
21
|
+
) -> tuple[dict[AnyStr, ResponsePrimitive] | None, ...]:
|
|
22
|
+
return tuple(
|
|
23
|
+
DictCallback[AnyStr, ResponsePrimitive]()(r, version=self.version)
|
|
24
|
+
for r in response
|
|
25
|
+
if r
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def transform_3(
|
|
29
|
+
self,
|
|
30
|
+
response: list[dict[AnyStr, ResponsePrimitive] | None],
|
|
31
|
+
) -> tuple[dict[AnyStr, ResponsePrimitive] | None, ...]:
|
|
32
|
+
return tuple(response)
|