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,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)