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/client/cluster.py CHANGED
@@ -5,6 +5,7 @@ import contextlib
5
5
  import contextvars
6
6
  import functools
7
7
  import inspect
8
+ import random
8
9
  import textwrap
9
10
  from abc import ABCMeta
10
11
  from ssl import SSLContext
@@ -13,7 +14,7 @@ from typing import TYPE_CHECKING, Any, cast, overload
13
14
  from deprecated.sphinx import versionadded
14
15
 
15
16
  from coredis._utils import b, hash_slot
16
- from coredis.cache import AbstractCache, SupportsClientTracking
17
+ from coredis.cache import AbstractCache
17
18
  from coredis.client.basic import Client, Redis
18
19
  from coredis.commands._key_spec import KeySpec
19
20
  from coredis.commands.constants import CommandName, NodeFlag
@@ -31,7 +32,7 @@ from coredis.exceptions import (
31
32
  TryAgainError,
32
33
  WatchError,
33
34
  )
34
- from coredis.globals import MODULE_GROUPS, READONLY_COMMANDS
35
+ from coredis.globals import CACHEABLE_COMMANDS, MODULE_GROUPS, READONLY_COMMANDS
35
36
  from coredis.pool import ClusterConnectionPool
36
37
  from coredis.pool.nodemanager import ManagedNode
37
38
  from coredis.response._callbacks import AsyncPreProcessingCallback, NoopCallback
@@ -42,6 +43,7 @@ from coredis.typing import (
42
43
  Awaitable,
43
44
  Callable,
44
45
  Coroutine,
46
+ ExecutionParameters,
45
47
  Iterable,
46
48
  Iterator,
47
49
  Literal,
@@ -49,10 +51,14 @@ from coredis.typing import (
49
51
  Node,
50
52
  Parameters,
51
53
  ParamSpec,
54
+ RedisCommand,
55
+ RedisCommandP,
56
+ RedisValueT,
52
57
  ResponseType,
53
58
  StringT,
59
+ TypeAdapter,
54
60
  TypeVar,
55
- ValueT,
61
+ Unpack,
56
62
  )
57
63
 
58
64
  P = ParamSpec("P")
@@ -203,6 +209,7 @@ class RedisCluster(
203
209
  noevict: bool = ...,
204
210
  notouch: bool = ...,
205
211
  retry_policy: RetryPolicy = ...,
212
+ type_adapter: TypeAdapter | None = ...,
206
213
  **kwargs: Any,
207
214
  ) -> None: ...
208
215
 
@@ -241,6 +248,7 @@ class RedisCluster(
241
248
  noevict: bool = ...,
242
249
  notouch: bool = ...,
243
250
  retry_policy: RetryPolicy = ...,
251
+ type_adapter: TypeAdapter | None = ...,
244
252
  **kwargs: Any,
245
253
  ) -> None: ...
246
254
 
@@ -288,6 +296,7 @@ class RedisCluster(
288
296
  0.1,
289
297
  ),
290
298
  ),
299
+ type_adapter: TypeAdapter | None = None,
291
300
  **kwargs: Any,
292
301
  ) -> None:
293
302
  """
@@ -422,6 +431,8 @@ class RedisCluster(
422
431
  :param notouch: Ensures that commands sent by the client will not alter the LRU/LFU
423
432
  of the keys they access.
424
433
  :param retry_policy: The retry policy to use when interacting with the cluster
434
+ :param type_adapter: The adapter to use for serializing / deserializing customs types
435
+ when interacting with redis commands.
425
436
  """
426
437
 
427
438
  if "db" in kwargs: # noqa
@@ -485,6 +496,7 @@ class RedisCluster(
485
496
  noevict=noevict,
486
497
  notouch=notouch,
487
498
  retry_policy=retry_policy,
499
+ type_adapter=type_adapter,
488
500
  **kwargs,
489
501
  )
490
502
 
@@ -518,6 +530,7 @@ class RedisCluster(
518
530
  noevict: bool = ...,
519
531
  notouch: bool = ...,
520
532
  retry_policy: RetryPolicy = ...,
533
+ type_adapter: TypeAdapter | None = ...,
521
534
  cache: AbstractCache | None = ...,
522
535
  **kwargs: Any,
523
536
  ) -> RedisCluster[bytes]: ...
@@ -537,6 +550,7 @@ class RedisCluster(
537
550
  noevict: bool = ...,
538
551
  notouch: bool = ...,
539
552
  retry_policy: RetryPolicy = ...,
553
+ type_adapter: TypeAdapter | None = ...,
540
554
  cache: AbstractCache | None = ...,
541
555
  **kwargs: Any,
542
556
  ) -> RedisCluster[str]: ...
@@ -566,6 +580,7 @@ class RedisCluster(
566
580
  0.1,
567
581
  ),
568
582
  ),
583
+ type_adapter: TypeAdapter | None = None,
569
584
  **kwargs: Any,
570
585
  ) -> RedisClusterT:
571
586
  """
@@ -588,6 +603,7 @@ class RedisCluster(
588
603
  verify_version=verify_version,
589
604
  noreply=noreply,
590
605
  retry_policy=retry_policy,
606
+ type_adapter=type_adapter,
591
607
  cache=cache,
592
608
  connection_pool=ClusterConnectionPool.from_url(
593
609
  url,
@@ -608,6 +624,7 @@ class RedisCluster(
608
624
  verify_version=verify_version,
609
625
  noreply=noreply,
610
626
  retry_policy=retry_policy,
627
+ type_adapter=type_adapter,
611
628
  cache=cache,
612
629
  connection_pool=ClusterConnectionPool.from_url(
613
630
  url,
@@ -678,9 +695,11 @@ class RedisCluster(
678
695
  if not self.connection_pool.initialized or self.refresh_table_asap:
679
696
  await self
680
697
 
681
- def _determine_slots(self, command: bytes, *args: ValueT, **options: ValueT | None) -> set[int]:
698
+ def _determine_slots(
699
+ self, command: bytes, *args: RedisValueT, **options: Unpack[ExecutionParameters]
700
+ ) -> set[int]:
682
701
  """Determines the slots the command and args would touch"""
683
- keys = cast(tuple[ValueT, ...], options.get("keys")) or KeySpec.extract_keys(
702
+ keys = cast(tuple[RedisValueT, ...], options.get("keys")) or KeySpec.extract_keys(
684
703
  command, *args, readonly_command=self.connection_pool.read_from_replicas
685
704
  )
686
705
  if (
@@ -704,7 +723,7 @@ class RedisCluster(
704
723
  self,
705
724
  command: bytes,
706
725
  res: dict[str, R],
707
- **kwargs: ValueT | None,
726
+ **kwargs: Unpack[ExecutionParameters],
708
727
  ) -> R:
709
728
  assert command in self.result_callbacks
710
729
  return cast(
@@ -712,7 +731,9 @@ class RedisCluster(
712
731
  self.result_callbacks[command](res, version=self.protocol_version, **kwargs),
713
732
  )
714
733
 
715
- def determine_node(self, command: bytes, **kwargs: ValueT | None) -> list[ManagedNode] | None:
734
+ def determine_node(
735
+ self, command: bytes, *args: RedisValueT, **kwargs: Unpack[ExecutionParameters]
736
+ ) -> list[ManagedNode] | None:
716
737
  node_flag = self.route_flags.get(command)
717
738
  if command in self.split_flags and self.non_atomic_cross_slot:
718
739
  node_flag = self.split_flags[command]
@@ -723,15 +744,16 @@ class RedisCluster(
723
744
  return list(self.connection_pool.nodes.all_primaries())
724
745
  elif node_flag == NodeFlag.ALL:
725
746
  return list(self.connection_pool.nodes.all_nodes())
726
- elif node_flag == NodeFlag.SLOT_ID:
727
- slot_id: ValueT | None = kwargs.get("slot_id")
728
- node_from_slot = (
729
- self.connection_pool.nodes.node_from_slot(int(slot_id))
730
- if slot_id is not None
731
- else None
747
+ elif node_flag == NodeFlag.SLOT_ID and (
748
+ slot_arguments_range := kwargs.get("slot_arguments_range", None)
749
+ ):
750
+ slot_start, slot_end = slot_arguments_range
751
+ nodes = list(
752
+ self.connection_pool.nodes.nodes_from_slots(
753
+ *cast(tuple[int, ...], args[slot_start:slot_end])
754
+ ).keys()
732
755
  )
733
- if node_from_slot:
734
- return [node_from_slot]
756
+ return [self.connection_pool.nodes.nodes[k] for k in nodes]
735
757
  return None
736
758
 
737
759
  async def on_connection_error(self, _: BaseException) -> None:
@@ -746,10 +768,9 @@ class RedisCluster(
746
768
 
747
769
  async def execute_command(
748
770
  self,
749
- command: bytes,
750
- *args: ValueT,
771
+ command: RedisCommandP,
751
772
  callback: Callable[..., R] = NoopCallback(),
752
- **kwargs: ValueT | None,
773
+ **kwargs: Unpack[ExecutionParameters],
753
774
  ) -> R:
754
775
  """
755
776
  Sends a command to one or many nodes in the cluster
@@ -757,7 +778,7 @@ class RedisCluster(
757
778
  """
758
779
 
759
780
  return await self.retry_policy.call_with_retries(
760
- lambda: self._execute_command(command, *args, callback=callback, **kwargs),
781
+ lambda: self._execute_command(command, callback=callback, **kwargs),
761
782
  failure_hook={
762
783
  ConnectionError: self.on_connection_error,
763
784
  ClusterDownError: self.on_cluster_down_error,
@@ -767,24 +788,27 @@ class RedisCluster(
767
788
 
768
789
  async def _execute_command(
769
790
  self,
770
- command: bytes,
771
- *args: ValueT,
791
+ command: RedisCommandP,
772
792
  callback: Callable[..., R] = NoopCallback(),
773
- **kwargs: ValueT | None,
793
+ **kwargs: Unpack[ExecutionParameters],
774
794
  ) -> R:
775
795
  """
776
796
  Sends a command to one or many nodes in the cluster
777
797
  """
778
- nodes = self.determine_node(command, **kwargs)
798
+ nodes = self.determine_node(command.name, *command.arguments, **kwargs)
779
799
  if nodes and len(nodes) > 1:
780
800
  tasks: dict[str, Coroutine[Any, Any, R]] = {}
781
- node_arg_mapping = self._split_args_over_nodes(nodes, command, *args)
801
+ node_arg_mapping = self._split_args_over_nodes(
802
+ nodes,
803
+ command.name,
804
+ *command.arguments,
805
+ slot_arguments_range=kwargs.get("slot_arguments_range", None),
806
+ )
782
807
  node_name_map = {n.name: n for n in nodes}
783
808
  for node_name in node_arg_mapping:
784
809
  for portion, pargs in enumerate(node_arg_mapping[node_name]):
785
810
  tasks[f"{node_name}:{portion}"] = self._execute_command_on_single_node(
786
- command,
787
- *pargs,
811
+ RedisCommand(command.name, pargs),
788
812
  callback=callback,
789
813
  node=node_name_map[node_name],
790
814
  slots=None,
@@ -796,28 +820,34 @@ class RedisCluster(
796
820
  return None # type: ignore
797
821
  return cast(
798
822
  R,
799
- self._merge_result(command, dict(zip(tasks.keys(), results)), **kwargs),
823
+ self._merge_result(command.name, dict(zip(tasks.keys(), results))),
800
824
  )
801
825
  else:
802
826
  node = None
803
827
  slots = None
804
828
  if not nodes:
805
- slots = list(self._determine_slots(command, *args, **kwargs))
829
+ slots = list(self._determine_slots(command.name, *command.arguments, **kwargs))
806
830
  else:
807
831
  node = nodes.pop()
808
832
  return await self._execute_command_on_single_node(
809
- command, *args, callback=callback, node=node, slots=slots, **kwargs
833
+ command,
834
+ callback=callback,
835
+ node=node,
836
+ slots=slots,
837
+ **kwargs,
810
838
  )
811
839
 
812
840
  def _split_args_over_nodes(
813
841
  self,
814
842
  nodes: list[ManagedNode],
815
843
  command: bytes,
816
- *args: ValueT,
817
- ) -> dict[str, list[tuple[ValueT, ...]]]:
844
+ *args: RedisValueT,
845
+ slot_arguments_range: tuple[int, int] | None = None,
846
+ ) -> dict[str, list[tuple[RedisValueT, ...]]]:
847
+ node_flag = self.route_flags.get(command)
848
+ node_arg_mapping: dict[str, list[tuple[RedisValueT, ...]]] = {}
818
849
  if command in self.split_flags and self.non_atomic_cross_slot:
819
850
  keys = KeySpec.extract_keys(command, *args)
820
- node_arg_mapping: dict[str, list[tuple[ValueT, ...]]] = {}
821
851
  if keys:
822
852
  key_start: int = args.index(keys[0])
823
853
  key_end: int = args.index(keys[-1])
@@ -839,20 +869,27 @@ class RedisCluster(
839
869
  )
840
870
  if self.cache and command not in READONLY_COMMANDS:
841
871
  self.cache.invalidate(*keys)
842
- return node_arg_mapping
872
+ elif node_flag == NodeFlag.SLOT_ID and slot_arguments_range:
873
+ # TODO: fix this nonsense put in place just to support a few cluster commands
874
+ # related to slot management in cluster client which really no one needs to be calling
875
+ # through the cluster client.
876
+ slot_start, slot_end = slot_arguments_range
877
+ all_slots = [int(k) for k in args[slot_start:slot_end] if k is not None]
878
+ for node, slots in self.connection_pool.nodes.nodes_from_slots(*all_slots).items():
879
+ node_arg_mapping[node] = [(*slots, *args[slot_end:])] # type: ignore
843
880
  else:
844
881
  # This command is not meant to be split across nodes and each node
845
882
  # should be called with the same arguments
846
- return {node.name: [args] for node in nodes}
883
+ node_arg_mapping = {node.name: [args] for node in nodes}
884
+ return node_arg_mapping
847
885
 
848
886
  async def _execute_command_on_single_node(
849
887
  self,
850
- command: bytes,
851
- *args: ValueT,
888
+ command: RedisCommandP,
852
889
  callback: Callable[..., R] = NoopCallback(),
853
890
  node: ManagedNode | None = None,
854
891
  slots: list[int] | None = None,
855
- **kwargs: ValueT | None,
892
+ **kwargs: Unpack[ExecutionParameters],
856
893
  ) -> R:
857
894
  redirect_addr = None
858
895
 
@@ -899,45 +936,85 @@ class RedisCluster(
899
936
  )
900
937
  await request
901
938
  asking = False
902
-
903
- if (
904
- isinstance(self.cache, AbstractCache)
905
- and isinstance(self.cache, SupportsClientTracking)
906
- and r.tracking_client_id != self.cache.get_client_id(r)
907
- ):
908
- self.cache.reset()
909
- await r.update_tracking_client(True, self.cache.get_client_id(r))
910
- if self.cache and command not in READONLY_COMMANDS:
911
- self.cache.invalidate(*KeySpec.extract_keys(command, *args))
912
- request = await r.create_request(
913
- command,
914
- *args,
915
- noreply=self.noreply,
916
- decode=kwargs.get("decode", self._decodecontext.get()),
917
- encoding=self._encodingcontext.get(),
939
+ keys = KeySpec.extract_keys(command.name, *command.arguments)
940
+ cacheable = (
941
+ self.cache
942
+ and command.name in CACHEABLE_COMMANDS
943
+ and len(keys) == 1
944
+ and not self.noreply
945
+ and self._decodecontext.get() is None
918
946
  )
919
- if quick_release and not (self.requires_wait or self.requires_waitaof):
920
- released = True
921
- self.connection_pool.release(r)
947
+ cache_hit = False
948
+ cached_reply = None
949
+ use_cached = False
950
+ reply = None
951
+ if self.cache:
952
+ if r.tracking_client_id != self.cache.get_client_id(r):
953
+ self.cache.reset()
954
+ await r.update_tracking_client(True, self.cache.get_client_id(r))
955
+ if command.name not in READONLY_COMMANDS:
956
+ self.cache.invalidate(*keys)
957
+ elif cacheable:
958
+ try:
959
+ cached_reply = cast(
960
+ R,
961
+ self.cache.get(
962
+ command.name,
963
+ keys[0],
964
+ *command.arguments,
965
+ ),
966
+ )
967
+ use_cached = random.random() * 100.0 < min(100.0, self.cache.confidence)
968
+ cache_hit = True
969
+ except KeyError:
970
+ pass
922
971
 
923
- reply = await request
924
- response = None
925
- maybe_wait = [
926
- await self._ensure_wait(command, r),
927
- await self._ensure_persistence(command, r),
928
- ]
929
- if not self.noreply:
972
+ if not (use_cached and cached_reply):
973
+ request = await r.create_request(
974
+ command.name,
975
+ *command.arguments,
976
+ noreply=self.noreply,
977
+ decode=kwargs.get("decode", self._decodecontext.get()),
978
+ encoding=self._encodingcontext.get(),
979
+ )
980
+ if quick_release and not (self.requires_wait or self.requires_waitaof):
981
+ released = True
982
+ self.connection_pool.release(r)
983
+
984
+ reply = await request
985
+ maybe_wait = [
986
+ await self._ensure_wait(command, r),
987
+ await self._ensure_persistence(command, r),
988
+ ]
989
+ await asyncio.gather(*maybe_wait)
990
+ if self.noreply:
991
+ return # type: ignore
992
+ else:
930
993
  if isinstance(callback, AsyncPreProcessingCallback):
931
994
  await callback.pre_process(
932
- self, reply, version=self.protocol_version, **kwargs
995
+ self,
996
+ reply,
933
997
  )
934
998
  response = callback(
935
- reply,
999
+ cached_reply if cache_hit else reply,
936
1000
  version=self.protocol_version,
937
- **kwargs,
938
1001
  )
939
- await asyncio.gather(*maybe_wait)
940
- return response # type: ignore
1002
+ if self.cache and cacheable:
1003
+ if cache_hit and not use_cached:
1004
+ self.cache.feedback(
1005
+ command.name,
1006
+ keys[0],
1007
+ *command.arguments,
1008
+ match=cached_reply == reply,
1009
+ )
1010
+ if not cache_hit:
1011
+ self.cache.put(
1012
+ command.name,
1013
+ keys[0],
1014
+ *command.arguments,
1015
+ value=reply,
1016
+ )
1017
+ return response
941
1018
  except (RedisClusterException, BusyLoadingError, asyncio.CancelledError):
942
1019
  raise
943
1020
  except MovedError as e:
@@ -1118,7 +1195,7 @@ class RedisCluster(
1118
1195
 
1119
1196
  from coredis.pipeline import ClusterPipeline
1120
1197
 
1121
- return ClusterPipeline[AnyStr].proxy(
1198
+ return ClusterPipeline[AnyStr](
1122
1199
  client=self,
1123
1200
  transaction=transaction,
1124
1201
  watches=watches,
@@ -1178,7 +1255,7 @@ class RedisCluster(
1178
1255
  count: int | None = None,
1179
1256
  type_: StringT | None = None,
1180
1257
  ) -> AsyncIterator[AnyStr]:
1181
- await self.initialize()
1258
+ await self._ensure_initialized()
1182
1259
  for node in self.primaries:
1183
1260
  cursor = None
1184
1261
  while cursor != 0:
@@ -10,29 +10,49 @@ from __future__ import annotations
10
10
  from abc import ABC, abstractmethod
11
11
 
12
12
  from coredis.response._callbacks import NoopCallback
13
- from coredis.typing import AnyStr, Callable, Generic, R, ValueT
13
+ from coredis.typing import (
14
+ AnyStr,
15
+ Awaitable,
16
+ Callable,
17
+ ExecutionParameters,
18
+ Generic,
19
+ R,
20
+ RedisCommandP,
21
+ Unpack,
22
+ ValueT,
23
+ )
14
24
 
15
25
  # Command wrappers
16
26
  from .bitfield import BitFieldOperation
17
27
  from .function import Function, Library
18
28
  from .monitor import Monitor
19
29
  from .pubsub import ClusterPubSub, PubSub, ShardedPubSub
30
+ from .request import CommandRequest, CommandResponseT
20
31
  from .script import Script
21
32
 
22
33
 
23
34
  class CommandMixin(Generic[AnyStr], ABC):
24
35
  @abstractmethod
25
- async def execute_command(
36
+ def execute_command(
26
37
  self,
27
- command: bytes,
28
- *args: ValueT,
38
+ command: RedisCommandP,
29
39
  callback: Callable[..., R] = NoopCallback(),
30
- **options: ValueT | None,
31
- ) -> R:
32
- pass
40
+ **options: Unpack[ExecutionParameters],
41
+ ) -> Awaitable[R]: ...
42
+
43
+ @abstractmethod
44
+ def create_request(
45
+ self,
46
+ name: bytes,
47
+ *arguments: ValueT,
48
+ callback: Callable[..., R],
49
+ execution_parameters: ExecutionParameters | None = None,
50
+ ) -> CommandRequest[R]: ...
33
51
 
34
52
 
35
53
  __all__ = [
54
+ "CommandRequest",
55
+ "CommandResponseT",
36
56
  "BitFieldOperation",
37
57
  "ClusterPubSub",
38
58
  "Function",
@@ -1,11 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from coredis._utils import b
4
- from coredis.typing import Callable, ClassVar, ValueT
4
+ from coredis.typing import Callable, ClassVar, RedisValueT
5
5
 
6
6
 
7
7
  class KeySpec:
8
- READONLY: ClassVar[dict[bytes, Callable[[tuple[ValueT, ...]], tuple[ValueT, ...]]]] = {
8
+ READONLY: ClassVar[
9
+ dict[bytes, Callable[[tuple[RedisValueT, ...]], tuple[RedisValueT, ...]]]
10
+ ] = {
9
11
  b"BITCOUNT": lambda args: ((args[1],)),
10
12
  b"BITFIELD_RO": lambda args: ((args[1],)),
11
13
  b"BITOP": lambda args: (args[3 : (len(args))]),
@@ -123,7 +125,7 @@ class KeySpec:
123
125
  b"ZUNION": lambda args: (args[2 : 2 + int(args[1])]),
124
126
  b"ZUNIONSTORE": lambda args: (args[3 : 3 + int(args[2])]),
125
127
  }
126
- ALL: ClassVar[dict[bytes, Callable[[tuple[ValueT, ...]], tuple[ValueT, ...]]]] = {
128
+ ALL: ClassVar[dict[bytes, Callable[[tuple[RedisValueT, ...]], tuple[RedisValueT, ...]]]] = {
127
129
  b"OBJECT": lambda args: ((args[2],)),
128
130
  b"DEBUG OBJECT": lambda args: ((args[1],)),
129
131
  b"APPEND": lambda args: ((args[1],)),
@@ -150,6 +152,8 @@ class KeySpec:
150
152
  b"HDEL": lambda args: ((args[1],)),
151
153
  b"HEXPIRE": lambda args: ((args[1],)),
152
154
  b"HEXPIREAT": lambda args: ((args[1],)),
155
+ b"HGETDEL": lambda args: ((args[1],)),
156
+ b"HGETEX": lambda args: ((args[1],)),
153
157
  b"HINCRBY": lambda args: ((args[1],)),
154
158
  b"HINCRBYFLOAT": lambda args: ((args[1],)),
155
159
  b"HMSET": lambda args: ((args[1],)),
@@ -157,6 +161,7 @@ class KeySpec:
157
161
  b"HPEXPIRE": lambda args: ((args[1],)),
158
162
  b"HPEXPIREAT": lambda args: ((args[1],)),
159
163
  b"HSET": lambda args: ((args[1],)),
164
+ b"HSETEX": lambda args: ((args[1],)),
160
165
  b"HSETNX": lambda args: ((args[1],)),
161
166
  b"INCR": lambda args: ((args[1],)),
162
167
  b"INCRBY": lambda args: ((args[1],)),
@@ -373,12 +378,6 @@ class KeySpec:
373
378
  b"SSUBSCRIBE": lambda args: (args[1 : (len(args))]),
374
379
  b"SUNSUBSCRIBE": lambda args: (args[1 : (len(args))]),
375
380
  b"UNLINK": lambda args: (args[1 : (len(args))]),
376
- b"EXPIREMEMBER": lambda args: ((args[1],)),
377
- b"EXPIREMEMBERAT": lambda args: ((args[1],)),
378
- b"PEXPIREMEMBERAT": lambda args: ((args[1],)),
379
- b"KEYDB.HRENAME": lambda args: ((args[1],)),
380
- b"KEYDB.MEXISTS": lambda args: (args[1:]),
381
- b"OBJECT LASTMODIFIED": lambda args: ((args[1],)),
382
381
  b"JSON.DEBUG MEMORY": lambda args: (args[1],),
383
382
  b"JSON.DEL": lambda args: (args[1],),
384
383
  b"JSON.FORGET": lambda args: (args[1],),
@@ -499,7 +498,9 @@ class KeySpec:
499
498
  }
500
499
 
501
500
  @classmethod
502
- def extract_keys(cls, *arguments: ValueT, readonly_command: bool = False) -> tuple[ValueT, ...]:
501
+ def extract_keys(
502
+ cls, *arguments: RedisValueT, readonly_command: bool = False
503
+ ) -> tuple[RedisValueT, ...]:
503
504
  if len(arguments) <= 1:
504
505
  return ()
505
506
 
@@ -45,7 +45,7 @@ def normalized_time_milliseconds(value: int | datetime.datetime) -> int:
45
45
  return value
46
46
 
47
47
 
48
- async def check_version(
48
+ def check_version(
49
49
  instance: coredis.client.Client[Any],
50
50
  function_name: str,
51
51
  command_details: CommandDetails,