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