1from __future__ import annotations
2
3import asyncio
4from typing import (
5 TYPE_CHECKING,
6 Any,
7 AsyncIterator,
8 Awaitable,
9 Dict,
10 Iterable,
11 Iterator,
12 List,
13 Literal,
14 Mapping,
15 NoReturn,
16 Sequence,
17 overload,
18)
19
20from redis.crc import key_slot
21from redis.exceptions import RedisClusterException, RedisError
22from redis.typing import (
23 AnyKeyT,
24 AsyncClientProtocol,
25 ClusterCommandsProtocol,
26 ClusterLinksResponse,
27 ClusterNodeDetail,
28 ClusterShardsResponse,
29 EncodableT,
30 KeysT,
31 KeyT,
32 PatternT,
33 ResponseT,
34 StralgoResponse,
35 SyncClientProtocol,
36)
37from redis.utils import deprecated_function
38
39from .core import (
40 ACLCommands,
41 AsyncACLCommands,
42 AsyncDataAccessCommands,
43 AsyncFunctionCommands,
44 AsyncManagementCommands,
45 AsyncModuleCommands,
46 AsyncScriptCommands,
47 DataAccessCommands,
48 FunctionCommands,
49 HotkeysMetricsTypes,
50 ManagementCommands,
51 ModuleCommands,
52 PubSubCommands,
53 ScriptCommands,
54)
55from .helpers import list_or_args
56from .redismodules import AsyncRedisModuleCommands, RedisModuleCommands
57
58if TYPE_CHECKING:
59 from redis.asyncio.cluster import TargetNodesT
60
61# Not complete, but covers the major ones
62# https://redis.io/commands
63READ_COMMANDS = frozenset(
64 [
65 # Bit Operations
66 "BITCOUNT",
67 "BITFIELD_RO",
68 "BITPOS",
69 # Scripting
70 "EVAL_RO",
71 "EVALSHA_RO",
72 "FCALL_RO",
73 # Key Operations
74 "DBSIZE",
75 "DIGEST",
76 "DUMP",
77 "EXISTS",
78 "EXPIRETIME",
79 "PEXPIRETIME",
80 "KEYS",
81 "SCAN",
82 "PTTL",
83 "RANDOMKEY",
84 "TTL",
85 "TYPE",
86 # String Operations
87 "GET",
88 "GETBIT",
89 "GETRANGE",
90 "MGET",
91 "STRLEN",
92 "LCS",
93 # Geo Operations
94 "GEODIST",
95 "GEOHASH",
96 "GEOPOS",
97 "GEOSEARCH",
98 # Hash Operations
99 "HEXISTS",
100 "HGET",
101 "HGETALL",
102 "HKEYS",
103 "HLEN",
104 "HMGET",
105 "HSTRLEN",
106 "HVALS",
107 "HRANDFIELD",
108 "HEXPIRETIME",
109 "HPEXPIRETIME",
110 "HTTL",
111 "HPTTL",
112 "HSCAN",
113 # List Operations
114 "LINDEX",
115 "LPOS",
116 "LLEN",
117 "LRANGE",
118 # Set Operations
119 "SCARD",
120 "SDIFF",
121 "SDIFFCARD",
122 "SINTER",
123 "SINTERCARD",
124 "SISMEMBER",
125 "SMISMEMBER",
126 "SMEMBERS",
127 "SRANDMEMBER",
128 "SUNION",
129 "SUNIONCARD",
130 "SSCAN",
131 # Sorted Set Operations
132 "ZCARD",
133 "ZCOUNT",
134 "ZDIFF",
135 "ZINTER",
136 "ZINTERCARD",
137 "ZLEXCOUNT",
138 "ZMSCORE",
139 "ZRANDMEMBER",
140 "ZRANGE",
141 "ZRANGEBYLEX",
142 "ZRANGEBYSCORE",
143 "ZRANK",
144 "ZREVRANGE",
145 "ZREVRANGEBYLEX",
146 "ZREVRANGEBYSCORE",
147 "ZREVRANK",
148 "ZSCAN",
149 "ZSCORE",
150 "ZUNION",
151 # Stream Operations
152 "XLEN",
153 "XPENDING",
154 "XRANGE",
155 "XREAD",
156 "XREVRANGE",
157 # JSON Module
158 "JSON.ARRINDEX",
159 "JSON.ARRLEN",
160 "JSON.GET",
161 "JSON.MGET",
162 "JSON.OBJKEYS",
163 "JSON.OBJLEN",
164 "JSON.RESP",
165 "JSON.STRLEN",
166 "JSON.TYPE",
167 # RediSearch Module
168 "FT.EXPLAIN",
169 "FT.INFO",
170 "FT.PROFILE",
171 "FT.SEARCH",
172 ]
173)
174
175
176class ClusterMultiKeyCommands(ClusterCommandsProtocol):
177 """
178 A class containing commands that handle more than one key
179 """
180
181 def _partition_keys_by_slot(self, keys: Iterable[KeyT]) -> Dict[int, List[KeyT]]:
182 """Split keys into a dictionary that maps a slot to a list of keys."""
183
184 slots_to_keys = {}
185 for key in keys:
186 slot = key_slot(self.encoder.encode(key))
187 slots_to_keys.setdefault(slot, []).append(key)
188
189 return slots_to_keys
190
191 def _partition_pairs_by_slot(
192 self, mapping: Mapping[AnyKeyT, EncodableT]
193 ) -> Dict[int, List[EncodableT]]:
194 """Split pairs into a dictionary that maps a slot to a list of pairs."""
195
196 slots_to_pairs = {}
197 for pair in mapping.items():
198 slot = key_slot(self.encoder.encode(pair[0]))
199 slots_to_pairs.setdefault(slot, []).extend(pair)
200
201 return slots_to_pairs
202
203 def _execute_pipeline_by_slot(
204 self, command: str, slots_to_args: Mapping[int, Iterable[EncodableT]]
205 ) -> List[Any]:
206 read_from_replicas = self.read_from_replicas and command in READ_COMMANDS
207 pipe = self.pipeline()
208 [
209 pipe.execute_command(
210 command,
211 *slot_args,
212 target_nodes=[
213 self.nodes_manager.get_node_from_slot(slot, read_from_replicas)
214 ],
215 )
216 for slot, slot_args in slots_to_args.items()
217 ]
218 return pipe.execute()
219
220 def _reorder_keys_by_command(
221 self,
222 keys: Iterable[KeyT],
223 slots_to_args: Mapping[int, Iterable[EncodableT]],
224 responses: Iterable[Any],
225 ) -> List[Any]:
226 results = {
227 k: v
228 for slot_values, response in zip(slots_to_args.values(), responses)
229 for k, v in zip(slot_values, response)
230 }
231 return [results[key] for key in keys]
232
233 def mget_nonatomic(self, keys: KeysT, *args: KeyT) -> List[Any | None]:
234 """
235 Splits the keys into different slots and then calls MGET
236 for the keys of every slot. This operation will not be atomic
237 if keys belong to more than one slot.
238
239 Returns a list of values ordered identically to ``keys``
240
241 For more information see https://redis.io/commands/mget
242 """
243
244 # Concatenate all keys into a list
245 keys = list_or_args(keys, args)
246
247 # Split keys into slots
248 slots_to_keys = self._partition_keys_by_slot(keys)
249
250 # Execute commands using a pipeline
251 res = self._execute_pipeline_by_slot("MGET", slots_to_keys)
252
253 # Reorder keys in the order the user provided & return
254 return self._reorder_keys_by_command(keys, slots_to_keys, res)
255
256 def mset_nonatomic(self, mapping: Mapping[AnyKeyT, EncodableT]) -> List[bool]:
257 """
258 Sets key/values based on a mapping. Mapping is a dictionary of
259 key/value pairs. Both keys and values should be strings or types that
260 can be cast to a string via str().
261
262 Splits the keys into different slots and then calls MSET
263 for the keys of every slot. This operation will not be atomic
264 if keys belong to more than one slot.
265
266 For more information see https://redis.io/commands/mset
267 """
268
269 # Partition the keys by slot
270 slots_to_pairs = self._partition_pairs_by_slot(mapping)
271
272 # Execute commands using a pipeline & return list of replies
273 return self._execute_pipeline_by_slot("MSET", slots_to_pairs)
274
275 def _split_command_across_slots(self, command: str, *keys: KeyT) -> int:
276 """
277 Runs the given command once for the keys
278 of each slot. Returns the sum of the return values.
279 """
280
281 # Partition the keys by slot
282 slots_to_keys = self._partition_keys_by_slot(keys)
283
284 # Sum up the reply from each command
285 return sum(self._execute_pipeline_by_slot(command, slots_to_keys))
286
287 @overload
288 def exists(self: SyncClientProtocol, *keys: KeyT) -> int: ...
289
290 @overload
291 def exists(self: AsyncClientProtocol, *keys: KeyT) -> Awaitable[int]: ...
292
293 def exists(self, *keys: KeyT) -> int | Awaitable[int]:
294 """
295 Returns the number of ``names`` that exist in the
296 whole cluster. The keys are first split up into slots
297 and then an EXISTS command is sent for every slot
298
299 For more information see https://redis.io/commands/exists
300 """
301 return self._split_command_across_slots("EXISTS", *keys)
302
303 @overload
304 def delete(self: SyncClientProtocol, *keys: KeyT) -> int: ...
305
306 @overload
307 def delete(self: AsyncClientProtocol, *keys: KeyT) -> Awaitable[int]: ...
308
309 def delete(self, *keys: KeyT) -> int | Awaitable[int]:
310 """
311 Deletes the given keys in the cluster.
312 The keys are first split up into slots
313 and then an DEL command is sent for every slot
314
315 Non-existent keys are ignored.
316 Returns the number of keys that were deleted.
317
318 For more information see https://redis.io/commands/del
319 """
320 return self._split_command_across_slots("DEL", *keys)
321
322 @overload
323 def touch(self: SyncClientProtocol, *keys: KeyT) -> int: ...
324
325 @overload
326 def touch(self: AsyncClientProtocol, *keys: KeyT) -> Awaitable[int]: ...
327
328 def touch(self, *keys: KeyT) -> int | Awaitable[int]:
329 """
330 Updates the last access time of given keys across the
331 cluster.
332
333 The keys are first split up into slots
334 and then an TOUCH command is sent for every slot
335
336 Non-existent keys are ignored.
337 Returns the number of keys that were touched.
338
339 For more information see https://redis.io/commands/touch
340 """
341 return self._split_command_across_slots("TOUCH", *keys)
342
343 @overload
344 def unlink(self: SyncClientProtocol, *keys: KeyT) -> int: ...
345
346 @overload
347 def unlink(self: AsyncClientProtocol, *keys: KeyT) -> Awaitable[int]: ...
348
349 def unlink(self, *keys: KeyT) -> int | Awaitable[int]:
350 """
351 Remove the specified keys in a different thread.
352
353 The keys are first split up into slots
354 and then an TOUCH command is sent for every slot
355
356 Non-existent keys are ignored.
357 Returns the number of keys that were unlinked.
358
359 For more information see https://redis.io/commands/unlink
360 """
361 return self._split_command_across_slots("UNLINK", *keys)
362
363
364class AsyncClusterMultiKeyCommands(ClusterMultiKeyCommands):
365 """
366 A class containing commands that handle more than one key
367 """
368
369 async def mget_nonatomic(self, keys: KeysT, *args: KeyT) -> List[Any | None]:
370 """
371 Splits the keys into different slots and then calls MGET
372 for the keys of every slot. This operation will not be atomic
373 if keys belong to more than one slot.
374
375 Returns a list of values ordered identically to ``keys``
376
377 For more information see https://redis.io/commands/mget
378 """
379
380 # Concatenate all keys into a list
381 keys = list_or_args(keys, args)
382
383 # Split keys into slots
384 slots_to_keys = self._partition_keys_by_slot(keys)
385
386 # Execute commands using a pipeline
387 res = await self._execute_pipeline_by_slot("MGET", slots_to_keys)
388
389 # Reorder keys in the order the user provided & return
390 return self._reorder_keys_by_command(keys, slots_to_keys, res)
391
392 async def mset_nonatomic(self, mapping: Mapping[AnyKeyT, EncodableT]) -> List[bool]:
393 """
394 Sets key/values based on a mapping. Mapping is a dictionary of
395 key/value pairs. Both keys and values should be strings or types that
396 can be cast to a string via str().
397
398 Splits the keys into different slots and then calls MSET
399 for the keys of every slot. This operation will not be atomic
400 if keys belong to more than one slot.
401
402 For more information see https://redis.io/commands/mset
403 """
404
405 # Partition the keys by slot
406 slots_to_pairs = self._partition_pairs_by_slot(mapping)
407
408 # Execute commands using a pipeline & return list of replies
409 return await self._execute_pipeline_by_slot("MSET", slots_to_pairs)
410
411 async def _split_command_across_slots(self, command: str, *keys: KeyT) -> int:
412 """
413 Runs the given command once for the keys
414 of each slot. Returns the sum of the return values.
415 """
416
417 # Partition the keys by slot
418 slots_to_keys = self._partition_keys_by_slot(keys)
419
420 # Sum up the reply from each command
421 return sum(await self._execute_pipeline_by_slot(command, slots_to_keys))
422
423 async def _execute_pipeline_by_slot(
424 self, command: str, slots_to_args: Mapping[int, Iterable[EncodableT]]
425 ) -> List[Any]:
426 if self._initialize:
427 await self.initialize()
428 read_from_replicas = self.read_from_replicas and command in READ_COMMANDS
429 pipe = self.pipeline()
430 [
431 pipe.execute_command(
432 command,
433 *slot_args,
434 target_nodes=[
435 self.nodes_manager.get_node_from_slot(slot, read_from_replicas)
436 ],
437 )
438 for slot, slot_args in slots_to_args.items()
439 ]
440 return await pipe.execute()
441
442
443class ClusterManagementCommands(ManagementCommands):
444 """
445 A class for Redis Cluster management commands
446
447 The class inherits from Redis's core ManagementCommands class and do the
448 required adjustments to work with cluster mode
449 """
450
451 def slaveof(self, *args, **kwargs) -> NoReturn:
452 """
453 Make the server a replica of another instance, or promote it as master.
454
455 For more information see https://redis.io/commands/slaveof
456 """
457 raise RedisClusterException("SLAVEOF is not supported in cluster mode")
458
459 def replicaof(self, *args, **kwargs) -> NoReturn:
460 """
461 Make the server a replica of another instance, or promote it as master.
462
463 For more information see https://redis.io/commands/replicaof
464 """
465 raise RedisClusterException("REPLICAOF is not supported in cluster mode")
466
467 def swapdb(self, *args, **kwargs) -> NoReturn:
468 """
469 Swaps two Redis databases.
470
471 For more information see https://redis.io/commands/swapdb
472 """
473 raise RedisClusterException("SWAPDB is not supported in cluster mode")
474
475 @overload
476 def cluster_myid(
477 self: SyncClientProtocol, target_node: "TargetNodesT"
478 ) -> bytes | str: ...
479
480 @overload
481 def cluster_myid(
482 self: AsyncClientProtocol, target_node: "TargetNodesT"
483 ) -> Awaitable[bytes | str]: ...
484
485 def cluster_myid(self, target_node: "TargetNodesT") -> (bytes | str) | Awaitable[
486 bytes | str
487 ]:
488 """
489 Returns the node's id.
490
491 :target_node: 'ClusterNode'
492 The node to execute the command on
493
494 For more information check https://redis.io/commands/cluster-myid/
495 """
496 return self.execute_command("CLUSTER MYID", target_nodes=target_node)
497
498 @overload
499 def cluster_addslots(
500 self: SyncClientProtocol, target_node: "TargetNodesT", *slots: EncodableT
501 ) -> bool: ...
502
503 @overload
504 def cluster_addslots(
505 self: AsyncClientProtocol, target_node: "TargetNodesT", *slots: EncodableT
506 ) -> Awaitable[bool]: ...
507
508 def cluster_addslots(
509 self, target_node: "TargetNodesT", *slots: EncodableT
510 ) -> bool | Awaitable[bool]:
511 """
512 Assign new hash slots to receiving node. Sends to specified node.
513
514 :target_node: 'ClusterNode'
515 The node to execute the command on
516
517 For more information see https://redis.io/commands/cluster-addslots
518 """
519 return self.execute_command(
520 "CLUSTER ADDSLOTS", *slots, target_nodes=target_node
521 )
522
523 @overload
524 def cluster_addslotsrange(
525 self: SyncClientProtocol, target_node: "TargetNodesT", *slots: EncodableT
526 ) -> bool: ...
527
528 @overload
529 def cluster_addslotsrange(
530 self: AsyncClientProtocol, target_node: "TargetNodesT", *slots: EncodableT
531 ) -> Awaitable[bool]: ...
532
533 def cluster_addslotsrange(
534 self, target_node: "TargetNodesT", *slots: EncodableT
535 ) -> bool | Awaitable[bool]:
536 """
537 Similar to the CLUSTER ADDSLOTS command.
538 The difference between the two commands is that ADDSLOTS takes a list of slots
539 to assign to the node, while ADDSLOTSRANGE takes a list of slot ranges
540 (specified by start and end slots) to assign to the node.
541
542 :target_node: 'ClusterNode'
543 The node to execute the command on
544
545 For more information see https://redis.io/commands/cluster-addslotsrange
546 """
547 return self.execute_command(
548 "CLUSTER ADDSLOTSRANGE", *slots, target_nodes=target_node
549 )
550
551 @overload
552 def cluster_countkeysinslot(self: SyncClientProtocol, slot_id: int) -> int: ...
553
554 @overload
555 def cluster_countkeysinslot(
556 self: AsyncClientProtocol, slot_id: int
557 ) -> Awaitable[int]: ...
558
559 def cluster_countkeysinslot(self, slot_id: int) -> int | Awaitable[int]:
560 """
561 Return the number of local keys in the specified hash slot
562 Send to node based on specified slot_id
563
564 For more information see https://redis.io/commands/cluster-countkeysinslot
565 """
566 return self.execute_command("CLUSTER COUNTKEYSINSLOT", slot_id)
567
568 @overload
569 def cluster_count_failure_report(self: SyncClientProtocol, node_id: str) -> int: ...
570
571 @overload
572 def cluster_count_failure_report(
573 self: AsyncClientProtocol, node_id: str
574 ) -> Awaitable[int]: ...
575
576 def cluster_count_failure_report(self, node_id: str) -> int | Awaitable[int]:
577 """
578 Return the number of failure reports active for a given node
579 Sends to a random node
580
581 For more information see https://redis.io/commands/cluster-count-failure-reports
582 """
583 return self.execute_command("CLUSTER COUNT-FAILURE-REPORTS", node_id)
584
585 def cluster_delslots(self, *slots: EncodableT) -> List[bool]:
586 """
587 Set hash slots as unbound in the cluster.
588 It determines by it self what node the slot is in and sends it there
589
590 Returns a list of the results for each processed slot.
591
592 For more information see https://redis.io/commands/cluster-delslots
593 """
594 return [self.execute_command("CLUSTER DELSLOTS", slot) for slot in slots]
595
596 @overload
597 def cluster_delslotsrange(self: SyncClientProtocol, *slots: EncodableT) -> bool: ...
598
599 @overload
600 def cluster_delslotsrange(
601 self: AsyncClientProtocol, *slots: EncodableT
602 ) -> Awaitable[bool]: ...
603
604 def cluster_delslotsrange(self, *slots: EncodableT) -> bool | Awaitable[bool]:
605 """
606 Similar to the CLUSTER DELSLOTS command.
607 The difference is that CLUSTER DELSLOTS takes a list of hash slots to remove
608 from the node, while CLUSTER DELSLOTSRANGE takes a list of slot ranges to remove
609 from the node.
610
611 For more information see https://redis.io/commands/cluster-delslotsrange
612 """
613 return self.execute_command("CLUSTER DELSLOTSRANGE", *slots)
614
615 @overload
616 def cluster_failover(
617 self: SyncClientProtocol,
618 target_node: "TargetNodesT",
619 option: str | None = None,
620 ) -> bool: ...
621
622 @overload
623 def cluster_failover(
624 self: AsyncClientProtocol,
625 target_node: "TargetNodesT",
626 option: str | None = None,
627 ) -> Awaitable[bool]: ...
628
629 def cluster_failover(
630 self, target_node: "TargetNodesT", option: str | None = None
631 ) -> bool | Awaitable[bool]:
632 """
633 Forces a slave to perform a manual failover of its master
634 Sends to specified node
635
636 :target_node: 'ClusterNode'
637 The node to execute the command on
638
639 For more information see https://redis.io/commands/cluster-failover
640 """
641 if option:
642 if option.upper() not in ["FORCE", "TAKEOVER"]:
643 raise RedisError(
644 f"Invalid option for CLUSTER FAILOVER command: {option}"
645 )
646 else:
647 return self.execute_command(
648 "CLUSTER FAILOVER", option, target_nodes=target_node
649 )
650 else:
651 return self.execute_command("CLUSTER FAILOVER", target_nodes=target_node)
652
653 @overload
654 def cluster_info(
655 self: SyncClientProtocol, target_nodes: "TargetNodesT" | None = None
656 ) -> dict[str, str]: ...
657
658 @overload
659 def cluster_info(
660 self: AsyncClientProtocol, target_nodes: "TargetNodesT" | None = None
661 ) -> Awaitable[dict[str, str]]: ...
662
663 def cluster_info(
664 self, target_nodes: "TargetNodesT" | None = None
665 ) -> dict[str, str] | Awaitable[dict[str, str]]:
666 """
667 Provides info about Redis Cluster node state.
668 The command will be sent to a random node in the cluster if no target
669 node is specified.
670
671 For more information see https://redis.io/commands/cluster-info
672 """
673 return self.execute_command("CLUSTER INFO", target_nodes=target_nodes)
674
675 @overload
676 def cluster_keyslot(self: SyncClientProtocol, key: str) -> int: ...
677
678 @overload
679 def cluster_keyslot(self: AsyncClientProtocol, key: str) -> Awaitable[int]: ...
680
681 def cluster_keyslot(self, key: str) -> int | Awaitable[int]:
682 """
683 Returns the hash slot of the specified key
684 Sends to random node in the cluster
685
686 For more information see https://redis.io/commands/cluster-keyslot
687 """
688 return self.execute_command("CLUSTER KEYSLOT", key)
689
690 @overload
691 def cluster_meet(
692 self: SyncClientProtocol,
693 host: str,
694 port: int,
695 target_nodes: "TargetNodesT" | None = None,
696 ) -> bool: ...
697
698 @overload
699 def cluster_meet(
700 self: AsyncClientProtocol,
701 host: str,
702 port: int,
703 target_nodes: "TargetNodesT" | None = None,
704 ) -> Awaitable[bool]: ...
705
706 def cluster_meet(
707 self, host: str, port: int, target_nodes: "TargetNodesT" | None = None
708 ) -> bool | Awaitable[bool]:
709 """
710 Force a node cluster to handshake with another node.
711 Sends to specified node.
712
713 For more information see https://redis.io/commands/cluster-meet
714 """
715 return self.execute_command(
716 "CLUSTER MEET", host, port, target_nodes=target_nodes
717 )
718
719 @overload
720 def cluster_nodes(self: SyncClientProtocol) -> dict[str, ClusterNodeDetail]: ...
721
722 @overload
723 def cluster_nodes(
724 self: AsyncClientProtocol,
725 ) -> Awaitable[dict[str, ClusterNodeDetail]]: ...
726
727 def cluster_nodes(
728 self,
729 ) -> dict[str, ClusterNodeDetail] | Awaitable[dict[str, ClusterNodeDetail]]:
730 """
731 Get Cluster config for the node.
732 Sends to random node in the cluster
733
734 For more information see https://redis.io/commands/cluster-nodes
735 """
736 return self.execute_command("CLUSTER NODES")
737
738 @overload
739 def cluster_replicate(
740 self: SyncClientProtocol, target_nodes: "TargetNodesT", node_id: str
741 ) -> bool: ...
742
743 @overload
744 def cluster_replicate(
745 self: AsyncClientProtocol, target_nodes: "TargetNodesT", node_id: str
746 ) -> Awaitable[bool]: ...
747
748 def cluster_replicate(
749 self, target_nodes: "TargetNodesT", node_id: str
750 ) -> bool | Awaitable[bool]:
751 """
752 Reconfigure a node as a slave of the specified master node
753
754 For more information see https://redis.io/commands/cluster-replicate
755 """
756 return self.execute_command(
757 "CLUSTER REPLICATE", node_id, target_nodes=target_nodes
758 )
759
760 @overload
761 def cluster_reset(
762 self: SyncClientProtocol,
763 soft: bool = True,
764 target_nodes: "TargetNodesT" | None = None,
765 ) -> bool: ...
766
767 @overload
768 def cluster_reset(
769 self: AsyncClientProtocol,
770 soft: bool = True,
771 target_nodes: "TargetNodesT" | None = None,
772 ) -> Awaitable[bool]: ...
773
774 def cluster_reset(
775 self, soft: bool = True, target_nodes: "TargetNodesT" | None = None
776 ) -> bool | Awaitable[bool]:
777 """
778 Reset a Redis Cluster node
779
780 If 'soft' is True then it will send 'SOFT' argument
781 If 'soft' is False then it will send 'HARD' argument
782
783 For more information see https://redis.io/commands/cluster-reset
784 """
785 return self.execute_command(
786 "CLUSTER RESET", b"SOFT" if soft else b"HARD", target_nodes=target_nodes
787 )
788
789 @overload
790 def cluster_save_config(
791 self: SyncClientProtocol, target_nodes: "TargetNodesT" | None = None
792 ) -> bool: ...
793
794 @overload
795 def cluster_save_config(
796 self: AsyncClientProtocol, target_nodes: "TargetNodesT" | None = None
797 ) -> Awaitable[bool]: ...
798
799 def cluster_save_config(
800 self, target_nodes: "TargetNodesT" | None = None
801 ) -> bool | Awaitable[bool]:
802 """
803 Forces the node to save cluster state on disk
804
805 For more information see https://redis.io/commands/cluster-saveconfig
806 """
807 return self.execute_command("CLUSTER SAVECONFIG", target_nodes=target_nodes)
808
809 @overload
810 def cluster_get_keys_in_slot(
811 self: SyncClientProtocol, slot: int, num_keys: int
812 ) -> list[bytes | str]: ...
813
814 @overload
815 def cluster_get_keys_in_slot(
816 self: AsyncClientProtocol, slot: int, num_keys: int
817 ) -> Awaitable[list[bytes | str]]: ...
818
819 def cluster_get_keys_in_slot(
820 self, slot: int, num_keys: int
821 ) -> list[bytes | str] | Awaitable[list[bytes | str]]:
822 """
823 Returns the number of keys in the specified cluster slot
824
825 For more information see https://redis.io/commands/cluster-getkeysinslot
826 """
827 return self.execute_command("CLUSTER GETKEYSINSLOT", slot, num_keys)
828
829 @overload
830 def cluster_set_config_epoch(
831 self: SyncClientProtocol, epoch: int, target_nodes: "TargetNodesT" | None = None
832 ) -> bool: ...
833
834 @overload
835 def cluster_set_config_epoch(
836 self: AsyncClientProtocol,
837 epoch: int,
838 target_nodes: "TargetNodesT" | None = None,
839 ) -> Awaitable[bool]: ...
840
841 def cluster_set_config_epoch(
842 self, epoch: int, target_nodes: "TargetNodesT" | None = None
843 ) -> bool | Awaitable[bool]:
844 """
845 Set the configuration epoch in a new node
846
847 For more information see https://redis.io/commands/cluster-set-config-epoch
848 """
849 return self.execute_command(
850 "CLUSTER SET-CONFIG-EPOCH", epoch, target_nodes=target_nodes
851 )
852
853 @overload
854 def cluster_setslot(
855 self: SyncClientProtocol,
856 target_node: "TargetNodesT",
857 node_id: str,
858 slot_id: int,
859 state: str,
860 ) -> bool: ...
861
862 @overload
863 def cluster_setslot(
864 self: AsyncClientProtocol,
865 target_node: "TargetNodesT",
866 node_id: str,
867 slot_id: int,
868 state: str,
869 ) -> Awaitable[bool]: ...
870
871 def cluster_setslot(
872 self, target_node: "TargetNodesT", node_id: str, slot_id: int, state: str
873 ) -> bool | Awaitable[bool]:
874 """
875 Bind an hash slot to a specific node
876
877 :target_node: 'ClusterNode'
878 The node to execute the command on
879
880 For more information see https://redis.io/commands/cluster-setslot
881 """
882 if state.upper() in ("IMPORTING", "NODE", "MIGRATING"):
883 return self.execute_command(
884 "CLUSTER SETSLOT", slot_id, state, node_id, target_nodes=target_node
885 )
886 elif state.upper() == "STABLE":
887 raise RedisError('For "stable" state please use cluster_setslot_stable')
888 else:
889 raise RedisError(f"Invalid slot state: {state}")
890
891 @overload
892 def cluster_setslot_stable(self: SyncClientProtocol, slot_id: int) -> bool: ...
893
894 @overload
895 def cluster_setslot_stable(
896 self: AsyncClientProtocol, slot_id: int
897 ) -> Awaitable[bool]: ...
898
899 def cluster_setslot_stable(self, slot_id: int) -> bool | Awaitable[bool]:
900 """
901 Clears migrating / importing state from the slot.
902 It determines by it self what node the slot is in and sends it there.
903
904 For more information see https://redis.io/commands/cluster-setslot
905 """
906 return self.execute_command("CLUSTER SETSLOT", slot_id, "STABLE")
907
908 @overload
909 def cluster_replicas(
910 self: SyncClientProtocol,
911 node_id: str,
912 target_nodes: "TargetNodesT" | None = None,
913 ) -> dict[str, ClusterNodeDetail]: ...
914
915 @overload
916 def cluster_replicas(
917 self: AsyncClientProtocol,
918 node_id: str,
919 target_nodes: "TargetNodesT" | None = None,
920 ) -> Awaitable[dict[str, ClusterNodeDetail]]: ...
921
922 def cluster_replicas(
923 self, node_id: str, target_nodes: "TargetNodesT" | None = None
924 ) -> dict[str, ClusterNodeDetail] | Awaitable[dict[str, ClusterNodeDetail]]:
925 """
926 Provides a list of replica nodes replicating from the specified primary
927 target node.
928
929 For more information see https://redis.io/commands/cluster-replicas
930 """
931 return self.execute_command(
932 "CLUSTER REPLICAS", node_id, target_nodes=target_nodes
933 )
934
935 @overload
936 def cluster_slots(
937 self: SyncClientProtocol, target_nodes: "TargetNodesT" | None = None
938 ) -> list[Any]: ...
939
940 @overload
941 def cluster_slots(
942 self: AsyncClientProtocol, target_nodes: "TargetNodesT" | None = None
943 ) -> Awaitable[list[Any]]: ...
944
945 def cluster_slots(
946 self, target_nodes: "TargetNodesT" | None = None
947 ) -> list[Any] | Awaitable[list[Any]]:
948 """
949 Get array of Cluster slot to node mappings
950
951 For more information see https://redis.io/commands/cluster-slots
952 """
953 return self.execute_command("CLUSTER SLOTS", target_nodes=target_nodes)
954
955 @overload
956 def cluster_shards(
957 self: SyncClientProtocol, target_nodes: "TargetNodesT" | None = None
958 ) -> ClusterShardsResponse: ...
959
960 @overload
961 def cluster_shards(
962 self: AsyncClientProtocol, target_nodes: "TargetNodesT" | None = None
963 ) -> Awaitable[ClusterShardsResponse]: ...
964
965 def cluster_shards(
966 self, target_nodes: "TargetNodesT" | None = None
967 ) -> ClusterShardsResponse | Awaitable[ClusterShardsResponse]:
968 """
969 Returns details about the shards of the cluster.
970
971 For more information see https://redis.io/commands/cluster-shards
972 """
973 return self.execute_command("CLUSTER SHARDS", target_nodes=target_nodes)
974
975 @overload
976 def cluster_myshardid(
977 self: SyncClientProtocol, target_nodes: "TargetNodesT" | None = None
978 ) -> bytes | str: ...
979
980 @overload
981 def cluster_myshardid(
982 self: AsyncClientProtocol, target_nodes: "TargetNodesT" | None = None
983 ) -> Awaitable[bytes | str]: ...
984
985 def cluster_myshardid(self, target_nodes: "TargetNodesT" | None = None) -> (
986 bytes | str
987 ) | Awaitable[bytes | str]:
988 """
989 Returns the shard ID of the node.
990
991 For more information see https://redis.io/commands/cluster-myshardid/
992 """
993 return self.execute_command("CLUSTER MYSHARDID", target_nodes=target_nodes)
994
995 @overload
996 def cluster_links(
997 self: SyncClientProtocol, target_node: "TargetNodesT"
998 ) -> ClusterLinksResponse: ...
999
1000 @overload
1001 def cluster_links(
1002 self: AsyncClientProtocol, target_node: "TargetNodesT"
1003 ) -> Awaitable[ClusterLinksResponse]: ...
1004
1005 def cluster_links(
1006 self, target_node: "TargetNodesT"
1007 ) -> ClusterLinksResponse | Awaitable[ClusterLinksResponse]:
1008 """
1009 Each node in a Redis Cluster maintains a pair of long-lived TCP link with each
1010 peer in the cluster: One for sending outbound messages towards the peer and one
1011 for receiving inbound messages from the peer.
1012
1013 This command outputs information of all such peer links as an array.
1014
1015 For more information see https://redis.io/commands/cluster-links
1016 """
1017 return self.execute_command("CLUSTER LINKS", target_nodes=target_node)
1018
1019 def cluster_flushslots(self, target_nodes: "TargetNodesT" | None = None) -> None:
1020 raise NotImplementedError(
1021 "CLUSTER FLUSHSLOTS is intentionally not implemented in the client."
1022 )
1023
1024 def cluster_bumpepoch(self, target_nodes: "TargetNodesT" | None = None) -> None:
1025 raise NotImplementedError(
1026 "CLUSTER BUMPEPOCH is intentionally not implemented in the client."
1027 )
1028
1029 def readonly(self, target_nodes: "TargetNodesT" | None = None) -> ResponseT:
1030 """
1031 Enables read queries.
1032 The command will be sent to the default cluster node if target_nodes is
1033 not specified.
1034
1035 For more information see https://redis.io/commands/readonly
1036 """
1037 if target_nodes == "replicas" or target_nodes == "all":
1038 # read_from_replicas will only be enabled if the READONLY command
1039 # is sent to all replicas
1040 self.read_from_replicas = True
1041 return self.execute_command("READONLY", target_nodes=target_nodes)
1042
1043 def readwrite(self, target_nodes: "TargetNodesT" | None = None) -> ResponseT:
1044 """
1045 Disables read queries.
1046 The command will be sent to the default cluster node if target_nodes is
1047 not specified.
1048
1049 For more information see https://redis.io/commands/readwrite
1050 """
1051 # Reset read from replicas flag
1052 self.read_from_replicas = False
1053 return self.execute_command("READWRITE", target_nodes=target_nodes)
1054
1055 @deprecated_function(
1056 version="7.2.0",
1057 reason="Use client-side caching feature instead.",
1058 )
1059 def client_tracking_on(
1060 self,
1061 clientid: int | None = None,
1062 prefix: Sequence[KeyT] = [],
1063 bcast: bool = False,
1064 optin: bool = False,
1065 optout: bool = False,
1066 noloop: bool = False,
1067 target_nodes: "TargetNodesT" | None = "all",
1068 ) -> ResponseT:
1069 """
1070 Enables the tracking feature of the Redis server, that is used
1071 for server assisted client side caching.
1072
1073 When clientid is provided - in target_nodes only the node that owns the
1074 connection with this id should be provided.
1075 When clientid is not provided - target_nodes can be any node.
1076
1077 For more information see https://redis.io/commands/client-tracking
1078 """
1079 return self.client_tracking(
1080 True,
1081 clientid,
1082 prefix,
1083 bcast,
1084 optin,
1085 optout,
1086 noloop,
1087 target_nodes=target_nodes,
1088 )
1089
1090 @deprecated_function(
1091 version="7.2.0",
1092 reason="Use client-side caching feature instead.",
1093 )
1094 def client_tracking_off(
1095 self,
1096 clientid: int | None = None,
1097 prefix: Sequence[KeyT] = [],
1098 bcast: bool = False,
1099 optin: bool = False,
1100 optout: bool = False,
1101 noloop: bool = False,
1102 target_nodes: "TargetNodesT" | None = "all",
1103 ) -> ResponseT:
1104 """
1105 Disables the tracking feature of the Redis server, that is used
1106 for server assisted client side caching.
1107
1108 When clientid is provided - in target_nodes only the node that owns the
1109 connection with this id should be provided.
1110 When clientid is not provided - target_nodes can be any node.
1111
1112 For more information see https://redis.io/commands/client-tracking
1113 """
1114 return self.client_tracking(
1115 False,
1116 clientid,
1117 prefix,
1118 bcast,
1119 optin,
1120 optout,
1121 noloop,
1122 target_nodes=target_nodes,
1123 )
1124
1125 def hotkeys_start(
1126 self,
1127 metrics: List[HotkeysMetricsTypes],
1128 count: int | None = None,
1129 duration: int | None = None,
1130 sample_ratio: int | None = None,
1131 slots: List[int] | None = None,
1132 **kwargs,
1133 ) -> str | bytes:
1134 """
1135 Cluster client does not support hotkeys command. Please use the non-cluster client.
1136
1137 For more information see https://redis.io/commands/hotkeys-start
1138 """
1139 raise NotImplementedError(
1140 "HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client."
1141 )
1142
1143 def hotkeys_stop(self, **kwargs) -> str | bytes:
1144 """
1145 Cluster client does not support hotkeys command. Please use the non-cluster client.
1146
1147 For more information see https://redis.io/commands/hotkeys-stop
1148 """
1149 raise NotImplementedError(
1150 "HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client."
1151 )
1152
1153 def hotkeys_reset(self, **kwargs) -> str | bytes:
1154 """
1155 Cluster client does not support hotkeys command. Please use the non-cluster client.
1156
1157 For more information see https://redis.io/commands/hotkeys-reset
1158 """
1159 raise NotImplementedError(
1160 "HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client."
1161 )
1162
1163 def hotkeys_get(self, **kwargs) -> list[dict[str | bytes, Any]]:
1164 """
1165 Cluster client does not support hotkeys command. Please use the non-cluster client.
1166
1167 For more information see https://redis.io/commands/hotkeys-get
1168 """
1169 raise NotImplementedError(
1170 "HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client."
1171 )
1172
1173
1174class AsyncClusterManagementCommands(
1175 ClusterManagementCommands, AsyncManagementCommands
1176):
1177 """
1178 A class for Redis Cluster management commands
1179
1180 The class inherits from Redis's core ManagementCommands class and do the
1181 required adjustments to work with cluster mode
1182 """
1183
1184 async def cluster_delslots(self, *slots: EncodableT) -> List[bool]:
1185 """
1186 Set hash slots as unbound in the cluster.
1187 It determines by it self what node the slot is in and sends it there
1188
1189 Returns a list of the results for each processed slot.
1190
1191 For more information see https://redis.io/commands/cluster-delslots
1192 """
1193 return await asyncio.gather(
1194 *(
1195 asyncio.create_task(self.execute_command("CLUSTER DELSLOTS", slot))
1196 for slot in slots
1197 )
1198 )
1199
1200 @deprecated_function(
1201 version="7.2.0",
1202 reason="Use client-side caching feature instead.",
1203 )
1204 async def client_tracking_on(
1205 self,
1206 clientid: int | None = None,
1207 prefix: Sequence[KeyT] = [],
1208 bcast: bool = False,
1209 optin: bool = False,
1210 optout: bool = False,
1211 noloop: bool = False,
1212 target_nodes: "TargetNodesT" | None = "all",
1213 ) -> ResponseT:
1214 """
1215 Enables the tracking feature of the Redis server, that is used
1216 for server assisted client side caching.
1217
1218 When clientid is provided - in target_nodes only the node that owns the
1219 connection with this id should be provided.
1220 When clientid is not provided - target_nodes can be any node.
1221
1222 For more information see https://redis.io/commands/client-tracking
1223 """
1224 return await self.client_tracking(
1225 True,
1226 clientid,
1227 prefix,
1228 bcast,
1229 optin,
1230 optout,
1231 noloop,
1232 target_nodes=target_nodes,
1233 )
1234
1235 @deprecated_function(
1236 version="7.2.0",
1237 reason="Use client-side caching feature instead.",
1238 )
1239 async def client_tracking_off(
1240 self,
1241 clientid: int | None = None,
1242 prefix: Sequence[KeyT] = [],
1243 bcast: bool = False,
1244 optin: bool = False,
1245 optout: bool = False,
1246 noloop: bool = False,
1247 target_nodes: "TargetNodesT" | None = "all",
1248 ) -> ResponseT:
1249 """
1250 Disables the tracking feature of the Redis server, that is used
1251 for server assisted client side caching.
1252
1253 When clientid is provided - in target_nodes only the node that owns the
1254 connection with this id should be provided.
1255 When clientid is not provided - target_nodes can be any node.
1256
1257 For more information see https://redis.io/commands/client-tracking
1258 """
1259 return await self.client_tracking(
1260 False,
1261 clientid,
1262 prefix,
1263 bcast,
1264 optin,
1265 optout,
1266 noloop,
1267 target_nodes=target_nodes,
1268 )
1269
1270 async def hotkeys_start(
1271 self,
1272 metrics: List[HotkeysMetricsTypes],
1273 count: int | None = None,
1274 duration: int | None = None,
1275 sample_ratio: int | None = None,
1276 slots: List[int] | None = None,
1277 **kwargs,
1278 ) -> Awaitable[str | bytes]:
1279 """
1280 Cluster client does not support hotkeys command. Please use the non-cluster client.
1281
1282 For more information see https://redis.io/commands/hotkeys-start
1283 """
1284 raise NotImplementedError(
1285 "HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client."
1286 )
1287
1288 async def hotkeys_stop(self, **kwargs) -> Awaitable[str | bytes]:
1289 """
1290 Cluster client does not support hotkeys command. Please use the non-cluster client.
1291
1292 For more information see https://redis.io/commands/hotkeys-stop
1293 """
1294 raise NotImplementedError(
1295 "HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client."
1296 )
1297
1298 async def hotkeys_reset(self, **kwargs) -> Awaitable[str | bytes]:
1299 """
1300 Cluster client does not support hotkeys command. Please use the non-cluster client.
1301
1302 For more information see https://redis.io/commands/hotkeys-reset
1303 """
1304 raise NotImplementedError(
1305 "HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client."
1306 )
1307
1308 async def hotkeys_get(self, **kwargs) -> Awaitable[list[dict[str | bytes, Any]]]:
1309 """
1310 Cluster client does not support hotkeys command. Please use the non-cluster client.
1311
1312 For more information see https://redis.io/commands/hotkeys-get
1313 """
1314 raise NotImplementedError(
1315 "HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client."
1316 )
1317
1318
1319class ClusterDataAccessCommands(DataAccessCommands):
1320 """
1321 A class for Redis Cluster Data Access Commands
1322
1323 The class inherits from Redis's core DataAccessCommand class and do the
1324 required adjustments to work with cluster mode
1325 """
1326
1327 @overload
1328 def stralgo(
1329 self: SyncClientProtocol,
1330 algo: Literal["LCS"],
1331 value1: KeyT,
1332 value2: KeyT,
1333 specific_argument: Literal["strings"] | Literal["keys"] = "strings",
1334 len: bool = False,
1335 idx: bool = False,
1336 minmatchlen: int | None = None,
1337 withmatchlen: bool = False,
1338 **kwargs,
1339 ) -> StralgoResponse: ...
1340
1341 @overload
1342 def stralgo(
1343 self: AsyncClientProtocol,
1344 algo: Literal["LCS"],
1345 value1: KeyT,
1346 value2: KeyT,
1347 specific_argument: Literal["strings"] | Literal["keys"] = "strings",
1348 len: bool = False,
1349 idx: bool = False,
1350 minmatchlen: int | None = None,
1351 withmatchlen: bool = False,
1352 **kwargs,
1353 ) -> Awaitable[StralgoResponse]: ...
1354
1355 def stralgo(
1356 self,
1357 algo: Literal["LCS"],
1358 value1: KeyT,
1359 value2: KeyT,
1360 specific_argument: Literal["strings"] | Literal["keys"] = "strings",
1361 len: bool = False,
1362 idx: bool = False,
1363 minmatchlen: int | None = None,
1364 withmatchlen: bool = False,
1365 **kwargs,
1366 ) -> StralgoResponse | Awaitable[StralgoResponse]:
1367 """
1368 Implements complex algorithms that operate on strings.
1369 Right now the only algorithm implemented is the LCS algorithm
1370 (longest common substring). However new algorithms could be
1371 implemented in the future.
1372
1373 ``algo`` Right now must be LCS
1374 ``value1`` and ``value2`` Can be two strings or two keys
1375 ``specific_argument`` Specifying if the arguments to the algorithm
1376 will be keys or strings. strings is the default.
1377 ``len`` Returns just the len of the match.
1378 ``idx`` Returns the match positions in each string.
1379 ``minmatchlen`` Restrict the list of matches to the ones of a given
1380 minimal length. Can be provided only when ``idx`` set to True.
1381 ``withmatchlen`` Returns the matches with the len of the match.
1382 Can be provided only when ``idx`` set to True.
1383
1384 For more information see https://redis.io/commands/stralgo
1385 """
1386 target_nodes = kwargs.pop("target_nodes", None)
1387 if specific_argument == "strings" and target_nodes is None:
1388 target_nodes = "default-node"
1389 kwargs.update({"target_nodes": target_nodes})
1390 return super().stralgo(
1391 algo,
1392 value1,
1393 value2,
1394 specific_argument,
1395 len,
1396 idx,
1397 minmatchlen,
1398 withmatchlen,
1399 **kwargs,
1400 )
1401
1402 def scan_iter(
1403 self,
1404 match: PatternT | None = None,
1405 count: int | None = None,
1406 _type: str | None = None,
1407 **kwargs,
1408 ) -> Iterator:
1409 # Do the first query with cursor=0 for all nodes
1410 cursors, data = self.scan(match=match, count=count, _type=_type, **kwargs)
1411 yield from data
1412
1413 cursors = {name: cursor for name, cursor in cursors.items() if cursor != 0}
1414 if cursors:
1415 # Get nodes by name
1416 nodes = {name: self.get_node(node_name=name) for name in cursors.keys()}
1417
1418 # Iterate over each node till its cursor is 0
1419 kwargs.pop("target_nodes", None)
1420 while cursors:
1421 for name, cursor in cursors.items():
1422 cur, data = self.scan(
1423 cursor=cursor,
1424 match=match,
1425 count=count,
1426 _type=_type,
1427 target_nodes=nodes[name],
1428 **kwargs,
1429 )
1430 yield from data
1431 cursors[name] = cur[name]
1432
1433 cursors = {
1434 name: cursor for name, cursor in cursors.items() if cursor != 0
1435 }
1436
1437
1438class AsyncClusterDataAccessCommands(
1439 ClusterDataAccessCommands, AsyncDataAccessCommands
1440):
1441 """
1442 A class for Redis Cluster Data Access Commands
1443
1444 The class inherits from Redis's core DataAccessCommand class and do the
1445 required adjustments to work with cluster mode
1446 """
1447
1448 async def scan_iter(
1449 self,
1450 match: PatternT | None = None,
1451 count: int | None = None,
1452 _type: str | None = None,
1453 **kwargs,
1454 ) -> AsyncIterator:
1455 # Do the first query with cursor=0 for all nodes
1456 cursors, data = await self.scan(match=match, count=count, _type=_type, **kwargs)
1457 for value in data:
1458 yield value
1459
1460 cursors = {name: cursor for name, cursor in cursors.items() if cursor != 0}
1461 if cursors:
1462 # Get nodes by name
1463 nodes = {name: self.get_node(node_name=name) for name in cursors.keys()}
1464
1465 # Iterate over each node till its cursor is 0
1466 kwargs.pop("target_nodes", None)
1467 while cursors:
1468 for name, cursor in cursors.items():
1469 cur, data = await self.scan(
1470 cursor=cursor,
1471 match=match,
1472 count=count,
1473 _type=_type,
1474 target_nodes=nodes[name],
1475 **kwargs,
1476 )
1477 for value in data:
1478 yield value
1479 cursors[name] = cur[name]
1480
1481 cursors = {
1482 name: cursor for name, cursor in cursors.items() if cursor != 0
1483 }
1484
1485
1486class RedisClusterCommands(
1487 ClusterMultiKeyCommands,
1488 ClusterManagementCommands,
1489 ACLCommands,
1490 PubSubCommands,
1491 ClusterDataAccessCommands,
1492 ScriptCommands,
1493 FunctionCommands,
1494 ModuleCommands,
1495 RedisModuleCommands,
1496):
1497 """
1498 A class for all Redis Cluster commands
1499
1500 For key-based commands, the target node(s) will be internally determined
1501 by the keys' hash slot.
1502 Non-key-based commands can be executed with the 'target_nodes' argument to
1503 target specific nodes. By default, if target_nodes is not specified, the
1504 command will be executed on the default cluster node.
1505
1506 :param :target_nodes: type can be one of the followings:
1507 - nodes flag: ALL_NODES, PRIMARIES, REPLICAS, RANDOM
1508 - 'ClusterNode'
1509 - 'list(ClusterNodes)'
1510 - 'dict(any:clusterNodes)'
1511
1512 for example:
1513 r.cluster_info(target_nodes=RedisCluster.ALL_NODES)
1514 """
1515
1516
1517class AsyncRedisClusterCommands(
1518 AsyncClusterMultiKeyCommands,
1519 AsyncClusterManagementCommands,
1520 AsyncACLCommands,
1521 PubSubCommands,
1522 AsyncClusterDataAccessCommands,
1523 AsyncScriptCommands,
1524 AsyncFunctionCommands,
1525 AsyncModuleCommands,
1526 AsyncRedisModuleCommands,
1527):
1528 """
1529 A class for all Redis Cluster commands
1530
1531 For key-based commands, the target node(s) will be internally determined
1532 by the keys' hash slot.
1533 Non-key-based commands can be executed with the 'target_nodes' argument to
1534 target specific nodes. By default, if target_nodes is not specified, the
1535 command will be executed on the default cluster node.
1536
1537 :param :target_nodes: type can be one of the followings:
1538 - nodes flag: ALL_NODES, PRIMARIES, REPLICAS, RANDOM
1539 - 'ClusterNode'
1540 - 'list(ClusterNodes)'
1541 - 'dict(any:clusterNodes)'
1542
1543 for example:
1544 r.cluster_info(target_nodes=RedisCluster.ALL_NODES)
1545 """