Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/redis/commands/cluster.py: 61%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

327 statements  

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 """