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