coredis 4.24.0__py3-none-any.whl → 5.0.0rc1__py3-none-any.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.

Potentially problematic release.


This version of coredis might be problematic. Click here for more details.

Files changed (77) hide show
  1. coredis/__init__.py +1 -3
  2. coredis/_packer.py +10 -10
  3. coredis/_protocols.py +23 -32
  4. coredis/_py_311_typing.py +20 -0
  5. coredis/_py_312_typing.py +17 -0
  6. coredis/_utils.py +49 -51
  7. coredis/_version.py +3 -3
  8. coredis/cache.py +57 -82
  9. coredis/client/__init__.py +1 -2
  10. coredis/client/basic.py +129 -56
  11. coredis/client/cluster.py +147 -70
  12. coredis/commands/__init__.py +27 -7
  13. coredis/commands/_key_spec.py +11 -10
  14. coredis/commands/_utils.py +1 -1
  15. coredis/commands/_validators.py +30 -20
  16. coredis/commands/_wrappers.py +19 -99
  17. coredis/commands/bitfield.py +10 -2
  18. coredis/commands/constants.py +20 -3
  19. coredis/commands/core.py +1627 -1246
  20. coredis/commands/function.py +21 -19
  21. coredis/commands/monitor.py +0 -71
  22. coredis/commands/pubsub.py +7 -142
  23. coredis/commands/request.py +108 -0
  24. coredis/commands/script.py +9 -9
  25. coredis/commands/sentinel.py +60 -49
  26. coredis/connection.py +14 -15
  27. coredis/exceptions.py +2 -2
  28. coredis/experimental/__init__.py +0 -4
  29. coredis/globals.py +3 -0
  30. coredis/modules/autocomplete.py +28 -30
  31. coredis/modules/base.py +15 -31
  32. coredis/modules/filters.py +269 -245
  33. coredis/modules/graph.py +61 -62
  34. coredis/modules/json.py +172 -140
  35. coredis/modules/response/_callbacks/autocomplete.py +5 -4
  36. coredis/modules/response/_callbacks/graph.py +34 -29
  37. coredis/modules/response/_callbacks/json.py +5 -3
  38. coredis/modules/response/_callbacks/search.py +49 -53
  39. coredis/modules/response/_callbacks/timeseries.py +18 -30
  40. coredis/modules/response/types.py +1 -5
  41. coredis/modules/search.py +186 -169
  42. coredis/modules/timeseries.py +184 -164
  43. coredis/parser.py +6 -19
  44. coredis/pipeline.py +391 -422
  45. coredis/pool/basic.py +7 -7
  46. coredis/pool/cluster.py +3 -3
  47. coredis/pool/nodemanager.py +10 -3
  48. coredis/response/_callbacks/__init__.py +76 -57
  49. coredis/response/_callbacks/acl.py +0 -3
  50. coredis/response/_callbacks/cluster.py +25 -16
  51. coredis/response/_callbacks/command.py +8 -6
  52. coredis/response/_callbacks/connection.py +4 -3
  53. coredis/response/_callbacks/geo.py +17 -13
  54. coredis/response/_callbacks/hash.py +13 -11
  55. coredis/response/_callbacks/keys.py +9 -5
  56. coredis/response/_callbacks/module.py +2 -3
  57. coredis/response/_callbacks/script.py +6 -8
  58. coredis/response/_callbacks/sentinel.py +21 -17
  59. coredis/response/_callbacks/server.py +36 -14
  60. coredis/response/_callbacks/sets.py +3 -4
  61. coredis/response/_callbacks/sorted_set.py +27 -24
  62. coredis/response/_callbacks/streams.py +22 -13
  63. coredis/response/_callbacks/strings.py +7 -6
  64. coredis/response/_callbacks/vector_sets.py +126 -0
  65. coredis/response/types.py +13 -4
  66. coredis/sentinel.py +1 -1
  67. coredis/stream.py +4 -3
  68. coredis/tokens.py +343 -16
  69. coredis/typing.py +432 -79
  70. {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/METADATA +4 -5
  71. coredis-5.0.0rc1.dist-info/RECORD +95 -0
  72. coredis/client/keydb.py +0 -336
  73. coredis/pipeline.pyi +0 -2103
  74. coredis-4.24.0.dist-info/RECORD +0 -93
  75. {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/WHEEL +0 -0
  76. {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/licenses/LICENSE +0 -0
  77. {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/top_level.txt +0 -0
coredis/typing.py CHANGED
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- import warnings
3
+ import dataclasses
4
+ import inspect
5
+ import sys
4
6
  from collections import OrderedDict
5
7
  from collections.abc import (
6
8
  AsyncGenerator,
@@ -20,9 +22,10 @@ from collections.abc import (
20
22
  Set,
21
23
  ValuesView,
22
24
  )
23
- from types import ModuleType
25
+ from types import GenericAlias, ModuleType, UnionType
24
26
  from typing import (
25
27
  TYPE_CHECKING,
28
+ Any,
26
29
  AnyStr,
27
30
  ClassVar,
28
31
  Final,
@@ -34,45 +37,25 @@ from typing import (
34
37
  TypedDict,
35
38
  TypeGuard,
36
39
  TypeVar,
40
+ cast,
41
+ get_origin,
42
+ get_type_hints,
37
43
  runtime_checkable,
38
44
  )
39
45
 
40
- from typing_extensions import Self
46
+ from beartype import beartype
47
+ from beartype.door import infer_hint, is_bearable, is_subhint
48
+ from typing_extensions import (
49
+ NotRequired,
50
+ Self,
51
+ Unpack,
52
+ )
41
53
 
42
54
  from coredis.config import Config
43
55
 
44
56
  _runtime_checks = False
45
- _beartype_found = False
46
-
47
- try:
48
- import beartype
49
-
50
- if not TYPE_CHECKING:
51
- from beartype.typing import ( # noqa: F811
52
- Iterable,
53
- Iterator,
54
- Mapping,
55
- MutableMapping,
56
- MutableSequence,
57
- MutableSet,
58
- OrderedDict,
59
- Sequence,
60
- ValuesView,
61
- )
62
- _beartype_found = True
63
- except ImportError: # pragma: no cover
64
- pass
65
-
66
- if Config.runtime_checks and not TYPE_CHECKING: # pragma: no cover
67
- if _beartype_found:
68
- _runtime_checks = True
69
- else:
70
- warnings.warn(
71
- "Runtime checks were enabled via environment variable COREDIS_RUNTIME_CHECKS"
72
- " but could not import beartype"
73
- )
74
57
 
75
- RUNTIME_TYPECHECKS = _runtime_checks
58
+ RUNTIME_TYPECHECKS = Config.runtime_checks and not TYPE_CHECKING
76
59
 
77
60
  P = ParamSpec("P")
78
61
  T_co = TypeVar("T_co", covariant=True)
@@ -80,15 +63,12 @@ R = TypeVar("R")
80
63
 
81
64
 
82
65
  def safe_beartype(func: Callable[P, R]) -> Callable[P, R]:
83
- if TYPE_CHECKING:
84
- return func
85
-
86
- return beartype.beartype(func) if _beartype_found else func
66
+ return beartype(func) if RUNTIME_TYPECHECKS else func
87
67
 
88
68
 
89
69
  def add_runtime_checks(func: Callable[P, R]) -> Callable[P, R]:
90
70
  if RUNTIME_TYPECHECKS and not TYPE_CHECKING:
91
- return safe_beartype(func)
71
+ return beartype(func)
92
72
 
93
73
  return func
94
74
 
@@ -100,9 +80,6 @@ class RedisError(Exception):
100
80
  """
101
81
 
102
82
 
103
- CommandArgList = list[str | bytes | int | float]
104
-
105
-
106
83
  class Node(TypedDict):
107
84
  """
108
85
  Definition of a cluster node
@@ -112,19 +89,414 @@ class Node(TypedDict):
112
89
  port: int
113
90
 
114
91
 
92
+ class RedisCommandP(Protocol):
93
+ """
94
+ Protocol of a redis command with all associated arguments
95
+ converted into the shape expected by the redis server.
96
+ Used by :meth:`~coredis.Redis.execute_command`
97
+ """
98
+
99
+ #: The name of the redis command
100
+ name: bytes
101
+ #: All arguments to be passed to the command
102
+ arguments: tuple[RedisValueT, ...]
103
+
104
+
105
+ @dataclasses.dataclass
106
+ class RedisCommand:
107
+ """
108
+ Convenience data class that conforms to :class:`~coredis.typing.RedisCommandP`
109
+ """
110
+
111
+ #: The name of the redis command
112
+ name: bytes
113
+ #: All arguments to be passed to the command
114
+ arguments: tuple[RedisValueT, ...]
115
+
116
+
117
+ class ExecutionParameters(TypedDict):
118
+ """
119
+ Extra parameters that can be passed to :meth:`~coredis.Redis.execute_command`
120
+ """
121
+
122
+ #: Whether to decode the response
123
+ #: (ignoring the value of :paramref:`~coredis.Redis.decode_responses`)
124
+ decode: NotRequired[bool]
125
+ slot_arguments_range: NotRequired[tuple[int, int]]
126
+
127
+
115
128
  #: Represents the acceptable types of a redis key
116
129
  KeyT = str | bytes
117
130
 
131
+
132
+ class Serializable(Generic[R]):
133
+ """
134
+ Wrapper to be used to pass arbitrary types to redis commands
135
+ to be eventually serialized by :class:`coredis.typing.TypeAdapter.serialize`
136
+
137
+ Wrapping a value in :class:`Serializable` will pass type checking
138
+ wherever a method expects a :class:`coredis.typing.ValueT` - however
139
+ it will still fail if there is no serializer registered through the instance
140
+ of :class:`coredis.typing.TypeAdapter` that is associated with the client.
141
+
142
+ For example::
143
+
144
+ class MyThing:
145
+ ...
146
+
147
+ client = coredis.Redis()
148
+
149
+ # This will pass type checking but will fail with an :exc:`LookupError`
150
+ # at runtime
151
+ await client.set("fubar", coredis.typing.Serializable(MyThing()))
152
+
153
+ # however, if a serializer is registered, the above would succeed
154
+ @client.type_adapter.serializer
155
+ def _(value: MyThing) -> str:
156
+ ... # some way to convert it to a string
157
+ """
158
+
159
+ def __init__(self, value: R) -> None:
160
+ self.value = value
161
+
162
+
163
+ AdaptableType = type | UnionType | GenericAlias
164
+
165
+
166
+ class TypeAdapter:
167
+ """
168
+ Used by the coredis clients :class:`~coredis.Redis` and :class:`~coredis.RedisCluster`
169
+ through :paramref:`~coredis.Redis.type_adapter` for adapting complex types that require
170
+ custom serialization/deserialization with redis commands.
171
+
172
+ For example to use Decimal types with some common redis operations::
173
+
174
+ from decimal import Decimal
175
+ from typing import Any, Mapping, Iterable
176
+ from coredis import Redis
177
+ from coredis.typing import TypeAdapter, Serializable
178
+
179
+ adapter = TypeAdapter()
180
+
181
+ @adapter.serializer
182
+ def decimal_to_str(value: Decimal) -> str:
183
+ return str(value)
184
+
185
+ @adapter.deserializer
186
+ def value_to_decimal(value: str|bytes) -> Decimal:
187
+ return Decimal(value.decode("utf-8") if isinstance(value, bytes) else value)
188
+
189
+ @adapter.deserializer
190
+ def list_to_decimal_list(items: Iterable[str|bytes]) -> list[Decimal]:
191
+ return [value_to_decimal(value) for value in items]
192
+
193
+ @adapter.deserializer
194
+ def mapping_to_decimal_mapping(mapping: Mapping[str|bytes, str|bytes]) -> dict[str|bytes, Decimal]:
195
+ return {key: value_to_decimal(value) for key, value in mapping.items()}
196
+
197
+ client = coredis.Redis(type_adapter=adapter, decode_responses=True)
198
+ await client.set("key", Serializable(Decimal(1.5)))
199
+ await client.lpush("list", [Serializable(Decimal(1.5))])
200
+ await client.hset("dict", {"first": Serializable(Decimal(1.5))})
201
+ assert Decimal(1.5) == await client.get("key").transform(Decimal)
202
+ assert [Decimal(1.5)] == await client.lrange("list", 0, 0).transform(list[Decimal])
203
+ assert {"first": Decimal(1.5)} == await client.hgetall("dict").transform(dict[str, Decimal])
204
+ """
205
+
206
+ def __init__(
207
+ self,
208
+ ) -> None:
209
+ self.__serializers: dict[
210
+ AdaptableType,
211
+ tuple[Callable[[Any], RedisValueT], int],
212
+ ] = {}
213
+ self.__deserializers: dict[
214
+ AdaptableType,
215
+ dict[AdaptableType, tuple[Callable[..., Any], int]],
216
+ ] = {}
217
+ self.__deserializer_cache: dict[
218
+ tuple[AdaptableType, AdaptableType | GenericAlias],
219
+ Callable[..., Any],
220
+ ] = {}
221
+ self.__serializer_cache: dict[AdaptableType, Callable[[Any], RedisValueT]] = {}
222
+
223
+ @classmethod
224
+ def format_type(cls, type_like: AdaptableType) -> str:
225
+ if get_origin(type_like):
226
+ return str(type_like)
227
+ else:
228
+ return getattr(type_like, "__name__", str(type_like))
229
+
230
+ def register(
231
+ self,
232
+ type: type[R] | UnionType,
233
+ serializer: Callable[[R], RedisValueT],
234
+ deserializer: Callable[[Any], R],
235
+ deserializable_type: type = object,
236
+ ) -> None:
237
+ """
238
+ Register both a serializer and a deserializer for :paramref:`type`
239
+
240
+ :param type: The type that should be serialized/deserialized
241
+ :param serializer: a function that receives an instance of :paramref:`type`
242
+ and returns a value of type :data:`coredis.typing.RedisValueT`
243
+ :param deserializer: a function that accepts the return types from
244
+ the redis commands that are expected to be used when deserializing
245
+ to :paramref:`type`.
246
+ :param deserializable_type: the types of values :paramref:`deserializer` should
247
+ be considered for
248
+ """
249
+ self.register_serializer(type, serializer)
250
+ self.register_deserializer(type, deserializer, deserializable_type)
251
+
252
+ def register_serializer(
253
+ self,
254
+ serializable_type: type[R] | UnionType,
255
+ serializer: Callable[[R], RedisValueT],
256
+ ) -> None:
257
+ """
258
+ Register a serializer for :paramref:`type`
259
+
260
+ :param type: The type that will be serialized
261
+ :param serializer: a function that receives an instance of :paramref:`type`
262
+ and returns a value of type :data:`coredis.typing.RedisValueT`
263
+ """
264
+ self.__serializers.setdefault(serializable_type, (serializer, 0))
265
+ self.__serializer_cache.clear()
266
+
267
+ def register_deserializer(
268
+ self,
269
+ deserialized_type: type[R] | UnionType,
270
+ deserializer: Callable[[Any], R],
271
+ deserializable_type: AdaptableType = object,
272
+ ) -> None:
273
+ """
274
+ Register a deserializer for :paramref:`type` and automatically register
275
+ deserializers for common collection types that use this type.
276
+
277
+ :param type: The type that should be deserialized
278
+ :param deserializer: a function that accepts the return types from
279
+ the redis commands that are expected to be used when deserializing
280
+ to :paramref:`type`.
281
+ :param deserializable_type: the types of values :paramref:`deserializer` should
282
+ be considered for
283
+ """
284
+
285
+ def register_collection_deserializer(
286
+ collection_type: AdaptableType,
287
+ deserializable_type: AdaptableType,
288
+ deserializer: Callable[[Any], Any],
289
+ ) -> None:
290
+ self.__deserializers.setdefault(collection_type, {}).setdefault(
291
+ deserializable_type,
292
+ (deserializer, -1),
293
+ )
294
+
295
+ # Register the base deserializer
296
+ self.__deserializers.setdefault(deserialized_type, {})[deserializable_type or object] = (
297
+ deserializer,
298
+ 0,
299
+ )
300
+
301
+ # Register collection deserializers
302
+ register_collection_deserializer(
303
+ GenericAlias(list, (deserialized_type,)),
304
+ GenericAlias(Iterable, deserializable_type),
305
+ lambda v: [deserializer(item) for item in v],
306
+ )
307
+ register_collection_deserializer(
308
+ GenericAlias(set, (deserialized_type,)),
309
+ GenericAlias(Iterable, deserializable_type),
310
+ lambda v: {deserializer(item) for item in v},
311
+ )
312
+ register_collection_deserializer(
313
+ GenericAlias(tuple, (deserialized_type, ...)),
314
+ GenericAlias(Iterable, deserializable_type),
315
+ lambda v: tuple([deserializer(item) for item in v]),
316
+ )
317
+
318
+ # Register dictionary deserializers for existing types
319
+ for t in list(self.__deserializers):
320
+ if t != deserialized_type:
321
+ for rt in list(self.__deserializers[t]):
322
+ _deserializer, priority = self.__deserializers[t][rt]
323
+ if priority >= 0:
324
+ register_collection_deserializer(
325
+ GenericAlias(dict, (t, deserialized_type)),
326
+ GenericAlias(Mapping, (rt, deserializable_type)),
327
+ lambda m, key_deserializer=_deserializer: { # type: ignore
328
+ key_deserializer(k): deserializer(v) for k, v in m.items()
329
+ },
330
+ )
331
+ register_collection_deserializer(
332
+ GenericAlias(dict, (deserialized_type, t)),
333
+ GenericAlias(Mapping, (deserializable_type, rt)),
334
+ lambda m, value_deserializer=_deserializer: { # type: ignore
335
+ deserializer(k): value_deserializer(v) for k, v in m.items()
336
+ },
337
+ )
338
+
339
+ # Register dictionary deserializers for primitive types
340
+ for t in {bytes, str}:
341
+ register_collection_deserializer(
342
+ GenericAlias(dict, (t, deserialized_type)),
343
+ GenericAlias(Mapping, (t, deserializable_type)),
344
+ lambda v: {k: deserializer(v) for k, v in v.items()},
345
+ )
346
+ register_collection_deserializer(
347
+ GenericAlias(dict, (deserialized_type, t)),
348
+ GenericAlias(Mapping, (deserializable_type, t)),
349
+ lambda v: {deserializer(k): v for k, v in v.items()},
350
+ )
351
+
352
+ self.__deserializer_cache.clear()
353
+
354
+ def serializer(self, func: Callable[[R], RedisValueT]) -> Callable[[R], RedisValueT]:
355
+ """
356
+ Decorator for registering a serializer
357
+
358
+ :param func: A serialization function that accepts an instance of
359
+ type `R` and returns one of the types defined by :data:`coredis.typing.RedisValueT`
360
+ The acceptable serializable types are inferred
361
+ from the annotations in the function signature.
362
+
363
+ :raises ValueError: when the appropriate serializable type cannot be
364
+ inferred.
365
+ """
366
+ if (parameters := list(inspect.signature(func).parameters.keys())) and (
367
+ input_hint := get_type_hints(func).get(parameters[0])
368
+ ):
369
+ self.register_serializer(input_hint, func)
370
+ return func
371
+ else:
372
+ raise ValueError(
373
+ "Unable to infer custom input type from decorated function. Check type annotations."
374
+ )
375
+
376
+ def deserializer(self, func: Callable[[Any], R]) -> Callable[[Any], R]:
377
+ """
378
+ Decorator for registering a deserializer
379
+
380
+ :param func: A deserialization function that returns an instance of
381
+ type `R` that can be used with :meth:`deserialize`. The acceptable
382
+ deserializable types and the expected deserialized type are inferred
383
+ from the annotations in the function signature.
384
+
385
+ :raises ValueError: when the appropriate input/output types cannot be
386
+ inferred.
387
+ """
388
+ if (
389
+ (parameters := list(inspect.signature(func).parameters.keys()))
390
+ and (input_hint := get_type_hints(func).get(parameters[0]))
391
+ ) and (response_type := get_type_hints(func).get("return")):
392
+ self.register_deserializer(response_type, func, input_hint)
393
+ return func
394
+ else:
395
+ raise ValueError(
396
+ "Unable to infer response type from decorated function. Check annotations."
397
+ )
398
+
399
+ def serialize(self, value: Serializable[R]) -> RedisValueT:
400
+ """
401
+ Serializes :paramref:`value` into one of the types represented by
402
+ :data:`~coredis.typing.RedisValueT` using a serializer registered
403
+ via :meth:`register_serializer` or decorated by :meth:`serializer`.
404
+
405
+ :param: a value wrapped in :class:`coredis.typing.Serializable`
406
+ """
407
+ value_type = cast(AdaptableType, infer_hint(value.value))
408
+ if not (transform_function := self.__serializer_cache.get(value_type, None)):
409
+ candidate: tuple[AdaptableType, Callable[[R], RedisValueT] | None] = (object, None)
410
+
411
+ for t in self.__serializers:
412
+ if is_bearable(value.value, t):
413
+ if not candidate[1] or is_subhint(t, candidate[0]):
414
+ candidate = (t, self.__serializers[t][0])
415
+ if candidate[1]:
416
+ transform_function = candidate[1]
417
+ self.__serializer_cache[value_type] = transform_function
418
+ if not transform_function:
419
+ raise LookupError(
420
+ f"No registered serializer to serialize {self.format_type(value_type)}"
421
+ )
422
+ return transform_function(value.value)
423
+
424
+ def deserialize(self, value: Any, return_type: type[R]) -> R:
425
+ """
426
+ Deserializes :paramref:`value` into an instance of :paramref:`return_type`
427
+ using a deserializer registered via :meth:`register_deserializer` or decorated
428
+ by :meth:`deserializer`.
429
+
430
+ :param value: the value to be deserialized (typically something returned by one of
431
+ the redis commands)
432
+ :param return_type: The type to deserialize to
433
+ """
434
+ value_type = cast(AdaptableType, infer_hint(value))
435
+ if not (deserializer := self.__deserializer_cache.get((value_type, return_type), None)):
436
+ if exact_match := self.__deserializers.get(return_type, {}).get(value_type, None):
437
+ deserializer = exact_match[0]
438
+ else:
439
+ candidate: tuple[AdaptableType, AdaptableType, Callable[[Any], R] | None, int] = (
440
+ object,
441
+ object,
442
+ None,
443
+ -100,
444
+ )
445
+ for registered_type, transforms in self.__deserializers.items():
446
+ if is_subhint(return_type, registered_type):
447
+ for expected_value_type in transforms:
448
+ if (
449
+ is_bearable(value, expected_value_type)
450
+ and is_subhint(registered_type, candidate[0])
451
+ and is_subhint(expected_value_type, candidate[1])
452
+ and transforms[expected_value_type][1] >= candidate[3]
453
+ ):
454
+ candidate = (
455
+ registered_type,
456
+ expected_value_type,
457
+ transforms[expected_value_type][0],
458
+ transforms[expected_value_type][1],
459
+ )
460
+ deserializer = candidate[2]
461
+ if deserializer:
462
+ deserialized = deserializer(value)
463
+ if RUNTIME_TYPECHECKS and not is_subhint(
464
+ transformed_type := cast(type, infer_hint(deserialized)), return_type
465
+ ):
466
+ raise TypeError(
467
+ f"Invalid deserializer. Requested {self.format_type(return_type)} but deserializer returned {self.format_type(transformed_type)}"
468
+ )
469
+ self.__deserializer_cache[(value_type, return_type)] = deserializer
470
+ return deserialized
471
+ elif is_subhint(value_type, return_type):
472
+ return cast(R, value)
473
+ else:
474
+ raise LookupError(
475
+ f"No registered deserializer to convert {self.format_type(value_type)} to {self.format_type(return_type)}"
476
+ )
477
+
478
+
118
479
  #: Represents the different python primitives that are accepted
119
480
  #: as input parameters for commands that can be used with loosely
120
- #: defined types. These are encoded using the configured encoding
121
- #: before being transmitted.
122
- ValueT = str | bytes | int | float
481
+ #: defined types. These will eventually be serialized before being
482
+ #: sent to redis.
483
+ #:
484
+ #: Additionally any object wrapped in a :class:`Serializable` will be
485
+ #: accepted and will be serialized using an appropriate type adapter
486
+ #: registered with the client. See :ref:`api/typing:custom types` for more details.
487
+ ValueT = str | bytes | int | float | Serializable[Any]
123
488
 
124
489
  #: The canonical type used for input parameters that represent "strings"
125
490
  #: that are transmitted to redis.
126
491
  StringT = str | bytes
127
492
 
493
+ CommandArgList = list[ValueT]
494
+
495
+ #: Primitive types that we can expect to be sent to redis with
496
+ #: simple serialization. The internals of coredis
497
+ #: pass around arguments to redis commands as this type.
498
+ RedisValueT = str | bytes | int | float
499
+
128
500
  #: Restricted union of container types accepted as arguments to apis
129
501
  #: that accept a variable number values for an argument (such as keys, values).
130
502
  #: This is used instead of :class:`typing.Iterable` as the latter allows
@@ -147,45 +519,17 @@ StringT = str | bytes
147
519
  #: length(b"123") # invalid
148
520
  Parameters = list[T_co] | Set[T_co] | tuple[T_co, ...] | ValuesView[T_co] | Iterator[T_co]
149
521
 
150
- #: Mapping of primitives returned by redis
522
+ #: Primitives returned by redis
151
523
  ResponsePrimitive = StringT | int | float | bool | None
152
524
 
153
- #: Represents the total structure of any response for a redis
154
- #: command.
155
- #:
156
- #: This should preferably be represented by a recursive definition to allow for
157
- #: Limitations in runtime type checkers (beartype) requires conditionally loosening
158
- #: the definition with the use of :class:`typing.Any` for now.
159
-
160
- if TYPE_CHECKING:
161
- ResponseType = (
162
- ResponsePrimitive
163
- | list["ResponseType"]
164
- | MutableSet[
165
- ResponsePrimitive | tuple[ResponsePrimitive, ...] | frozenset[ResponsePrimitive]
166
- ]
167
- | dict[
168
- ResponsePrimitive | tuple[ResponsePrimitive, ...] | frozenset[ResponsePrimitive],
169
- "ResponseType",
170
- ]
171
- | RedisError # response errors get mapped to exceptions.
172
- )
525
+ if sys.version_info >= (3, 12):
526
+ from ._py_312_typing import JsonType, ResponseType
173
527
  else:
174
- from typing import Any
175
-
176
- ResponseType = (
177
- ResponsePrimitive
178
- | list[Any]
179
- | MutableSet[
180
- ResponsePrimitive | tuple[ResponsePrimitive, ...] | frozenset[ResponsePrimitive]
181
- ]
182
- | dict[
183
- ResponsePrimitive | tuple[ResponsePrimitive, ...] | frozenset[ResponsePrimitive],
184
- Any,
185
- ]
186
- | RedisError # response errors get mapped to exceptions.
187
- )
528
+ from ._py_311_typing import JsonType, ResponseType
529
+
530
+
188
531
  __all__ = [
532
+ "Serializable",
189
533
  "AnyStr",
190
534
  "AsyncIterator",
191
535
  "AsyncGenerator",
@@ -200,6 +544,7 @@ __all__ = [
200
544
  "Hashable",
201
545
  "Iterable",
202
546
  "Iterator",
547
+ "JsonType",
203
548
  "KeyT",
204
549
  "Literal",
205
550
  "Mapping",
@@ -209,10 +554,14 @@ __all__ = [
209
554
  "MutableSequence",
210
555
  "NamedTuple",
211
556
  "Node",
557
+ "NotRequired",
212
558
  "OrderedDict",
213
559
  "Parameters",
214
560
  "ParamSpec",
215
561
  "Protocol",
562
+ "RedisCommand",
563
+ "RedisCommandP",
564
+ "ExecutionParameters",
216
565
  "ResponsePrimitive",
217
566
  "ResponseType",
218
567
  "runtime_checkable",
@@ -222,8 +571,12 @@ __all__ = [
222
571
  "TypeGuard",
223
572
  "TypedDict",
224
573
  "TypeVar",
574
+ "Unpack",
225
575
  "ValueT",
576
+ "RedisValueT",
226
577
  "ValuesView",
227
578
  "TYPE_CHECKING",
579
+ "TypeAdapter",
580
+ "ValueT",
228
581
  "RUNTIME_TYPECHECKS",
229
582
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coredis
3
- Version: 4.24.0
3
+ Version: 5.0.0rc1
4
4
  Summary: Python async client for Redis key-value store
5
5
  Home-page: https://github.com/alisaifee/coredis
6
6
  Author: Ali-Akber Saifee
@@ -26,11 +26,11 @@ Requires-Python: >=3.10
26
26
  Description-Content-Type: text/markdown
27
27
  License-File: LICENSE
28
28
  Requires-Dist: async_timeout<6,>4
29
+ Requires-Dist: beartype>=0.20
29
30
  Requires-Dist: deprecated>=1.2
30
- Requires-Dist: typing_extensions>=4.3
31
+ Requires-Dist: typing_extensions>=4.13
31
32
  Requires-Dist: packaging<26,>=21
32
33
  Requires-Dist: pympler<2,>1
33
- Requires-Dist: wrapt<2,>=1.1.0
34
34
  Provides-Extra: recipes
35
35
  Requires-Dist: aiobotocore>=2.15.2; extra == "recipes"
36
36
  Requires-Dist: asyncache>=0.3.1; extra == "recipes"
@@ -201,7 +201,7 @@ Details about supported Redis modules and their commands can be found
201
201
 
202
202
  ## Compatibility
203
203
 
204
- coredis is tested against redis versions >= `6.4`
204
+ coredis is tested against redis versions >= `7.0`
205
205
  The test matrix status can be reviewed
206
206
  [here](https://github.com/alisaifee/coredis/actions/workflows/main.yml)
207
207
 
@@ -221,7 +221,6 @@ coredis is additionally tested against:
221
221
 
222
222
  **coredis** is known to work with the following databases that have redis protocol compatibility:
223
223
 
224
- - [KeyDB](https://docs.keydb.dev/)
225
224
  - [Dragonfly](https://dragonflydb.io/)
226
225
  - [Redict](https://redict.io/)
227
226
  - [Valkey](https://github.com/valkey-io/valkey)