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

122 "SINTER", 

123 "SINTERCARD", 

124 "SISMEMBER", 

125 "SMISMEMBER", 

126 "SMEMBERS", 

127 "SRANDMEMBER", 

128 "SUNION", 

129 "SUNIONCARD", 

130 "SSCAN", 

131 # Sorted Set Operations 

132 "ZCARD", 

133 "ZCOUNT", 

134 "ZDIFF", 

135 "ZINTER", 

136 "ZINTERCARD", 

137 "ZLEXCOUNT", 

138 "ZMSCORE", 

139 "ZRANDMEMBER", 

140 "ZRANGE", 

141 "ZRANGEBYLEX", 

142 "ZRANGEBYSCORE", 

143 "ZRANK", 

144 "ZREVRANGE", 

145 "ZREVRANGEBYLEX", 

146 "ZREVRANGEBYSCORE", 

147 "ZREVRANK", 

148 "ZSCAN", 

149 "ZSCORE", 

150 "ZUNION", 

151 # Stream Operations 

152 "XLEN", 

153 "XPENDING", 

154 "XRANGE", 

155 "XREAD", 

156 "XREVRANGE", 

157 # JSON Module 

158 "JSON.ARRINDEX", 

159 "JSON.ARRLEN", 

160 "JSON.GET", 

161 "JSON.MGET", 

162 "JSON.OBJKEYS", 

163 "JSON.OBJLEN", 

164 "JSON.RESP", 

165 "JSON.STRLEN", 

166 "JSON.TYPE", 

167 # RediSearch Module 

168 "FT.EXPLAIN", 

169 "FT.INFO", 

170 "FT.PROFILE", 

171 "FT.SEARCH", 

172 ] 

173) 

174 

175 

176class ClusterMultiKeyCommands(ClusterCommandsProtocol): 

177 """ 

178 A class containing commands that handle more than one key 

179 """ 

180 

181 def _partition_keys_by_slot(self, keys: Iterable[KeyT]) -> Dict[int, List[KeyT]]: 

182 """Split keys into a dictionary that maps a slot to a list of keys.""" 

183 

184 slots_to_keys = {} 

185 for key in keys: 

186 slot = key_slot(self.encoder.encode(key)) 

187 slots_to_keys.setdefault(slot, []).append(key) 

188 

189 return slots_to_keys 

190 

191 def _partition_pairs_by_slot( 

192 self, mapping: Mapping[AnyKeyT, EncodableT] 

193 ) -> Dict[int, List[EncodableT]]: 

194 """Split pairs into a dictionary that maps a slot to a list of pairs.""" 

195 

196 slots_to_pairs = {} 

197 for pair in mapping.items(): 

198 slot = key_slot(self.encoder.encode(pair[0])) 

199 slots_to_pairs.setdefault(slot, []).extend(pair) 

200 

201 return slots_to_pairs 

202 

203 def _execute_pipeline_by_slot( 

204 self, command: str, slots_to_args: Mapping[int, Iterable[EncodableT]] 

205 ) -> List[Any]: 

206 read_from_replicas = self.read_from_replicas and command in READ_COMMANDS 

207 pipe = self.pipeline() 

208 [ 

209 pipe.execute_command( 

210 command, 

211 *slot_args, 

212 target_nodes=[ 

213 self.nodes_manager.get_node_from_slot(slot, read_from_replicas) 

214 ], 

215 ) 

216 for slot, slot_args in slots_to_args.items() 

217 ] 

218 return pipe.execute() 

219 

220 def _reorder_keys_by_command( 

221 self, 

222 keys: Iterable[KeyT], 

223 slots_to_args: Mapping[int, Iterable[EncodableT]], 

224 responses: Iterable[Any], 

225 ) -> List[Any]: 

226 results = { 

227 k: v 

228 for slot_values, response in zip(slots_to_args.values(), responses) 

229 for k, v in zip(slot_values, response) 

230 } 

231 return [results[key] for key in keys] 

232 

233 def mget_nonatomic(self, keys: KeysT, *args: KeyT) -> List[Any | None]: 

234 """ 

235 Splits the keys into different slots and then calls MGET 

236 for the keys of every slot. This operation will not be atomic 

237 if keys belong to more than one slot. 

238 

239 Returns a list of values ordered identically to ``keys`` 

240 

241 For more information see https://redis.io/commands/mget 

242 """ 

243 

244 # Concatenate all keys into a list 

245 keys = list_or_args(keys, args) 

246 

247 # Split keys into slots 

248 slots_to_keys = self._partition_keys_by_slot(keys) 

249 

250 # Execute commands using a pipeline 

251 res = self._execute_pipeline_by_slot("MGET", slots_to_keys) 

252 

253 # Reorder keys in the order the user provided & return 

254 return self._reorder_keys_by_command(keys, slots_to_keys, res) 

255 

256 def mset_nonatomic(self, mapping: Mapping[AnyKeyT, EncodableT]) -> List[bool]: 

257 """ 

258 Sets key/values based on a mapping. Mapping is a dictionary of 

259 key/value pairs. Both keys and values should be strings or types that 

260 can be cast to a string via str(). 

261 

262 Splits the keys into different slots and then calls MSET 

263 for the keys of every slot. This operation will not be atomic 

264 if keys belong to more than one slot. 

265 

266 For more information see https://redis.io/commands/mset 

267 """ 

268 

269 # Partition the keys by slot 

270 slots_to_pairs = self._partition_pairs_by_slot(mapping) 

271 

272 # Execute commands using a pipeline & return list of replies 

273 return self._execute_pipeline_by_slot("MSET", slots_to_pairs) 

274 

275 def _split_command_across_slots(self, command: str, *keys: KeyT) -> int: 

276 """ 

277 Runs the given command once for the keys 

278 of each slot. Returns the sum of the return values. 

279 """ 

280 

281 # Partition the keys by slot 

282 slots_to_keys = self._partition_keys_by_slot(keys) 

283 

284 # Sum up the reply from each command 

285 return sum(self._execute_pipeline_by_slot(command, slots_to_keys)) 

286 

287 @overload 

288 def exists(self: SyncClientProtocol, *keys: KeyT) -> int: ... 

289 

290 @overload 

291 def exists(self: AsyncClientProtocol, *keys: KeyT) -> Awaitable[int]: ... 

292 

293 def exists(self, *keys: KeyT) -> int | Awaitable[int]: 

294 """ 

295 Returns the number of ``names`` that exist in the 

296 whole cluster. The keys are first split up into slots 

297 and then an EXISTS command is sent for every slot 

298 

299 For more information see https://redis.io/commands/exists 

300 """ 

301 return self._split_command_across_slots("EXISTS", *keys) 

302 

303 @overload 

304 def delete(self: SyncClientProtocol, *keys: KeyT) -> int: ... 

305 

306 @overload 

307 def delete(self: AsyncClientProtocol, *keys: KeyT) -> Awaitable[int]: ... 

308 

309 def delete(self, *keys: KeyT) -> int | Awaitable[int]: 

310 """ 

311 Deletes the given keys in the cluster. 

312 The keys are first split up into slots 

313 and then an DEL command is sent for every slot 

314 

315 Non-existent keys are ignored. 

316 Returns the number of keys that were deleted. 

317 

318 For more information see https://redis.io/commands/del 

319 """ 

320 return self._split_command_across_slots("DEL", *keys) 

321 

322 @overload 

323 def touch(self: SyncClientProtocol, *keys: KeyT) -> int: ... 

324 

325 @overload 

326 def touch(self: AsyncClientProtocol, *keys: KeyT) -> Awaitable[int]: ... 

327 

328 def touch(self, *keys: KeyT) -> int | Awaitable[int]: 

329 """ 

330 Updates the last access time of given keys across the 

331 cluster. 

332 

333 The keys are first split up into slots 

334 and then an TOUCH command is sent for every slot 

335 

336 Non-existent keys are ignored. 

337 Returns the number of keys that were touched. 

338 

339 For more information see https://redis.io/commands/touch 

340 """ 

341 return self._split_command_across_slots("TOUCH", *keys) 

342 

343 @overload 

344 def unlink(self: SyncClientProtocol, *keys: KeyT) -> int: ... 

345 

346 @overload 

347 def unlink(self: AsyncClientProtocol, *keys: KeyT) -> Awaitable[int]: ... 

348 

349 def unlink(self, *keys: KeyT) -> int | Awaitable[int]: 

350 """ 

351 Remove the specified keys in a different thread. 

352 

353 The keys are first split up into slots 

354 and then an TOUCH command is sent for every slot 

355 

356 Non-existent keys are ignored. 

357 Returns the number of keys that were unlinked. 

358 

359 For more information see https://redis.io/commands/unlink 

360 """ 

361 return self._split_command_across_slots("UNLINK", *keys) 

362 

363 

364class AsyncClusterMultiKeyCommands(ClusterMultiKeyCommands): 

365 """ 

366 A class containing commands that handle more than one key 

367 """ 

368 

369 async def mget_nonatomic(self, keys: KeysT, *args: KeyT) -> List[Any | None]: 

370 """ 

371 Splits the keys into different slots and then calls MGET 

372 for the keys of every slot. This operation will not be atomic 

373 if keys belong to more than one slot. 

374 

375 Returns a list of values ordered identically to ``keys`` 

376 

377 For more information see https://redis.io/commands/mget 

378 """ 

379 

380 # Concatenate all keys into a list 

381 keys = list_or_args(keys, args) 

382 

383 # Split keys into slots 

384 slots_to_keys = self._partition_keys_by_slot(keys) 

385 

386 # Execute commands using a pipeline 

387 res = await self._execute_pipeline_by_slot("MGET", slots_to_keys) 

388 

389 # Reorder keys in the order the user provided & return 

390 return self._reorder_keys_by_command(keys, slots_to_keys, res) 

391 

392 async def mset_nonatomic(self, mapping: Mapping[AnyKeyT, EncodableT]) -> List[bool]: 

393 """ 

394 Sets key/values based on a mapping. Mapping is a dictionary of 

395 key/value pairs. Both keys and values should be strings or types that 

396 can be cast to a string via str(). 

397 

398 Splits the keys into different slots and then calls MSET 

399 for the keys of every slot. This operation will not be atomic 

400 if keys belong to more than one slot. 

401 

402 For more information see https://redis.io/commands/mset 

403 """ 

404 

405 # Partition the keys by slot 

406 slots_to_pairs = self._partition_pairs_by_slot(mapping) 

407 

408 # Execute commands using a pipeline & return list of replies 

409 return await self._execute_pipeline_by_slot("MSET", slots_to_pairs) 

410 

411 async def _split_command_across_slots(self, command: str, *keys: KeyT) -> int: 

412 """ 

413 Runs the given command once for the keys 

414 of each slot. Returns the sum of the return values. 

415 """ 

416 

417 # Partition the keys by slot 

418 slots_to_keys = self._partition_keys_by_slot(keys) 

419 

420 # Sum up the reply from each command 

421 return sum(await self._execute_pipeline_by_slot(command, slots_to_keys)) 

422 

423 async def _execute_pipeline_by_slot( 

424 self, command: str, slots_to_args: Mapping[int, Iterable[EncodableT]] 

425 ) -> List[Any]: 

426 if self._initialize: 

427 await self.initialize() 

428 read_from_replicas = self.read_from_replicas and command in READ_COMMANDS 

429 pipe = self.pipeline() 

430 [ 

431 pipe.execute_command( 

432 command, 

433 *slot_args, 

434 target_nodes=[ 

435 self.nodes_manager.get_node_from_slot(slot, read_from_replicas) 

436 ], 

437 ) 

438 for slot, slot_args in slots_to_args.items() 

439 ] 

440 return await pipe.execute() 

441 

442 

443class ClusterManagementCommands(ManagementCommands): 

444 """ 

445 A class for Redis Cluster management commands 

446 

447 The class inherits from Redis's core ManagementCommands class and do the 

448 required adjustments to work with cluster mode 

449 """ 

450 

451 def slaveof(self, *args, **kwargs) -> NoReturn: 

452 """ 

453 Make the server a replica of another instance, or promote it as master. 

454 

455 For more information see https://redis.io/commands/slaveof 

456 """ 

457 raise RedisClusterException("SLAVEOF is not supported in cluster mode") 

458 

459 def replicaof(self, *args, **kwargs) -> NoReturn: 

460 """ 

461 Make the server a replica of another instance, or promote it as master. 

462 

463 For more information see https://redis.io/commands/replicaof 

464 """ 

465 raise RedisClusterException("REPLICAOF is not supported in cluster mode") 

466 

467 def swapdb(self, *args, **kwargs) -> NoReturn: 

468 """ 

469 Swaps two Redis databases. 

470 

471 For more information see https://redis.io/commands/swapdb 

472 """ 

473 raise RedisClusterException("SWAPDB is not supported in cluster mode") 

474 

475 @overload 

476 def cluster_myid( 

477 self: SyncClientProtocol, target_node: "TargetNodesT" 

478 ) -> bytes | str: ... 

479 

480 @overload 

481 def cluster_myid( 

482 self: AsyncClientProtocol, target_node: "TargetNodesT" 

483 ) -> Awaitable[bytes | str]: ... 

484 

485 def cluster_myid(self, target_node: "TargetNodesT") -> (bytes | str) | Awaitable[ 

486 bytes | str 

487 ]: 

488 """ 

489 Returns the node's id. 

490 

491 :target_node: 'ClusterNode' 

492 The node to execute the command on 

493 

494 For more information check https://redis.io/commands/cluster-myid/ 

495 """ 

496 return self.execute_command("CLUSTER MYID", target_nodes=target_node) 

497 

498 @overload 

499 def cluster_addslots( 

500 self: SyncClientProtocol, target_node: "TargetNodesT", *slots: EncodableT 

501 ) -> bool: ... 

502 

503 @overload 

504 def cluster_addslots( 

505 self: AsyncClientProtocol, target_node: "TargetNodesT", *slots: EncodableT 

506 ) -> Awaitable[bool]: ... 

507 

508 def cluster_addslots( 

509 self, target_node: "TargetNodesT", *slots: EncodableT 

510 ) -> bool | Awaitable[bool]: 

511 """ 

512 Assign new hash slots to receiving node. Sends to specified node. 

513 

514 :target_node: 'ClusterNode' 

515 The node to execute the command on 

516 

517 For more information see https://redis.io/commands/cluster-addslots 

518 """ 

519 return self.execute_command( 

520 "CLUSTER ADDSLOTS", *slots, target_nodes=target_node 

521 ) 

522 

523 @overload 

524 def cluster_addslotsrange( 

525 self: SyncClientProtocol, target_node: "TargetNodesT", *slots: EncodableT 

526 ) -> bool: ... 

527 

528 @overload 

529 def cluster_addslotsrange( 

530 self: AsyncClientProtocol, target_node: "TargetNodesT", *slots: EncodableT 

531 ) -> Awaitable[bool]: ... 

532 

533 def cluster_addslotsrange( 

534 self, target_node: "TargetNodesT", *slots: EncodableT 

535 ) -> bool | Awaitable[bool]: 

536 """ 

537 Similar to the CLUSTER ADDSLOTS command. 

538 The difference between the two commands is that ADDSLOTS takes a list of slots 

539 to assign to the node, while ADDSLOTSRANGE takes a list of slot ranges 

540 (specified by start and end slots) to assign to the node. 

541 

542 :target_node: 'ClusterNode' 

543 The node to execute the command on 

544 

545 For more information see https://redis.io/commands/cluster-addslotsrange 

546 """ 

547 return self.execute_command( 

548 "CLUSTER ADDSLOTSRANGE", *slots, target_nodes=target_node 

549 ) 

550 

551 @overload 

552 def cluster_countkeysinslot(self: SyncClientProtocol, slot_id: int) -> int: ... 

553 

554 @overload 

555 def cluster_countkeysinslot( 

556 self: AsyncClientProtocol, slot_id: int 

557 ) -> Awaitable[int]: ... 

558 

559 def cluster_countkeysinslot(self, slot_id: int) -> int | Awaitable[int]: 

560 """ 

561 Return the number of local keys in the specified hash slot 

562 Send to node based on specified slot_id 

563 

564 For more information see https://redis.io/commands/cluster-countkeysinslot 

565 """ 

566 return self.execute_command("CLUSTER COUNTKEYSINSLOT", slot_id) 

567 

568 @overload 

569 def cluster_count_failure_report(self: SyncClientProtocol, node_id: str) -> int: ... 

570 

571 @overload 

572 def cluster_count_failure_report( 

573 self: AsyncClientProtocol, node_id: str 

574 ) -> Awaitable[int]: ... 

575 

576 def cluster_count_failure_report(self, node_id: str) -> int | Awaitable[int]: 

577 """ 

578 Return the number of failure reports active for a given node 

579 Sends to a random node 

580 

581 For more information see https://redis.io/commands/cluster-count-failure-reports 

582 """ 

583 return self.execute_command("CLUSTER COUNT-FAILURE-REPORTS", node_id) 

584 

585 def cluster_delslots(self, *slots: EncodableT) -> List[bool]: 

586 """ 

587 Set hash slots as unbound in the cluster. 

588 It determines by it self what node the slot is in and sends it there 

589 

590 Returns a list of the results for each processed slot. 

591 

592 For more information see https://redis.io/commands/cluster-delslots 

593 """ 

594 return [self.execute_command("CLUSTER DELSLOTS", slot) for slot in slots] 

595 

596 @overload 

597 def cluster_delslotsrange(self: SyncClientProtocol, *slots: EncodableT) -> bool: ... 

598 

599 @overload 

600 def cluster_delslotsrange( 

601 self: AsyncClientProtocol, *slots: EncodableT 

602 ) -> Awaitable[bool]: ... 

603 

604 def cluster_delslotsrange(self, *slots: EncodableT) -> bool | Awaitable[bool]: 

605 """ 

606 Similar to the CLUSTER DELSLOTS command. 

607 The difference is that CLUSTER DELSLOTS takes a list of hash slots to remove 

608 from the node, while CLUSTER DELSLOTSRANGE takes a list of slot ranges to remove 

609 from the node. 

610 

611 For more information see https://redis.io/commands/cluster-delslotsrange 

612 """ 

613 return self.execute_command("CLUSTER DELSLOTSRANGE", *slots) 

614 

615 @overload 

616 def cluster_failover( 

617 self: SyncClientProtocol, 

618 target_node: "TargetNodesT", 

619 option: str | None = None, 

620 ) -> bool: ... 

621 

622 @overload 

623 def cluster_failover( 

624 self: AsyncClientProtocol, 

625 target_node: "TargetNodesT", 

626 option: str | None = None, 

627 ) -> Awaitable[bool]: ... 

628 

629 def cluster_failover( 

630 self, target_node: "TargetNodesT", option: str | None = None 

631 ) -> bool | Awaitable[bool]: 

632 """ 

633 Forces a slave to perform a manual failover of its master 

634 Sends to specified node 

635 

636 :target_node: 'ClusterNode' 

637 The node to execute the command on 

638 

639 For more information see https://redis.io/commands/cluster-failover 

640 """ 

641 if option: 

642 if option.upper() not in ["FORCE", "TAKEOVER"]: 

643 raise RedisError( 

644 f"Invalid option for CLUSTER FAILOVER command: {option}" 

645 ) 

646 else: 

647 return self.execute_command( 

648 "CLUSTER FAILOVER", option, target_nodes=target_node 

649 ) 

650 else: 

651 return self.execute_command("CLUSTER FAILOVER", target_nodes=target_node) 

652 

653 @overload 

654 def cluster_info( 

655 self: SyncClientProtocol, target_nodes: "TargetNodesT" | None = None 

656 ) -> dict[str, str]: ... 

657 

658 @overload 

659 def cluster_info( 

660 self: AsyncClientProtocol, target_nodes: "TargetNodesT" | None = None 

661 ) -> Awaitable[dict[str, str]]: ... 

662 

663 def cluster_info( 

664 self, target_nodes: "TargetNodesT" | None = None 

665 ) -> dict[str, str] | Awaitable[dict[str, str]]: 

666 """ 

667 Provides info about Redis Cluster node state. 

668 The command will be sent to a random node in the cluster if no target 

669 node is specified. 

670 

671 For more information see https://redis.io/commands/cluster-info 

672 """ 

673 return self.execute_command("CLUSTER INFO", target_nodes=target_nodes) 

674 

675 @overload 

676 def cluster_keyslot(self: SyncClientProtocol, key: str) -> int: ... 

677 

678 @overload 

679 def cluster_keyslot(self: AsyncClientProtocol, key: str) -> Awaitable[int]: ... 

680 

681 def cluster_keyslot(self, key: str) -> int | Awaitable[int]: 

682 """ 

683 Returns the hash slot of the specified key 

684 Sends to random node in the cluster 

685 

686 For more information see https://redis.io/commands/cluster-keyslot 

687 """ 

688 return self.execute_command("CLUSTER KEYSLOT", key) 

689 

690 @overload 

691 def cluster_meet( 

692 self: SyncClientProtocol, 

693 host: str, 

694 port: int, 

695 target_nodes: "TargetNodesT" | None = None, 

696 ) -> bool: ... 

697 

698 @overload 

699 def cluster_meet( 

700 self: AsyncClientProtocol, 

701 host: str, 

702 port: int, 

703 target_nodes: "TargetNodesT" | None = None, 

704 ) -> Awaitable[bool]: ... 

705 

706 def cluster_meet( 

707 self, host: str, port: int, target_nodes: "TargetNodesT" | None = None 

708 ) -> bool | Awaitable[bool]: 

709 """ 

710 Force a node cluster to handshake with another node. 

711 Sends to specified node. 

712 

713 For more information see https://redis.io/commands/cluster-meet 

714 """ 

715 return self.execute_command( 

716 "CLUSTER MEET", host, port, target_nodes=target_nodes 

717 ) 

718 

719 @overload 

720 def cluster_nodes(self: SyncClientProtocol) -> dict[str, ClusterNodeDetail]: ... 

721 

722 @overload 

723 def cluster_nodes( 

724 self: AsyncClientProtocol, 

725 ) -> Awaitable[dict[str, ClusterNodeDetail]]: ... 

726 

727 def cluster_nodes( 

728 self, 

729 ) -> dict[str, ClusterNodeDetail] | Awaitable[dict[str, ClusterNodeDetail]]: 

730 """ 

731 Get Cluster config for the node. 

732 Sends to random node in the cluster 

733 

734 For more information see https://redis.io/commands/cluster-nodes 

735 """ 

736 return self.execute_command("CLUSTER NODES") 

737 

738 @overload 

739 def cluster_replicate( 

740 self: SyncClientProtocol, target_nodes: "TargetNodesT", node_id: str 

741 ) -> bool: ... 

742 

743 @overload 

744 def cluster_replicate( 

745 self: AsyncClientProtocol, target_nodes: "TargetNodesT", node_id: str 

746 ) -> Awaitable[bool]: ... 

747 

748 def cluster_replicate( 

749 self, target_nodes: "TargetNodesT", node_id: str 

750 ) -> bool | Awaitable[bool]: 

751 """ 

752 Reconfigure a node as a slave of the specified master node 

753 

754 For more information see https://redis.io/commands/cluster-replicate 

755 """ 

756 return self.execute_command( 

757 "CLUSTER REPLICATE", node_id, target_nodes=target_nodes 

758 ) 

759 

760 @overload 

761 def cluster_reset( 

762 self: SyncClientProtocol, 

763 soft: bool = True, 

764 target_nodes: "TargetNodesT" | None = None, 

765 ) -> bool: ... 

766 

767 @overload 

768 def cluster_reset( 

769 self: AsyncClientProtocol, 

770 soft: bool = True, 

771 target_nodes: "TargetNodesT" | None = None, 

772 ) -> Awaitable[bool]: ... 

773 

774 def cluster_reset( 

775 self, soft: bool = True, target_nodes: "TargetNodesT" | None = None 

776 ) -> bool | Awaitable[bool]: 

777 """ 

778 Reset a Redis Cluster node 

779 

780 If 'soft' is True then it will send 'SOFT' argument 

781 If 'soft' is False then it will send 'HARD' argument 

782 

783 For more information see https://redis.io/commands/cluster-reset 

784 """ 

785 return self.execute_command( 

786 "CLUSTER RESET", b"SOFT" if soft else b"HARD", target_nodes=target_nodes 

787 ) 

788 

789 @overload 

790 def cluster_save_config( 

791 self: SyncClientProtocol, target_nodes: "TargetNodesT" | None = None 

792 ) -> bool: ... 

793 

794 @overload 

795 def cluster_save_config( 

796 self: AsyncClientProtocol, target_nodes: "TargetNodesT" | None = None 

797 ) -> Awaitable[bool]: ... 

798 

799 def cluster_save_config( 

800 self, target_nodes: "TargetNodesT" | None = None 

801 ) -> bool | Awaitable[bool]: 

802 """ 

803 Forces the node to save cluster state on disk 

804 

805 For more information see https://redis.io/commands/cluster-saveconfig 

806 """ 

807 return self.execute_command("CLUSTER SAVECONFIG", target_nodes=target_nodes) 

808 

809 @overload 

810 def cluster_get_keys_in_slot( 

811 self: SyncClientProtocol, slot: int, num_keys: int 

812 ) -> list[bytes | str]: ... 

813 

814 @overload 

815 def cluster_get_keys_in_slot( 

816 self: AsyncClientProtocol, slot: int, num_keys: int 

817 ) -> Awaitable[list[bytes | str]]: ... 

818 

819 def cluster_get_keys_in_slot( 

820 self, slot: int, num_keys: int 

821 ) -> list[bytes | str] | Awaitable[list[bytes | str]]: 

822 """ 

823 Returns the number of keys in the specified cluster slot 

824 

825 For more information see https://redis.io/commands/cluster-getkeysinslot 

826 """ 

827 return self.execute_command("CLUSTER GETKEYSINSLOT", slot, num_keys) 

828 

829 @overload 

830 def cluster_set_config_epoch( 

831 self: SyncClientProtocol, epoch: int, target_nodes: "TargetNodesT" | None = None 

832 ) -> bool: ... 

833 

834 @overload 

835 def cluster_set_config_epoch( 

836 self: AsyncClientProtocol, 

837 epoch: int, 

838 target_nodes: "TargetNodesT" | None = None, 

839 ) -> Awaitable[bool]: ... 

840 

841 def cluster_set_config_epoch( 

842 self, epoch: int, target_nodes: "TargetNodesT" | None = None 

843 ) -> bool | Awaitable[bool]: 

844 """ 

845 Set the configuration epoch in a new node 

846 

847 For more information see https://redis.io/commands/cluster-set-config-epoch 

848 """ 

849 return self.execute_command( 

850 "CLUSTER SET-CONFIG-EPOCH", epoch, target_nodes=target_nodes 

851 ) 

852 

853 @overload 

854 def cluster_setslot( 

855 self: SyncClientProtocol, 

856 target_node: "TargetNodesT", 

857 node_id: str, 

858 slot_id: int, 

859 state: str, 

860 ) -> bool: ... 

861 

862 @overload 

863 def cluster_setslot( 

864 self: AsyncClientProtocol, 

865 target_node: "TargetNodesT", 

866 node_id: str, 

867 slot_id: int, 

868 state: str, 

869 ) -> Awaitable[bool]: ... 

870 

871 def cluster_setslot( 

872 self, target_node: "TargetNodesT", node_id: str, slot_id: int, state: str 

873 ) -> bool | Awaitable[bool]: 

874 """ 

875 Bind an hash slot to a specific node 

876 

877 :target_node: 'ClusterNode' 

878 The node to execute the command on 

879 

880 For more information see https://redis.io/commands/cluster-setslot 

881 """ 

882 if state.upper() in ("IMPORTING", "NODE", "MIGRATING"): 

883 return self.execute_command( 

884 "CLUSTER SETSLOT", slot_id, state, node_id, target_nodes=target_node 

885 ) 

886 elif state.upper() == "STABLE": 

887 raise RedisError('For "stable" state please use cluster_setslot_stable') 

888 else: 

889 raise RedisError(f"Invalid slot state: {state}") 

890 

891 @overload 

892 def cluster_setslot_stable(self: SyncClientProtocol, slot_id: int) -> bool: ... 

893 

894 @overload 

895 def cluster_setslot_stable( 

896 self: AsyncClientProtocol, slot_id: int 

897 ) -> Awaitable[bool]: ... 

898 

899 def cluster_setslot_stable(self, slot_id: int) -> bool | Awaitable[bool]: 

900 """ 

901 Clears migrating / importing state from the slot. 

902 It determines by it self what node the slot is in and sends it there. 

903 

904 For more information see https://redis.io/commands/cluster-setslot 

905 """ 

906 return self.execute_command("CLUSTER SETSLOT", slot_id, "STABLE") 

907 

908 @overload 

909 def cluster_replicas( 

910 self: SyncClientProtocol, 

911 node_id: str, 

912 target_nodes: "TargetNodesT" | None = None, 

913 ) -> dict[str, ClusterNodeDetail]: ... 

914 

915 @overload 

916 def cluster_replicas( 

917 self: AsyncClientProtocol, 

918 node_id: str, 

919 target_nodes: "TargetNodesT" | None = None, 

920 ) -> Awaitable[dict[str, ClusterNodeDetail]]: ... 

921 

922 def cluster_replicas( 

923 self, node_id: str, target_nodes: "TargetNodesT" | None = None 

924 ) -> dict[str, ClusterNodeDetail] | Awaitable[dict[str, ClusterNodeDetail]]: 

925 """ 

926 Provides a list of replica nodes replicating from the specified primary 

927 target node. 

928 

929 For more information see https://redis.io/commands/cluster-replicas 

930 """ 

931 return self.execute_command( 

932 "CLUSTER REPLICAS", node_id, target_nodes=target_nodes 

933 ) 

934 

935 @overload 

936 def cluster_slots( 

937 self: SyncClientProtocol, target_nodes: "TargetNodesT" | None = None 

938 ) -> list[Any]: ... 

939 

940 @overload 

941 def cluster_slots( 

942 self: AsyncClientProtocol, target_nodes: "TargetNodesT" | None = None 

943 ) -> Awaitable[list[Any]]: ... 

944 

945 def cluster_slots( 

946 self, target_nodes: "TargetNodesT" | None = None 

947 ) -> list[Any] | Awaitable[list[Any]]: 

948 """ 

949 Get array of Cluster slot to node mappings 

950 

951 For more information see https://redis.io/commands/cluster-slots 

952 """ 

953 return self.execute_command("CLUSTER SLOTS", target_nodes=target_nodes) 

954 

955 @overload 

956 def cluster_shards( 

957 self: SyncClientProtocol, target_nodes: "TargetNodesT" | None = None 

958 ) -> ClusterShardsResponse: ... 

959 

960 @overload 

961 def cluster_shards( 

962 self: AsyncClientProtocol, target_nodes: "TargetNodesT" | None = None 

963 ) -> Awaitable[ClusterShardsResponse]: ... 

964 

965 def cluster_shards( 

966 self, target_nodes: "TargetNodesT" | None = None 

967 ) -> ClusterShardsResponse | Awaitable[ClusterShardsResponse]: 

968 """ 

969 Returns details about the shards of the cluster. 

970 

971 For more information see https://redis.io/commands/cluster-shards 

972 """ 

973 return self.execute_command("CLUSTER SHARDS", target_nodes=target_nodes) 

974 

975 @overload 

976 def cluster_myshardid( 

977 self: SyncClientProtocol, target_nodes: "TargetNodesT" | None = None 

978 ) -> bytes | str: ... 

979 

980 @overload 

981 def cluster_myshardid( 

982 self: AsyncClientProtocol, target_nodes: "TargetNodesT" | None = None 

983 ) -> Awaitable[bytes | str]: ... 

984 

985 def cluster_myshardid(self, target_nodes: "TargetNodesT" | None = None) -> ( 

986 bytes | str 

987 ) | Awaitable[bytes | str]: 

988 """ 

989 Returns the shard ID of the node. 

990 

991 For more information see https://redis.io/commands/cluster-myshardid/ 

992 """ 

993 return self.execute_command("CLUSTER MYSHARDID", target_nodes=target_nodes) 

994 

995 @overload 

996 def cluster_links( 

997 self: SyncClientProtocol, target_node: "TargetNodesT" 

998 ) -> ClusterLinksResponse: ... 

999 

1000 @overload 

1001 def cluster_links( 

1002 self: AsyncClientProtocol, target_node: "TargetNodesT" 

1003 ) -> Awaitable[ClusterLinksResponse]: ... 

1004 

1005 def cluster_links( 

1006 self, target_node: "TargetNodesT" 

1007 ) -> ClusterLinksResponse | Awaitable[ClusterLinksResponse]: 

1008 """ 

1009 Each node in a Redis Cluster maintains a pair of long-lived TCP link with each 

1010 peer in the cluster: One for sending outbound messages towards the peer and one 

1011 for receiving inbound messages from the peer. 

1012 

1013 This command outputs information of all such peer links as an array. 

1014 

1015 For more information see https://redis.io/commands/cluster-links 

1016 """ 

1017 return self.execute_command("CLUSTER LINKS", target_nodes=target_node) 

1018 

1019 def cluster_flushslots(self, target_nodes: "TargetNodesT" | None = None) -> None: 

1020 raise NotImplementedError( 

1021 "CLUSTER FLUSHSLOTS is intentionally not implemented in the client." 

1022 ) 

1023 

1024 def cluster_bumpepoch(self, target_nodes: "TargetNodesT" | None = None) -> None: 

1025 raise NotImplementedError( 

1026 "CLUSTER BUMPEPOCH is intentionally not implemented in the client." 

1027 ) 

1028 

1029 def readonly(self, target_nodes: "TargetNodesT" | None = None) -> ResponseT: 

1030 """ 

1031 Enables read queries. 

1032 The command will be sent to the default cluster node if target_nodes is 

1033 not specified. 

1034 

1035 For more information see https://redis.io/commands/readonly 

1036 """ 

1037 if target_nodes == "replicas" or target_nodes == "all": 

1038 # read_from_replicas will only be enabled if the READONLY command 

1039 # is sent to all replicas 

1040 self.read_from_replicas = True 

1041 return self.execute_command("READONLY", target_nodes=target_nodes) 

1042 

1043 def readwrite(self, target_nodes: "TargetNodesT" | None = None) -> ResponseT: 

1044 """ 

1045 Disables read queries. 

1046 The command will be sent to the default cluster node if target_nodes is 

1047 not specified. 

1048 

1049 For more information see https://redis.io/commands/readwrite 

1050 """ 

1051 # Reset read from replicas flag 

1052 self.read_from_replicas = False 

1053 return self.execute_command("READWRITE", target_nodes=target_nodes) 

1054 

1055 @deprecated_function( 

1056 version="7.2.0", 

1057 reason="Use client-side caching feature instead.", 

1058 ) 

1059 def client_tracking_on( 

1060 self, 

1061 clientid: int | None = None, 

1062 prefix: Sequence[KeyT] = [], 

1063 bcast: bool = False, 

1064 optin: bool = False, 

1065 optout: bool = False, 

1066 noloop: bool = False, 

1067 target_nodes: "TargetNodesT" | None = "all", 

1068 ) -> ResponseT: 

1069 """ 

1070 Enables the tracking feature of the Redis server, that is used 

1071 for server assisted client side caching. 

1072 

1073 When clientid is provided - in target_nodes only the node that owns the 

1074 connection with this id should be provided. 

1075 When clientid is not provided - target_nodes can be any node. 

1076 

1077 For more information see https://redis.io/commands/client-tracking 

1078 """ 

1079 return self.client_tracking( 

1080 True, 

1081 clientid, 

1082 prefix, 

1083 bcast, 

1084 optin, 

1085 optout, 

1086 noloop, 

1087 target_nodes=target_nodes, 

1088 ) 

1089 

1090 @deprecated_function( 

1091 version="7.2.0", 

1092 reason="Use client-side caching feature instead.", 

1093 ) 

1094 def client_tracking_off( 

1095 self, 

1096 clientid: int | None = None, 

1097 prefix: Sequence[KeyT] = [], 

1098 bcast: bool = False, 

1099 optin: bool = False, 

1100 optout: bool = False, 

1101 noloop: bool = False, 

1102 target_nodes: "TargetNodesT" | None = "all", 

1103 ) -> ResponseT: 

1104 """ 

1105 Disables the tracking feature of the Redis server, that is used 

1106 for server assisted client side caching. 

1107 

1108 When clientid is provided - in target_nodes only the node that owns the 

1109 connection with this id should be provided. 

1110 When clientid is not provided - target_nodes can be any node. 

1111 

1112 For more information see https://redis.io/commands/client-tracking 

1113 """ 

1114 return self.client_tracking( 

1115 False, 

1116 clientid, 

1117 prefix, 

1118 bcast, 

1119 optin, 

1120 optout, 

1121 noloop, 

1122 target_nodes=target_nodes, 

1123 ) 

1124 

1125 def hotkeys_start( 

1126 self, 

1127 metrics: List[HotkeysMetricsTypes], 

1128 count: int | None = None, 

1129 duration: int | None = None, 

1130 sample_ratio: int | None = None, 

1131 slots: List[int] | None = None, 

1132 **kwargs, 

1133 ) -> str | bytes: 

1134 """ 

1135 Cluster client does not support hotkeys command. Please use the non-cluster client. 

1136 

1137 For more information see https://redis.io/commands/hotkeys-start 

1138 """ 

1139 raise NotImplementedError( 

1140 "HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client." 

1141 ) 

1142 

1143 def hotkeys_stop(self, **kwargs) -> str | bytes: 

1144 """ 

1145 Cluster client does not support hotkeys command. Please use the non-cluster client. 

1146 

1147 For more information see https://redis.io/commands/hotkeys-stop 

1148 """ 

1149 raise NotImplementedError( 

1150 "HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client." 

1151 ) 

1152 

1153 def hotkeys_reset(self, **kwargs) -> str | bytes: 

1154 """ 

1155 Cluster client does not support hotkeys command. Please use the non-cluster client. 

1156 

1157 For more information see https://redis.io/commands/hotkeys-reset 

1158 """ 

1159 raise NotImplementedError( 

1160 "HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client." 

1161 ) 

1162 

1163 def hotkeys_get(self, **kwargs) -> list[dict[str | bytes, Any]]: 

1164 """ 

1165 Cluster client does not support hotkeys command. Please use the non-cluster client. 

1166 

1167 For more information see https://redis.io/commands/hotkeys-get 

1168 """ 

1169 raise NotImplementedError( 

1170 "HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client." 

1171 ) 

1172 

1173 

1174class AsyncClusterManagementCommands( 

1175 ClusterManagementCommands, AsyncManagementCommands 

1176): 

1177 """ 

1178 A class for Redis Cluster management commands 

1179 

1180 The class inherits from Redis's core ManagementCommands class and do the 

1181 required adjustments to work with cluster mode 

1182 """ 

1183 

1184 async def cluster_delslots(self, *slots: EncodableT) -> List[bool]: 

1185 """ 

1186 Set hash slots as unbound in the cluster. 

1187 It determines by it self what node the slot is in and sends it there 

1188 

1189 Returns a list of the results for each processed slot. 

1190 

1191 For more information see https://redis.io/commands/cluster-delslots 

1192 """ 

1193 return await asyncio.gather( 

1194 *( 

1195 asyncio.create_task(self.execute_command("CLUSTER DELSLOTS", slot)) 

1196 for slot in slots 

1197 ) 

1198 ) 

1199 

1200 @deprecated_function( 

1201 version="7.2.0", 

1202 reason="Use client-side caching feature instead.", 

1203 ) 

1204 async def client_tracking_on( 

1205 self, 

1206 clientid: int | None = None, 

1207 prefix: Sequence[KeyT] = [], 

1208 bcast: bool = False, 

1209 optin: bool = False, 

1210 optout: bool = False, 

1211 noloop: bool = False, 

1212 target_nodes: "TargetNodesT" | None = "all", 

1213 ) -> ResponseT: 

1214 """ 

1215 Enables the tracking feature of the Redis server, that is used 

1216 for server assisted client side caching. 

1217 

1218 When clientid is provided - in target_nodes only the node that owns the 

1219 connection with this id should be provided. 

1220 When clientid is not provided - target_nodes can be any node. 

1221 

1222 For more information see https://redis.io/commands/client-tracking 

1223 """ 

1224 return await self.client_tracking( 

1225 True, 

1226 clientid, 

1227 prefix, 

1228 bcast, 

1229 optin, 

1230 optout, 

1231 noloop, 

1232 target_nodes=target_nodes, 

1233 ) 

1234 

1235 @deprecated_function( 

1236 version="7.2.0", 

1237 reason="Use client-side caching feature instead.", 

1238 ) 

1239 async def client_tracking_off( 

1240 self, 

1241 clientid: int | None = None, 

1242 prefix: Sequence[KeyT] = [], 

1243 bcast: bool = False, 

1244 optin: bool = False, 

1245 optout: bool = False, 

1246 noloop: bool = False, 

1247 target_nodes: "TargetNodesT" | None = "all", 

1248 ) -> ResponseT: 

1249 """ 

1250 Disables the tracking feature of the Redis server, that is used 

1251 for server assisted client side caching. 

1252 

1253 When clientid is provided - in target_nodes only the node that owns the 

1254 connection with this id should be provided. 

1255 When clientid is not provided - target_nodes can be any node. 

1256 

1257 For more information see https://redis.io/commands/client-tracking 

1258 """ 

1259 return await self.client_tracking( 

1260 False, 

1261 clientid, 

1262 prefix, 

1263 bcast, 

1264 optin, 

1265 optout, 

1266 noloop, 

1267 target_nodes=target_nodes, 

1268 ) 

1269 

1270 async def hotkeys_start( 

1271 self, 

1272 metrics: List[HotkeysMetricsTypes], 

1273 count: int | None = None, 

1274 duration: int | None = None, 

1275 sample_ratio: int | None = None, 

1276 slots: List[int] | None = None, 

1277 **kwargs, 

1278 ) -> Awaitable[str | bytes]: 

1279 """ 

1280 Cluster client does not support hotkeys command. Please use the non-cluster client. 

1281 

1282 For more information see https://redis.io/commands/hotkeys-start 

1283 """ 

1284 raise NotImplementedError( 

1285 "HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client." 

1286 ) 

1287 

1288 async def hotkeys_stop(self, **kwargs) -> Awaitable[str | bytes]: 

1289 """ 

1290 Cluster client does not support hotkeys command. Please use the non-cluster client. 

1291 

1292 For more information see https://redis.io/commands/hotkeys-stop 

1293 """ 

1294 raise NotImplementedError( 

1295 "HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client." 

1296 ) 

1297 

1298 async def hotkeys_reset(self, **kwargs) -> Awaitable[str | bytes]: 

1299 """ 

1300 Cluster client does not support hotkeys command. Please use the non-cluster client. 

1301 

1302 For more information see https://redis.io/commands/hotkeys-reset 

1303 """ 

1304 raise NotImplementedError( 

1305 "HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client." 

1306 ) 

1307 

1308 async def hotkeys_get(self, **kwargs) -> Awaitable[list[dict[str | bytes, Any]]]: 

1309 """ 

1310 Cluster client does not support hotkeys command. Please use the non-cluster client. 

1311 

1312 For more information see https://redis.io/commands/hotkeys-get 

1313 """ 

1314 raise NotImplementedError( 

1315 "HOTKEYS commands are not supported in cluster mode. Please use the non-cluster client." 

1316 ) 

1317 

1318 

1319class ClusterDataAccessCommands(DataAccessCommands): 

1320 """ 

1321 A class for Redis Cluster Data Access Commands 

1322 

1323 The class inherits from Redis's core DataAccessCommand class and do the 

1324 required adjustments to work with cluster mode 

1325 """ 

1326 

1327 @overload 

1328 def stralgo( 

1329 self: SyncClientProtocol, 

1330 algo: Literal["LCS"], 

1331 value1: KeyT, 

1332 value2: KeyT, 

1333 specific_argument: Literal["strings"] | Literal["keys"] = "strings", 

1334 len: bool = False, 

1335 idx: bool = False, 

1336 minmatchlen: int | None = None, 

1337 withmatchlen: bool = False, 

1338 **kwargs, 

1339 ) -> StralgoResponse: ... 

1340 

1341 @overload 

1342 def stralgo( 

1343 self: AsyncClientProtocol, 

1344 algo: Literal["LCS"], 

1345 value1: KeyT, 

1346 value2: KeyT, 

1347 specific_argument: Literal["strings"] | Literal["keys"] = "strings", 

1348 len: bool = False, 

1349 idx: bool = False, 

1350 minmatchlen: int | None = None, 

1351 withmatchlen: bool = False, 

1352 **kwargs, 

1353 ) -> Awaitable[StralgoResponse]: ... 

1354 

1355 def stralgo( 

1356 self, 

1357 algo: Literal["LCS"], 

1358 value1: KeyT, 

1359 value2: KeyT, 

1360 specific_argument: Literal["strings"] | Literal["keys"] = "strings", 

1361 len: bool = False, 

1362 idx: bool = False, 

1363 minmatchlen: int | None = None, 

1364 withmatchlen: bool = False, 

1365 **kwargs, 

1366 ) -> StralgoResponse | Awaitable[StralgoResponse]: 

1367 """ 

1368 Implements complex algorithms that operate on strings. 

1369 Right now the only algorithm implemented is the LCS algorithm 

1370 (longest common substring). However new algorithms could be 

1371 implemented in the future. 

1372 

1373 ``algo`` Right now must be LCS 

1374 ``value1`` and ``value2`` Can be two strings or two keys 

1375 ``specific_argument`` Specifying if the arguments to the algorithm 

1376 will be keys or strings. strings is the default. 

1377 ``len`` Returns just the len of the match. 

1378 ``idx`` Returns the match positions in each string. 

1379 ``minmatchlen`` Restrict the list of matches to the ones of a given 

1380 minimal length. Can be provided only when ``idx`` set to True. 

1381 ``withmatchlen`` Returns the matches with the len of the match. 

1382 Can be provided only when ``idx`` set to True. 

1383 

1384 For more information see https://redis.io/commands/stralgo 

1385 """ 

1386 target_nodes = kwargs.pop("target_nodes", None) 

1387 if specific_argument == "strings" and target_nodes is None: 

1388 target_nodes = "default-node" 

1389 kwargs.update({"target_nodes": target_nodes}) 

1390 return super().stralgo( 

1391 algo, 

1392 value1, 

1393 value2, 

1394 specific_argument, 

1395 len, 

1396 idx, 

1397 minmatchlen, 

1398 withmatchlen, 

1399 **kwargs, 

1400 ) 

1401 

1402 def scan_iter( 

1403 self, 

1404 match: PatternT | None = None, 

1405 count: int | None = None, 

1406 _type: str | None = None, 

1407 **kwargs, 

1408 ) -> Iterator: 

1409 # Do the first query with cursor=0 for all nodes 

1410 cursors, data = self.scan(match=match, count=count, _type=_type, **kwargs) 

1411 yield from data 

1412 

1413 cursors = {name: cursor for name, cursor in cursors.items() if cursor != 0} 

1414 if cursors: 

1415 # Get nodes by name 

1416 nodes = {name: self.get_node(node_name=name) for name in cursors.keys()} 

1417 

1418 # Iterate over each node till its cursor is 0 

1419 kwargs.pop("target_nodes", None) 

1420 while cursors: 

1421 for name, cursor in cursors.items(): 

1422 cur, data = self.scan( 

1423 cursor=cursor, 

1424 match=match, 

1425 count=count, 

1426 _type=_type, 

1427 target_nodes=nodes[name], 

1428 **kwargs, 

1429 ) 

1430 yield from data 

1431 cursors[name] = cur[name] 

1432 

1433 cursors = { 

1434 name: cursor for name, cursor in cursors.items() if cursor != 0 

1435 } 

1436 

1437 

1438class AsyncClusterDataAccessCommands( 

1439 ClusterDataAccessCommands, AsyncDataAccessCommands 

1440): 

1441 """ 

1442 A class for Redis Cluster Data Access Commands 

1443 

1444 The class inherits from Redis's core DataAccessCommand class and do the 

1445 required adjustments to work with cluster mode 

1446 """ 

1447 

1448 async def scan_iter( 

1449 self, 

1450 match: PatternT | None = None, 

1451 count: int | None = None, 

1452 _type: str | None = None, 

1453 **kwargs, 

1454 ) -> AsyncIterator: 

1455 # Do the first query with cursor=0 for all nodes 

1456 cursors, data = await self.scan(match=match, count=count, _type=_type, **kwargs) 

1457 for value in data: 

1458 yield value 

1459 

1460 cursors = {name: cursor for name, cursor in cursors.items() if cursor != 0} 

1461 if cursors: 

1462 # Get nodes by name 

1463 nodes = {name: self.get_node(node_name=name) for name in cursors.keys()} 

1464 

1465 # Iterate over each node till its cursor is 0 

1466 kwargs.pop("target_nodes", None) 

1467 while cursors: 

1468 for name, cursor in cursors.items(): 

1469 cur, data = await self.scan( 

1470 cursor=cursor, 

1471 match=match, 

1472 count=count, 

1473 _type=_type, 

1474 target_nodes=nodes[name], 

1475 **kwargs, 

1476 ) 

1477 for value in data: 

1478 yield value 

1479 cursors[name] = cur[name] 

1480 

1481 cursors = { 

1482 name: cursor for name, cursor in cursors.items() if cursor != 0 

1483 } 

1484 

1485 

1486class RedisClusterCommands( 

1487 ClusterMultiKeyCommands, 

1488 ClusterManagementCommands, 

1489 ACLCommands, 

1490 PubSubCommands, 

1491 ClusterDataAccessCommands, 

1492 ScriptCommands, 

1493 FunctionCommands, 

1494 ModuleCommands, 

1495 RedisModuleCommands, 

1496): 

1497 """ 

1498 A class for all Redis Cluster commands 

1499 

1500 For key-based commands, the target node(s) will be internally determined 

1501 by the keys' hash slot. 

1502 Non-key-based commands can be executed with the 'target_nodes' argument to 

1503 target specific nodes. By default, if target_nodes is not specified, the 

1504 command will be executed on the default cluster node. 

1505 

1506 :param :target_nodes: type can be one of the followings: 

1507 - nodes flag: ALL_NODES, PRIMARIES, REPLICAS, RANDOM 

1508 - 'ClusterNode' 

1509 - 'list(ClusterNodes)' 

1510 - 'dict(any:clusterNodes)' 

1511 

1512 for example: 

1513 r.cluster_info(target_nodes=RedisCluster.ALL_NODES) 

1514 """ 

1515 

1516 

1517class AsyncRedisClusterCommands( 

1518 AsyncClusterMultiKeyCommands, 

1519 AsyncClusterManagementCommands, 

1520 AsyncACLCommands, 

1521 PubSubCommands, 

1522 AsyncClusterDataAccessCommands, 

1523 AsyncScriptCommands, 

1524 AsyncFunctionCommands, 

1525 AsyncModuleCommands, 

1526 AsyncRedisModuleCommands, 

1527): 

1528 """ 

1529 A class for all Redis Cluster commands 

1530 

1531 For key-based commands, the target node(s) will be internally determined 

1532 by the keys' hash slot. 

1533 Non-key-based commands can be executed with the 'target_nodes' argument to 

1534 target specific nodes. By default, if target_nodes is not specified, the 

1535 command will be executed on the default cluster node. 

1536 

1537 :param :target_nodes: type can be one of the followings: 

1538 - nodes flag: ALL_NODES, PRIMARIES, REPLICAS, RANDOM 

1539 - 'ClusterNode' 

1540 - 'list(ClusterNodes)' 

1541 - 'dict(any:clusterNodes)' 

1542 

1543 for example: 

1544 r.cluster_info(target_nodes=RedisCluster.ALL_NODES) 

1545 """