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.
- coredis/__init__.py +1 -3
- coredis/_packer.py +10 -10
- coredis/_protocols.py +23 -32
- coredis/_py_311_typing.py +20 -0
- coredis/_py_312_typing.py +17 -0
- coredis/_utils.py +49 -51
- coredis/_version.py +3 -3
- coredis/cache.py +57 -82
- coredis/client/__init__.py +1 -2
- coredis/client/basic.py +129 -56
- coredis/client/cluster.py +147 -70
- coredis/commands/__init__.py +27 -7
- coredis/commands/_key_spec.py +11 -10
- coredis/commands/_utils.py +1 -1
- coredis/commands/_validators.py +30 -20
- coredis/commands/_wrappers.py +19 -99
- coredis/commands/bitfield.py +10 -2
- coredis/commands/constants.py +20 -3
- coredis/commands/core.py +1627 -1246
- coredis/commands/function.py +21 -19
- coredis/commands/monitor.py +0 -71
- coredis/commands/pubsub.py +7 -142
- coredis/commands/request.py +108 -0
- coredis/commands/script.py +9 -9
- coredis/commands/sentinel.py +60 -49
- coredis/connection.py +14 -15
- coredis/exceptions.py +2 -2
- coredis/experimental/__init__.py +0 -4
- coredis/globals.py +3 -0
- coredis/modules/autocomplete.py +28 -30
- coredis/modules/base.py +15 -31
- coredis/modules/filters.py +269 -245
- coredis/modules/graph.py +61 -62
- coredis/modules/json.py +172 -140
- coredis/modules/response/_callbacks/autocomplete.py +5 -4
- coredis/modules/response/_callbacks/graph.py +34 -29
- coredis/modules/response/_callbacks/json.py +5 -3
- coredis/modules/response/_callbacks/search.py +49 -53
- coredis/modules/response/_callbacks/timeseries.py +18 -30
- coredis/modules/response/types.py +1 -5
- coredis/modules/search.py +186 -169
- coredis/modules/timeseries.py +184 -164
- coredis/parser.py +6 -19
- coredis/pipeline.py +391 -422
- coredis/pool/basic.py +7 -7
- coredis/pool/cluster.py +3 -3
- coredis/pool/nodemanager.py +10 -3
- coredis/response/_callbacks/__init__.py +76 -57
- coredis/response/_callbacks/acl.py +0 -3
- coredis/response/_callbacks/cluster.py +25 -16
- coredis/response/_callbacks/command.py +8 -6
- coredis/response/_callbacks/connection.py +4 -3
- coredis/response/_callbacks/geo.py +17 -13
- coredis/response/_callbacks/hash.py +13 -11
- coredis/response/_callbacks/keys.py +9 -5
- coredis/response/_callbacks/module.py +2 -3
- coredis/response/_callbacks/script.py +6 -8
- coredis/response/_callbacks/sentinel.py +21 -17
- coredis/response/_callbacks/server.py +36 -14
- coredis/response/_callbacks/sets.py +3 -4
- coredis/response/_callbacks/sorted_set.py +27 -24
- coredis/response/_callbacks/streams.py +22 -13
- coredis/response/_callbacks/strings.py +7 -6
- coredis/response/_callbacks/vector_sets.py +126 -0
- coredis/response/types.py +13 -4
- coredis/sentinel.py +1 -1
- coredis/stream.py +4 -3
- coredis/tokens.py +343 -16
- coredis/typing.py +432 -79
- {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/METADATA +4 -5
- coredis-5.0.0rc1.dist-info/RECORD +95 -0
- coredis/client/keydb.py +0 -336
- coredis/pipeline.pyi +0 -2103
- coredis-4.24.0.dist-info/RECORD +0 -93
- {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/WHEEL +0 -0
- {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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
|
-
|
|
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(
|
|
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[
|
|
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:
|
|
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(
|
|
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
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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
|
-
|
|
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:
|
|
750
|
-
*args: ValueT,
|
|
771
|
+
command: RedisCommandP,
|
|
751
772
|
callback: Callable[..., R] = NoopCallback(),
|
|
752
|
-
**kwargs:
|
|
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,
|
|
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:
|
|
771
|
-
*args: ValueT,
|
|
791
|
+
command: RedisCommandP,
|
|
772
792
|
callback: Callable[..., R] = NoopCallback(),
|
|
773
|
-
**kwargs:
|
|
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(
|
|
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))
|
|
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, *
|
|
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,
|
|
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:
|
|
817
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
904
|
-
|
|
905
|
-
and
|
|
906
|
-
and
|
|
907
|
-
|
|
908
|
-
self.
|
|
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
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
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,
|
|
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
|
-
|
|
940
|
-
|
|
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]
|
|
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.
|
|
1258
|
+
await self._ensure_initialized()
|
|
1182
1259
|
for node in self.primaries:
|
|
1183
1260
|
cursor = None
|
|
1184
1261
|
while cursor != 0:
|
coredis/commands/__init__.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
36
|
+
def execute_command(
|
|
26
37
|
self,
|
|
27
|
-
command:
|
|
28
|
-
*args: ValueT,
|
|
38
|
+
command: RedisCommandP,
|
|
29
39
|
callback: Callable[..., R] = NoopCallback(),
|
|
30
|
-
**options:
|
|
31
|
-
) -> R:
|
|
32
|
-
|
|
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",
|
coredis/commands/_key_spec.py
CHANGED
|
@@ -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,
|
|
4
|
+
from coredis.typing import Callable, ClassVar, RedisValueT
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class KeySpec:
|
|
8
|
-
READONLY: ClassVar[
|
|
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[
|
|
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(
|
|
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
|
|
coredis/commands/_utils.py
CHANGED