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

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

2024 statements  

1# from __future__ import annotations 

2 

3import datetime 

4import hashlib 

5 

6# Try to import the xxhash library as an optional dependency 

7try: 

8 import xxhash 

9 

10 HAS_XXHASH = True 

11except ImportError: 

12 HAS_XXHASH = False 

13 

14import warnings 

15from enum import Enum 

16from typing import ( 

17 TYPE_CHECKING, 

18 Any, 

19 AsyncIterator, 

20 Awaitable, 

21 Callable, 

22 Dict, 

23 Iterable, 

24 Iterator, 

25 List, 

26 Literal, 

27 Mapping, 

28 Optional, 

29 Sequence, 

30 Set, 

31 Tuple, 

32 Union, 

33) 

34 

35from redis.exceptions import ConnectionError, DataError, NoScriptError, RedisError 

36from redis.typing import ( 

37 AbsExpiryT, 

38 AnyKeyT, 

39 BitfieldOffsetT, 

40 ChannelT, 

41 CommandsProtocol, 

42 ConsumerT, 

43 EncodableT, 

44 ExpiryT, 

45 FieldT, 

46 GroupT, 

47 KeysT, 

48 KeyT, 

49 Number, 

50 PatternT, 

51 ResponseT, 

52 ScriptTextT, 

53 StreamIdT, 

54 TimeoutSecT, 

55 ZScoreBoundT, 

56) 

57from redis.utils import ( 

58 deprecated_function, 

59 experimental_args, 

60 experimental_method, 

61 extract_expire_flags, 

62) 

63 

64from .helpers import at_most_one_value_set, list_or_args 

65 

66if TYPE_CHECKING: 

67 import redis.asyncio.client 

68 import redis.client 

69 

70 

71class ACLCommands(CommandsProtocol): 

72 """ 

73 Redis Access Control List (ACL) commands. 

74 see: https://redis.io/topics/acl 

75 """ 

76 

77 def acl_cat(self, category: Optional[str] = None, **kwargs) -> ResponseT: 

78 """ 

79 Returns a list of categories or commands within a category. 

80 

81 If ``category`` is not supplied, returns a list of all categories. 

82 If ``category`` is supplied, returns a list of all commands within 

83 that category. 

84 

85 For more information, see https://redis.io/commands/acl-cat 

86 """ 

87 pieces: list[EncodableT] = [category] if category else [] 

88 return self.execute_command("ACL CAT", *pieces, **kwargs) 

89 

90 def acl_dryrun(self, username, *args, **kwargs): 

91 """ 

92 Simulate the execution of a given command by a given ``username``. 

93 

94 For more information, see https://redis.io/commands/acl-dryrun 

95 """ 

96 return self.execute_command("ACL DRYRUN", username, *args, **kwargs) 

97 

98 def acl_deluser(self, *username: str, **kwargs) -> ResponseT: 

99 """ 

100 Delete the ACL for the specified ``username``\\s 

101 

102 For more information, see https://redis.io/commands/acl-deluser 

103 """ 

104 return self.execute_command("ACL DELUSER", *username, **kwargs) 

105 

106 def acl_genpass(self, bits: Optional[int] = None, **kwargs) -> ResponseT: 

107 """Generate a random password value. 

108 If ``bits`` is supplied then use this number of bits, rounded to 

109 the next multiple of 4. 

110 See: https://redis.io/commands/acl-genpass 

111 """ 

112 pieces = [] 

113 if bits is not None: 

114 try: 

115 b = int(bits) 

116 if b < 0 or b > 4096: 

117 raise ValueError 

118 pieces.append(b) 

119 except ValueError: 

120 raise DataError( 

121 "genpass optionally accepts a bits argument, between 0 and 4096." 

122 ) 

123 return self.execute_command("ACL GENPASS", *pieces, **kwargs) 

124 

125 def acl_getuser(self, username: str, **kwargs) -> ResponseT: 

126 """ 

127 Get the ACL details for the specified ``username``. 

128 

129 If ``username`` does not exist, return None 

130 

131 For more information, see https://redis.io/commands/acl-getuser 

132 """ 

133 return self.execute_command("ACL GETUSER", username, **kwargs) 

134 

135 def acl_help(self, **kwargs) -> ResponseT: 

136 """The ACL HELP command returns helpful text describing 

137 the different subcommands. 

138 

139 For more information, see https://redis.io/commands/acl-help 

140 """ 

141 return self.execute_command("ACL HELP", **kwargs) 

142 

143 def acl_list(self, **kwargs) -> ResponseT: 

144 """ 

145 Return a list of all ACLs on the server 

146 

147 For more information, see https://redis.io/commands/acl-list 

148 """ 

149 return self.execute_command("ACL LIST", **kwargs) 

150 

151 def acl_log(self, count: Optional[int] = None, **kwargs) -> ResponseT: 

152 """ 

153 Get ACL logs as a list. 

154 :param int count: Get logs[0:count]. 

155 :rtype: List. 

156 

157 For more information, see https://redis.io/commands/acl-log 

158 """ 

159 args = [] 

160 if count is not None: 

161 if not isinstance(count, int): 

162 raise DataError("ACL LOG count must be an integer") 

163 args.append(count) 

164 

165 return self.execute_command("ACL LOG", *args, **kwargs) 

166 

167 def acl_log_reset(self, **kwargs) -> ResponseT: 

168 """ 

169 Reset ACL logs. 

170 :rtype: Boolean. 

171 

172 For more information, see https://redis.io/commands/acl-log 

173 """ 

174 args = [b"RESET"] 

175 return self.execute_command("ACL LOG", *args, **kwargs) 

176 

177 def acl_load(self, **kwargs) -> ResponseT: 

178 """ 

179 Load ACL rules from the configured ``aclfile``. 

180 

181 Note that the server must be configured with the ``aclfile`` 

182 directive to be able to load ACL rules from an aclfile. 

183 

184 For more information, see https://redis.io/commands/acl-load 

185 """ 

186 return self.execute_command("ACL LOAD", **kwargs) 

187 

188 def acl_save(self, **kwargs) -> ResponseT: 

189 """ 

190 Save ACL rules to the configured ``aclfile``. 

191 

192 Note that the server must be configured with the ``aclfile`` 

193 directive to be able to save ACL rules to an aclfile. 

194 

195 For more information, see https://redis.io/commands/acl-save 

196 """ 

197 return self.execute_command("ACL SAVE", **kwargs) 

198 

199 def acl_setuser( 

200 self, 

201 username: str, 

202 enabled: bool = False, 

203 nopass: bool = False, 

204 passwords: Optional[Union[str, Iterable[str]]] = None, 

205 hashed_passwords: Optional[Union[str, Iterable[str]]] = None, 

206 categories: Optional[Iterable[str]] = None, 

207 commands: Optional[Iterable[str]] = None, 

208 keys: Optional[Iterable[KeyT]] = None, 

209 channels: Optional[Iterable[ChannelT]] = None, 

210 selectors: Optional[Iterable[Tuple[str, KeyT]]] = None, 

211 reset: bool = False, 

212 reset_keys: bool = False, 

213 reset_channels: bool = False, 

214 reset_passwords: bool = False, 

215 **kwargs, 

216 ) -> ResponseT: 

217 """ 

218 Create or update an ACL user. 

219 

220 Create or update the ACL for `username`. If the user already exists, 

221 the existing ACL is completely overwritten and replaced with the 

222 specified values. 

223 

224 For more information, see https://redis.io/commands/acl-setuser 

225 

226 Args: 

227 username: The name of the user whose ACL is to be created or updated. 

228 enabled: Indicates whether the user should be allowed to authenticate. 

229 Defaults to `False`. 

230 nopass: Indicates whether the user can authenticate without a password. 

231 This cannot be `True` if `passwords` are also specified. 

232 passwords: A list of plain text passwords to add to or remove from the user. 

233 Each password must be prefixed with a '+' to add or a '-' to 

234 remove. For convenience, a single prefixed string can be used 

235 when adding or removing a single password. 

236 hashed_passwords: A list of SHA-256 hashed passwords to add to or remove 

237 from the user. Each hashed password must be prefixed with 

238 a '+' to add or a '-' to remove. For convenience, a single 

239 prefixed string can be used when adding or removing a 

240 single password. 

241 categories: A list of strings representing category permissions. Each string 

242 must be prefixed with either a '+' to add the category 

243 permission or a '-' to remove the category permission. 

244 commands: A list of strings representing command permissions. Each string 

245 must be prefixed with either a '+' to add the command permission 

246 or a '-' to remove the command permission. 

247 keys: A list of key patterns to grant the user access to. Key patterns allow 

248 ``'*'`` to support wildcard matching. For example, ``'*'`` grants 

249 access to all keys while ``'cache:*'`` grants access to all keys that 

250 are prefixed with ``cache:``. 

251 `keys` should not be prefixed with a ``'~'``. 

252 reset: Indicates whether the user should be fully reset prior to applying 

253 the new ACL. Setting this to `True` will remove all existing 

254 passwords, flags, and privileges from the user and then apply the 

255 specified rules. If `False`, the user's existing passwords, flags, 

256 and privileges will be kept and any new specified rules will be 

257 applied on top. 

258 reset_keys: Indicates whether the user's key permissions should be reset 

259 prior to applying any new key permissions specified in `keys`. 

260 If `False`, the user's existing key permissions will be kept and 

261 any new specified key permissions will be applied on top. 

262 reset_channels: Indicates whether the user's channel permissions should be 

263 reset prior to applying any new channel permissions 

264 specified in `channels`. If `False`, the user's existing 

265 channel permissions will be kept and any new specified 

266 channel permissions will be applied on top. 

267 reset_passwords: Indicates whether to remove all existing passwords and the 

268 `nopass` flag from the user prior to applying any new 

269 passwords specified in `passwords` or `hashed_passwords`. 

270 If `False`, the user's existing passwords and `nopass` 

271 status will be kept and any new specified passwords or 

272 hashed passwords will be applied on top. 

273 """ 

274 encoder = self.get_encoder() 

275 pieces: List[EncodableT] = [username] 

276 

277 if reset: 

278 pieces.append(b"reset") 

279 

280 if reset_keys: 

281 pieces.append(b"resetkeys") 

282 

283 if reset_channels: 

284 pieces.append(b"resetchannels") 

285 

286 if reset_passwords: 

287 pieces.append(b"resetpass") 

288 

289 if enabled: 

290 pieces.append(b"on") 

291 else: 

292 pieces.append(b"off") 

293 

294 if (passwords or hashed_passwords) and nopass: 

295 raise DataError( 

296 "Cannot set 'nopass' and supply 'passwords' or 'hashed_passwords'" 

297 ) 

298 

299 if passwords: 

300 # as most users will have only one password, allow remove_passwords 

301 # to be specified as a simple string or a list 

302 passwords = list_or_args(passwords, []) 

303 for i, password in enumerate(passwords): 

304 password = encoder.encode(password) 

305 if password.startswith(b"+"): 

306 pieces.append(b">%s" % password[1:]) 

307 elif password.startswith(b"-"): 

308 pieces.append(b"<%s" % password[1:]) 

309 else: 

310 raise DataError( 

311 f"Password {i} must be prefixed with a " 

312 f'"+" to add or a "-" to remove' 

313 ) 

314 

315 if hashed_passwords: 

316 # as most users will have only one password, allow remove_passwords 

317 # to be specified as a simple string or a list 

318 hashed_passwords = list_or_args(hashed_passwords, []) 

319 for i, hashed_password in enumerate(hashed_passwords): 

320 hashed_password = encoder.encode(hashed_password) 

321 if hashed_password.startswith(b"+"): 

322 pieces.append(b"#%s" % hashed_password[1:]) 

323 elif hashed_password.startswith(b"-"): 

324 pieces.append(b"!%s" % hashed_password[1:]) 

325 else: 

326 raise DataError( 

327 f"Hashed password {i} must be prefixed with a " 

328 f'"+" to add or a "-" to remove' 

329 ) 

330 

331 if nopass: 

332 pieces.append(b"nopass") 

333 

334 if categories: 

335 for category in categories: 

336 category = encoder.encode(category) 

337 # categories can be prefixed with one of (+@, +, -@, -) 

338 if category.startswith(b"+@"): 

339 pieces.append(category) 

340 elif category.startswith(b"+"): 

341 pieces.append(b"+@%s" % category[1:]) 

342 elif category.startswith(b"-@"): 

343 pieces.append(category) 

344 elif category.startswith(b"-"): 

345 pieces.append(b"-@%s" % category[1:]) 

346 else: 

347 raise DataError( 

348 f'Category "{encoder.decode(category, force=True)}" ' 

349 'must be prefixed with "+" or "-"' 

350 ) 

351 if commands: 

352 for cmd in commands: 

353 cmd = encoder.encode(cmd) 

354 if not cmd.startswith(b"+") and not cmd.startswith(b"-"): 

355 raise DataError( 

356 f'Command "{encoder.decode(cmd, force=True)}" ' 

357 'must be prefixed with "+" or "-"' 

358 ) 

359 pieces.append(cmd) 

360 

361 if keys: 

362 for key in keys: 

363 key = encoder.encode(key) 

364 if not key.startswith(b"%") and not key.startswith(b"~"): 

365 key = b"~%s" % key 

366 pieces.append(key) 

367 

368 if channels: 

369 for channel in channels: 

370 channel = encoder.encode(channel) 

371 pieces.append(b"&%s" % channel) 

372 

373 if selectors: 

374 for cmd, key in selectors: 

375 cmd = encoder.encode(cmd) 

376 if not cmd.startswith(b"+") and not cmd.startswith(b"-"): 

377 raise DataError( 

378 f'Command "{encoder.decode(cmd, force=True)}" ' 

379 'must be prefixed with "+" or "-"' 

380 ) 

381 

382 key = encoder.encode(key) 

383 if not key.startswith(b"%") and not key.startswith(b"~"): 

384 key = b"~%s" % key 

385 

386 pieces.append(b"(%s %s)" % (cmd, key)) 

387 

388 return self.execute_command("ACL SETUSER", *pieces, **kwargs) 

389 

390 def acl_users(self, **kwargs) -> ResponseT: 

391 """Returns a list of all registered users on the server. 

392 

393 For more information, see https://redis.io/commands/acl-users 

394 """ 

395 return self.execute_command("ACL USERS", **kwargs) 

396 

397 def acl_whoami(self, **kwargs) -> ResponseT: 

398 """Get the username for the current connection 

399 

400 For more information, see https://redis.io/commands/acl-whoami 

401 """ 

402 return self.execute_command("ACL WHOAMI", **kwargs) 

403 

404 

405AsyncACLCommands = ACLCommands 

406 

407 

408class HotkeysMetricsTypes(Enum): 

409 CPU = "CPU" 

410 NET = "NET" 

411 

412 

413class ManagementCommands(CommandsProtocol): 

414 """ 

415 Redis management commands 

416 """ 

417 

418 def auth(self, password: str, username: Optional[str] = None, **kwargs): 

419 """ 

420 Authenticates the user. If you do not pass username, Redis will try to 

421 authenticate for the "default" user. If you do pass username, it will 

422 authenticate for the given user. 

423 For more information, see https://redis.io/commands/auth 

424 """ 

425 pieces = [] 

426 if username is not None: 

427 pieces.append(username) 

428 pieces.append(password) 

429 return self.execute_command("AUTH", *pieces, **kwargs) 

430 

431 def bgrewriteaof(self, **kwargs): 

432 """Tell the Redis server to rewrite the AOF file from data in memory. 

433 

434 For more information, see https://redis.io/commands/bgrewriteaof 

435 """ 

436 return self.execute_command("BGREWRITEAOF", **kwargs) 

437 

438 def bgsave(self, schedule: bool = True, **kwargs) -> ResponseT: 

439 """ 

440 Tell the Redis server to save its data to disk. Unlike save(), 

441 this method is asynchronous and returns immediately. 

442 

443 For more information, see https://redis.io/commands/bgsave 

444 """ 

445 pieces = [] 

446 if schedule: 

447 pieces.append("SCHEDULE") 

448 return self.execute_command("BGSAVE", *pieces, **kwargs) 

449 

450 def role(self) -> ResponseT: 

451 """ 

452 Provide information on the role of a Redis instance in 

453 the context of replication, by returning if the instance 

454 is currently a master, slave, or sentinel. 

455 

456 For more information, see https://redis.io/commands/role 

457 """ 

458 return self.execute_command("ROLE") 

459 

460 def client_kill(self, address: str, **kwargs) -> ResponseT: 

461 """Disconnects the client at ``address`` (ip:port) 

462 

463 For more information, see https://redis.io/commands/client-kill 

464 """ 

465 return self.execute_command("CLIENT KILL", address, **kwargs) 

466 

467 def client_kill_filter( 

468 self, 

469 _id: Optional[str] = None, 

470 _type: Optional[str] = None, 

471 addr: Optional[str] = None, 

472 skipme: Optional[bool] = None, 

473 laddr: Optional[bool] = None, 

474 user: Optional[str] = None, 

475 maxage: Optional[int] = None, 

476 **kwargs, 

477 ) -> ResponseT: 

478 """ 

479 Disconnects client(s) using a variety of filter options 

480 :param _id: Kills a client by its unique ID field 

481 :param _type: Kills a client by type where type is one of 'normal', 

482 'master', 'slave' or 'pubsub' 

483 :param addr: Kills a client by its 'address:port' 

484 :param skipme: If True, then the client calling the command 

485 will not get killed even if it is identified by one of the filter 

486 options. If skipme is not provided, the server defaults to skipme=True 

487 :param laddr: Kills a client by its 'local (bind) address:port' 

488 :param user: Kills a client for a specific user name 

489 :param maxage: Kills clients that are older than the specified age in seconds 

490 """ 

491 args = [] 

492 if _type is not None: 

493 client_types = ("normal", "master", "slave", "pubsub") 

494 if str(_type).lower() not in client_types: 

495 raise DataError(f"CLIENT KILL type must be one of {client_types!r}") 

496 args.extend((b"TYPE", _type)) 

497 if skipme is not None: 

498 if not isinstance(skipme, bool): 

499 raise DataError("CLIENT KILL skipme must be a bool") 

500 if skipme: 

501 args.extend((b"SKIPME", b"YES")) 

502 else: 

503 args.extend((b"SKIPME", b"NO")) 

504 if _id is not None: 

505 args.extend((b"ID", _id)) 

506 if addr is not None: 

507 args.extend((b"ADDR", addr)) 

508 if laddr is not None: 

509 args.extend((b"LADDR", laddr)) 

510 if user is not None: 

511 args.extend((b"USER", user)) 

512 if maxage is not None: 

513 args.extend((b"MAXAGE", maxage)) 

514 if not args: 

515 raise DataError( 

516 "CLIENT KILL <filter> <value> ... ... <filter> " 

517 "<value> must specify at least one filter" 

518 ) 

519 return self.execute_command("CLIENT KILL", *args, **kwargs) 

520 

521 def client_info(self, **kwargs) -> ResponseT: 

522 """ 

523 Returns information and statistics about the current 

524 client connection. 

525 

526 For more information, see https://redis.io/commands/client-info 

527 """ 

528 return self.execute_command("CLIENT INFO", **kwargs) 

529 

530 def client_list( 

531 self, _type: Optional[str] = None, client_id: List[EncodableT] = [], **kwargs 

532 ) -> ResponseT: 

533 """ 

534 Returns a list of currently connected clients. 

535 If type of client specified, only that type will be returned. 

536 

537 :param _type: optional. one of the client types (normal, master, 

538 replica, pubsub) 

539 :param client_id: optional. a list of client ids 

540 

541 For more information, see https://redis.io/commands/client-list 

542 """ 

543 args = [] 

544 if _type is not None: 

545 client_types = ("normal", "master", "replica", "pubsub") 

546 if str(_type).lower() not in client_types: 

547 raise DataError(f"CLIENT LIST _type must be one of {client_types!r}") 

548 args.append(b"TYPE") 

549 args.append(_type) 

550 if not isinstance(client_id, list): 

551 raise DataError("client_id must be a list") 

552 if client_id: 

553 args.append(b"ID") 

554 args += client_id 

555 return self.execute_command("CLIENT LIST", *args, **kwargs) 

556 

557 def client_getname(self, **kwargs) -> ResponseT: 

558 """ 

559 Returns the current connection name 

560 

561 For more information, see https://redis.io/commands/client-getname 

562 """ 

563 return self.execute_command("CLIENT GETNAME", **kwargs) 

564 

565 def client_getredir(self, **kwargs) -> ResponseT: 

566 """ 

567 Returns the ID (an integer) of the client to whom we are 

568 redirecting tracking notifications. 

569 

570 see: https://redis.io/commands/client-getredir 

571 """ 

572 return self.execute_command("CLIENT GETREDIR", **kwargs) 

573 

574 def client_reply( 

575 self, reply: Union[Literal["ON"], Literal["OFF"], Literal["SKIP"]], **kwargs 

576 ) -> ResponseT: 

577 """ 

578 Enable and disable redis server replies. 

579 

580 ``reply`` Must be ON OFF or SKIP, 

581 ON - The default most with server replies to commands 

582 OFF - Disable server responses to commands 

583 SKIP - Skip the response of the immediately following command. 

584 

585 Note: When setting OFF or SKIP replies, you will need a client object 

586 with a timeout specified in seconds, and will need to catch the 

587 TimeoutError. 

588 The test_client_reply unit test illustrates this, and 

589 conftest.py has a client with a timeout. 

590 

591 See https://redis.io/commands/client-reply 

592 """ 

593 replies = ["ON", "OFF", "SKIP"] 

594 if reply not in replies: 

595 raise DataError(f"CLIENT REPLY must be one of {replies!r}") 

596 return self.execute_command("CLIENT REPLY", reply, **kwargs) 

597 

598 def client_id(self, **kwargs) -> ResponseT: 

599 """ 

600 Returns the current connection id 

601 

602 For more information, see https://redis.io/commands/client-id 

603 """ 

604 return self.execute_command("CLIENT ID", **kwargs) 

605 

606 def client_tracking_on( 

607 self, 

608 clientid: Optional[int] = None, 

609 prefix: Sequence[KeyT] = [], 

610 bcast: bool = False, 

611 optin: bool = False, 

612 optout: bool = False, 

613 noloop: bool = False, 

614 ) -> ResponseT: 

615 """ 

616 Turn on the tracking mode. 

617 For more information, about the options look at client_tracking func. 

618 

619 See https://redis.io/commands/client-tracking 

620 """ 

621 return self.client_tracking( 

622 True, clientid, prefix, bcast, optin, optout, noloop 

623 ) 

624 

625 def client_tracking_off( 

626 self, 

627 clientid: Optional[int] = None, 

628 prefix: Sequence[KeyT] = [], 

629 bcast: bool = False, 

630 optin: bool = False, 

631 optout: bool = False, 

632 noloop: bool = False, 

633 ) -> ResponseT: 

634 """ 

635 Turn off the tracking mode. 

636 For more information, about the options look at client_tracking func. 

637 

638 See https://redis.io/commands/client-tracking 

639 """ 

640 return self.client_tracking( 

641 False, clientid, prefix, bcast, optin, optout, noloop 

642 ) 

643 

644 def client_tracking( 

645 self, 

646 on: bool = True, 

647 clientid: Optional[int] = None, 

648 prefix: Sequence[KeyT] = [], 

649 bcast: bool = False, 

650 optin: bool = False, 

651 optout: bool = False, 

652 noloop: bool = False, 

653 **kwargs, 

654 ) -> ResponseT: 

655 """ 

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

657 for server assisted client side caching. 

658 

659 ``on`` indicate for tracking on or tracking off. The default is on. 

660 

661 ``clientid`` send invalidation messages to the connection with 

662 the specified ID. 

663 

664 ``bcast`` enable tracking in broadcasting mode. In this mode 

665 invalidation messages are reported for all the prefixes 

666 specified, regardless of the keys requested by the connection. 

667 

668 ``optin`` when broadcasting is NOT active, normally don't track 

669 keys in read only commands, unless they are called immediately 

670 after a CLIENT CACHING yes command. 

671 

672 ``optout`` when broadcasting is NOT active, normally track keys in 

673 read only commands, unless they are called immediately after a 

674 CLIENT CACHING no command. 

675 

676 ``noloop`` don't send notifications about keys modified by this 

677 connection itself. 

678 

679 ``prefix`` for broadcasting, register a given key prefix, so that 

680 notifications will be provided only for keys starting with this string. 

681 

682 See https://redis.io/commands/client-tracking 

683 """ 

684 

685 if len(prefix) != 0 and bcast is False: 

686 raise DataError("Prefix can only be used with bcast") 

687 

688 pieces = ["ON"] if on else ["OFF"] 

689 if clientid is not None: 

690 pieces.extend(["REDIRECT", clientid]) 

691 for p in prefix: 

692 pieces.extend(["PREFIX", p]) 

693 if bcast: 

694 pieces.append("BCAST") 

695 if optin: 

696 pieces.append("OPTIN") 

697 if optout: 

698 pieces.append("OPTOUT") 

699 if noloop: 

700 pieces.append("NOLOOP") 

701 

702 return self.execute_command("CLIENT TRACKING", *pieces, **kwargs) 

703 

704 def client_trackinginfo(self, **kwargs) -> ResponseT: 

705 """ 

706 Returns the information about the current client connection's 

707 use of the server assisted client side cache. 

708 

709 See https://redis.io/commands/client-trackinginfo 

710 """ 

711 return self.execute_command("CLIENT TRACKINGINFO", **kwargs) 

712 

713 def client_setname(self, name: str, **kwargs) -> ResponseT: 

714 """ 

715 Sets the current connection name 

716 

717 For more information, see https://redis.io/commands/client-setname 

718 

719 .. note:: 

720 This method sets client name only for **current** connection. 

721 

722 If you want to set a common name for all connections managed 

723 by this client, use ``client_name`` constructor argument. 

724 """ 

725 return self.execute_command("CLIENT SETNAME", name, **kwargs) 

726 

727 def client_setinfo(self, attr: str, value: str, **kwargs) -> ResponseT: 

728 """ 

729 Sets the current connection library name or version 

730 For mor information see https://redis.io/commands/client-setinfo 

731 """ 

732 return self.execute_command("CLIENT SETINFO", attr, value, **kwargs) 

733 

734 def client_unblock( 

735 self, client_id: int, error: bool = False, **kwargs 

736 ) -> ResponseT: 

737 """ 

738 Unblocks a connection by its client id. 

739 If ``error`` is True, unblocks the client with a special error message. 

740 If ``error`` is False (default), the client is unblocked using the 

741 regular timeout mechanism. 

742 

743 For more information, see https://redis.io/commands/client-unblock 

744 """ 

745 args = ["CLIENT UNBLOCK", int(client_id)] 

746 if error: 

747 args.append(b"ERROR") 

748 return self.execute_command(*args, **kwargs) 

749 

750 def client_pause(self, timeout: int, all: bool = True, **kwargs) -> ResponseT: 

751 """ 

752 Suspend all the Redis clients for the specified amount of time. 

753 

754 

755 For more information, see https://redis.io/commands/client-pause 

756 

757 Args: 

758 timeout: milliseconds to pause clients 

759 all: If true (default) all client commands are blocked. 

760 otherwise, clients are only blocked if they attempt to execute 

761 a write command. 

762 

763 For the WRITE mode, some commands have special behavior: 

764 

765 * EVAL/EVALSHA: Will block client for all scripts. 

766 * PUBLISH: Will block client. 

767 * PFCOUNT: Will block client. 

768 * WAIT: Acknowledgments will be delayed, so this command will 

769 appear blocked. 

770 """ 

771 args = ["CLIENT PAUSE", str(timeout)] 

772 if not isinstance(timeout, int): 

773 raise DataError("CLIENT PAUSE timeout must be an integer") 

774 if not all: 

775 args.append("WRITE") 

776 return self.execute_command(*args, **kwargs) 

777 

778 def client_unpause(self, **kwargs) -> ResponseT: 

779 """ 

780 Unpause all redis clients 

781 

782 For more information, see https://redis.io/commands/client-unpause 

783 """ 

784 return self.execute_command("CLIENT UNPAUSE", **kwargs) 

785 

786 def client_no_evict(self, mode: str) -> Union[Awaitable[str], str]: 

787 """ 

788 Sets the client eviction mode for the current connection. 

789 

790 For more information, see https://redis.io/commands/client-no-evict 

791 """ 

792 return self.execute_command("CLIENT NO-EVICT", mode) 

793 

794 def client_no_touch(self, mode: str) -> Union[Awaitable[str], str]: 

795 """ 

796 # The command controls whether commands sent by the client will alter 

797 # the LRU/LFU of the keys they access. 

798 # When turned on, the current client will not change LFU/LRU stats, 

799 # unless it sends the TOUCH command. 

800 

801 For more information, see https://redis.io/commands/client-no-touch 

802 """ 

803 return self.execute_command("CLIENT NO-TOUCH", mode) 

804 

805 def command(self, **kwargs): 

806 """ 

807 Returns dict reply of details about all Redis commands. 

808 

809 For more information, see https://redis.io/commands/command 

810 """ 

811 return self.execute_command("COMMAND", **kwargs) 

812 

813 def command_info(self, **kwargs) -> None: 

814 raise NotImplementedError( 

815 "COMMAND INFO is intentionally not implemented in the client." 

816 ) 

817 

818 def command_count(self, **kwargs) -> ResponseT: 

819 return self.execute_command("COMMAND COUNT", **kwargs) 

820 

821 def command_list( 

822 self, 

823 module: Optional[str] = None, 

824 category: Optional[str] = None, 

825 pattern: Optional[str] = None, 

826 ) -> ResponseT: 

827 """ 

828 Return an array of the server's command names. 

829 You can use one of the following filters: 

830 ``module``: get the commands that belong to the module 

831 ``category``: get the commands in the ACL category 

832 ``pattern``: get the commands that match the given pattern 

833 

834 For more information, see https://redis.io/commands/command-list/ 

835 """ 

836 pieces = [] 

837 if module is not None: 

838 pieces.extend(["MODULE", module]) 

839 if category is not None: 

840 pieces.extend(["ACLCAT", category]) 

841 if pattern is not None: 

842 pieces.extend(["PATTERN", pattern]) 

843 

844 if pieces: 

845 pieces.insert(0, "FILTERBY") 

846 

847 return self.execute_command("COMMAND LIST", *pieces) 

848 

849 def command_getkeysandflags(self, *args: str) -> List[Union[str, List[str]]]: 

850 """ 

851 Returns array of keys from a full Redis command and their usage flags. 

852 

853 For more information, see https://redis.io/commands/command-getkeysandflags 

854 """ 

855 return self.execute_command("COMMAND GETKEYSANDFLAGS", *args) 

856 

857 def command_docs(self, *args): 

858 """ 

859 This function throws a NotImplementedError since it is intentionally 

860 not supported. 

861 """ 

862 raise NotImplementedError( 

863 "COMMAND DOCS is intentionally not implemented in the client." 

864 ) 

865 

866 def config_get( 

867 self, pattern: PatternT = "*", *args: PatternT, **kwargs 

868 ) -> ResponseT: 

869 """ 

870 Return a dictionary of configuration based on the ``pattern`` 

871 

872 For more information, see https://redis.io/commands/config-get 

873 """ 

874 return self.execute_command("CONFIG GET", pattern, *args, **kwargs) 

875 

876 def config_set( 

877 self, 

878 name: KeyT, 

879 value: EncodableT, 

880 *args: Union[KeyT, EncodableT], 

881 **kwargs, 

882 ) -> ResponseT: 

883 """Set config item ``name`` with ``value`` 

884 

885 For more information, see https://redis.io/commands/config-set 

886 """ 

887 return self.execute_command("CONFIG SET", name, value, *args, **kwargs) 

888 

889 def config_resetstat(self, **kwargs) -> ResponseT: 

890 """ 

891 Reset runtime statistics 

892 

893 For more information, see https://redis.io/commands/config-resetstat 

894 """ 

895 return self.execute_command("CONFIG RESETSTAT", **kwargs) 

896 

897 def config_rewrite(self, **kwargs) -> ResponseT: 

898 """ 

899 Rewrite config file with the minimal change to reflect running config. 

900 

901 For more information, see https://redis.io/commands/config-rewrite 

902 """ 

903 return self.execute_command("CONFIG REWRITE", **kwargs) 

904 

905 def dbsize(self, **kwargs) -> ResponseT: 

906 """ 

907 Returns the number of keys in the current database 

908 

909 For more information, see https://redis.io/commands/dbsize 

910 """ 

911 return self.execute_command("DBSIZE", **kwargs) 

912 

913 def debug_object(self, key: KeyT, **kwargs) -> ResponseT: 

914 """ 

915 Returns version specific meta information about a given key 

916 

917 For more information, see https://redis.io/commands/debug-object 

918 """ 

919 return self.execute_command("DEBUG OBJECT", key, **kwargs) 

920 

921 def debug_segfault(self, **kwargs) -> None: 

922 raise NotImplementedError( 

923 """ 

924 DEBUG SEGFAULT is intentionally not implemented in the client. 

925 

926 For more information, see https://redis.io/commands/debug-segfault 

927 """ 

928 ) 

929 

930 def echo(self, value: EncodableT, **kwargs) -> ResponseT: 

931 """ 

932 Echo the string back from the server 

933 

934 For more information, see https://redis.io/commands/echo 

935 """ 

936 return self.execute_command("ECHO", value, **kwargs) 

937 

938 def flushall(self, asynchronous: bool = False, **kwargs) -> ResponseT: 

939 """ 

940 Delete all keys in all databases on the current host. 

941 

942 ``asynchronous`` indicates whether the operation is 

943 executed asynchronously by the server. 

944 

945 For more information, see https://redis.io/commands/flushall 

946 """ 

947 args = [] 

948 if asynchronous: 

949 args.append(b"ASYNC") 

950 return self.execute_command("FLUSHALL", *args, **kwargs) 

951 

952 def flushdb(self, asynchronous: bool = False, **kwargs) -> ResponseT: 

953 """ 

954 Delete all keys in the current database. 

955 

956 ``asynchronous`` indicates whether the operation is 

957 executed asynchronously by the server. 

958 

959 For more information, see https://redis.io/commands/flushdb 

960 """ 

961 args = [] 

962 if asynchronous: 

963 args.append(b"ASYNC") 

964 return self.execute_command("FLUSHDB", *args, **kwargs) 

965 

966 def sync(self) -> ResponseT: 

967 """ 

968 Initiates a replication stream from the master. 

969 

970 For more information, see https://redis.io/commands/sync 

971 """ 

972 from redis.client import NEVER_DECODE 

973 

974 options = {} 

975 options[NEVER_DECODE] = [] 

976 return self.execute_command("SYNC", **options) 

977 

978 def psync(self, replicationid: str, offset: int): 

979 """ 

980 Initiates a replication stream from the master. 

981 Newer version for `sync`. 

982 

983 For more information, see https://redis.io/commands/sync 

984 """ 

985 from redis.client import NEVER_DECODE 

986 

987 options = {} 

988 options[NEVER_DECODE] = [] 

989 return self.execute_command("PSYNC", replicationid, offset, **options) 

990 

991 def swapdb(self, first: int, second: int, **kwargs) -> ResponseT: 

992 """ 

993 Swap two databases 

994 

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

996 """ 

997 return self.execute_command("SWAPDB", first, second, **kwargs) 

998 

999 def select(self, index: int, **kwargs) -> ResponseT: 

1000 """Select the Redis logical database at index. 

1001 

1002 See: https://redis.io/commands/select 

1003 """ 

1004 return self.execute_command("SELECT", index, **kwargs) 

1005 

1006 def info(self, section: Optional[str] = None, *args: str, **kwargs) -> ResponseT: 

1007 """ 

1008 Returns a dictionary containing information about the Redis server 

1009 

1010 The ``section`` option can be used to select a specific section 

1011 of information 

1012 

1013 The section option is not supported by older versions of Redis Server, 

1014 and will generate ResponseError 

1015 

1016 For more information, see https://redis.io/commands/info 

1017 """ 

1018 if section is None: 

1019 return self.execute_command("INFO", **kwargs) 

1020 else: 

1021 return self.execute_command("INFO", section, *args, **kwargs) 

1022 

1023 def lastsave(self, **kwargs) -> ResponseT: 

1024 """ 

1025 Return a Python datetime object representing the last time the 

1026 Redis database was saved to disk 

1027 

1028 For more information, see https://redis.io/commands/lastsave 

1029 """ 

1030 return self.execute_command("LASTSAVE", **kwargs) 

1031 

1032 def latency_doctor(self): 

1033 """Raise a NotImplementedError, as the client will not support LATENCY DOCTOR. 

1034 This function is best used within the redis-cli. 

1035 

1036 For more information, see https://redis.io/commands/latency-doctor 

1037 """ 

1038 raise NotImplementedError( 

1039 """ 

1040 LATENCY DOCTOR is intentionally not implemented in the client. 

1041 

1042 For more information, see https://redis.io/commands/latency-doctor 

1043 """ 

1044 ) 

1045 

1046 def latency_graph(self): 

1047 """Raise a NotImplementedError, as the client will not support LATENCY GRAPH. 

1048 This function is best used within the redis-cli. 

1049 

1050 For more information, see https://redis.io/commands/latency-graph. 

1051 """ 

1052 raise NotImplementedError( 

1053 """ 

1054 LATENCY GRAPH is intentionally not implemented in the client. 

1055 

1056 For more information, see https://redis.io/commands/latency-graph 

1057 """ 

1058 ) 

1059 

1060 def lolwut(self, *version_numbers: Union[str, float], **kwargs) -> ResponseT: 

1061 """ 

1062 Get the Redis version and a piece of generative computer art 

1063 

1064 See: https://redis.io/commands/lolwut 

1065 """ 

1066 if version_numbers: 

1067 return self.execute_command("LOLWUT VERSION", *version_numbers, **kwargs) 

1068 else: 

1069 return self.execute_command("LOLWUT", **kwargs) 

1070 

1071 def reset(self) -> ResponseT: 

1072 """Perform a full reset on the connection's server-side context. 

1073 

1074 See: https://redis.io/commands/reset 

1075 """ 

1076 return self.execute_command("RESET") 

1077 

1078 def migrate( 

1079 self, 

1080 host: str, 

1081 port: int, 

1082 keys: KeysT, 

1083 destination_db: int, 

1084 timeout: int, 

1085 copy: bool = False, 

1086 replace: bool = False, 

1087 auth: Optional[str] = None, 

1088 **kwargs, 

1089 ) -> ResponseT: 

1090 """ 

1091 Migrate 1 or more keys from the current Redis server to a different 

1092 server specified by the ``host``, ``port`` and ``destination_db``. 

1093 

1094 The ``timeout``, specified in milliseconds, indicates the maximum 

1095 time the connection between the two servers can be idle before the 

1096 command is interrupted. 

1097 

1098 If ``copy`` is True, the specified ``keys`` are NOT deleted from 

1099 the source server. 

1100 

1101 If ``replace`` is True, this operation will overwrite the keys 

1102 on the destination server if they exist. 

1103 

1104 If ``auth`` is specified, authenticate to the destination server with 

1105 the password provided. 

1106 

1107 For more information, see https://redis.io/commands/migrate 

1108 """ 

1109 keys = list_or_args(keys, []) 

1110 if not keys: 

1111 raise DataError("MIGRATE requires at least one key") 

1112 pieces = [] 

1113 if copy: 

1114 pieces.append(b"COPY") 

1115 if replace: 

1116 pieces.append(b"REPLACE") 

1117 if auth: 

1118 pieces.append(b"AUTH") 

1119 pieces.append(auth) 

1120 pieces.append(b"KEYS") 

1121 pieces.extend(keys) 

1122 return self.execute_command( 

1123 "MIGRATE", host, port, "", destination_db, timeout, *pieces, **kwargs 

1124 ) 

1125 

1126 def object(self, infotype: str, key: KeyT, **kwargs) -> ResponseT: 

1127 """ 

1128 Return the encoding, idletime, or refcount about the key 

1129 """ 

1130 return self.execute_command( 

1131 "OBJECT", infotype, key, infotype=infotype, **kwargs 

1132 ) 

1133 

1134 def memory_doctor(self, **kwargs) -> None: 

1135 raise NotImplementedError( 

1136 """ 

1137 MEMORY DOCTOR is intentionally not implemented in the client. 

1138 

1139 For more information, see https://redis.io/commands/memory-doctor 

1140 """ 

1141 ) 

1142 

1143 def memory_help(self, **kwargs) -> None: 

1144 raise NotImplementedError( 

1145 """ 

1146 MEMORY HELP is intentionally not implemented in the client. 

1147 

1148 For more information, see https://redis.io/commands/memory-help 

1149 """ 

1150 ) 

1151 

1152 def memory_stats(self, **kwargs) -> ResponseT: 

1153 """ 

1154 Return a dictionary of memory stats 

1155 

1156 For more information, see https://redis.io/commands/memory-stats 

1157 """ 

1158 return self.execute_command("MEMORY STATS", **kwargs) 

1159 

1160 def memory_malloc_stats(self, **kwargs) -> ResponseT: 

1161 """ 

1162 Return an internal statistics report from the memory allocator. 

1163 

1164 See: https://redis.io/commands/memory-malloc-stats 

1165 """ 

1166 return self.execute_command("MEMORY MALLOC-STATS", **kwargs) 

1167 

1168 def memory_usage( 

1169 self, key: KeyT, samples: Optional[int] = None, **kwargs 

1170 ) -> ResponseT: 

1171 """ 

1172 Return the total memory usage for key, its value and associated 

1173 administrative overheads. 

1174 

1175 For nested data structures, ``samples`` is the number of elements to 

1176 sample. If left unspecified, the server's default is 5. Use 0 to sample 

1177 all elements. 

1178 

1179 For more information, see https://redis.io/commands/memory-usage 

1180 """ 

1181 args = [] 

1182 if isinstance(samples, int): 

1183 args.extend([b"SAMPLES", samples]) 

1184 return self.execute_command("MEMORY USAGE", key, *args, **kwargs) 

1185 

1186 def memory_purge(self, **kwargs) -> ResponseT: 

1187 """ 

1188 Attempts to purge dirty pages for reclamation by allocator 

1189 

1190 For more information, see https://redis.io/commands/memory-purge 

1191 """ 

1192 return self.execute_command("MEMORY PURGE", **kwargs) 

1193 

1194 def latency_histogram(self, *args): 

1195 """ 

1196 This function throws a NotImplementedError since it is intentionally 

1197 not supported. 

1198 """ 

1199 raise NotImplementedError( 

1200 "LATENCY HISTOGRAM is intentionally not implemented in the client." 

1201 ) 

1202 

1203 def latency_history(self, event: str) -> ResponseT: 

1204 """ 

1205 Returns the raw data of the ``event``'s latency spikes time series. 

1206 

1207 For more information, see https://redis.io/commands/latency-history 

1208 """ 

1209 return self.execute_command("LATENCY HISTORY", event) 

1210 

1211 def latency_latest(self) -> ResponseT: 

1212 """ 

1213 Reports the latest latency events logged. 

1214 

1215 For more information, see https://redis.io/commands/latency-latest 

1216 """ 

1217 return self.execute_command("LATENCY LATEST") 

1218 

1219 def latency_reset(self, *events: str) -> ResponseT: 

1220 """ 

1221 Resets the latency spikes time series of all, or only some, events. 

1222 

1223 For more information, see https://redis.io/commands/latency-reset 

1224 """ 

1225 return self.execute_command("LATENCY RESET", *events) 

1226 

1227 def ping(self, **kwargs) -> Union[Awaitable[bool], bool]: 

1228 """ 

1229 Ping the Redis server to test connectivity. 

1230 

1231 Sends a PING command to the Redis server and returns True if the server 

1232 responds with "PONG". 

1233 

1234 This command is useful for: 

1235 - Testing whether a connection is still alive 

1236 - Verifying the server's ability to serve data 

1237 

1238 For more information on the underlying ping command see https://redis.io/commands/ping 

1239 """ 

1240 return self.execute_command("PING", **kwargs) 

1241 

1242 def quit(self, **kwargs) -> ResponseT: 

1243 """ 

1244 Ask the server to close the connection. 

1245 

1246 For more information, see https://redis.io/commands/quit 

1247 """ 

1248 return self.execute_command("QUIT", **kwargs) 

1249 

1250 def replicaof(self, *args, **kwargs) -> ResponseT: 

1251 """ 

1252 Update the replication settings of a redis replica, on the fly. 

1253 

1254 Examples of valid arguments include: 

1255 

1256 NO ONE (set no replication) 

1257 host port (set to the host and port of a redis server) 

1258 

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

1260 """ 

1261 return self.execute_command("REPLICAOF", *args, **kwargs) 

1262 

1263 def save(self, **kwargs) -> ResponseT: 

1264 """ 

1265 Tell the Redis server to save its data to disk, 

1266 blocking until the save is complete 

1267 

1268 For more information, see https://redis.io/commands/save 

1269 """ 

1270 return self.execute_command("SAVE", **kwargs) 

1271 

1272 def shutdown( 

1273 self, 

1274 save: bool = False, 

1275 nosave: bool = False, 

1276 now: bool = False, 

1277 force: bool = False, 

1278 abort: bool = False, 

1279 **kwargs, 

1280 ) -> None: 

1281 """Shutdown the Redis server. If Redis has persistence configured, 

1282 data will be flushed before shutdown. 

1283 It is possible to specify modifiers to alter the behavior of the command: 

1284 ``save`` will force a DB saving operation even if no save points are configured. 

1285 ``nosave`` will prevent a DB saving operation even if one or more save points 

1286 are configured. 

1287 ``now`` skips waiting for lagging replicas, i.e. it bypasses the first step in 

1288 the shutdown sequence. 

1289 ``force`` ignores any errors that would normally prevent the server from exiting 

1290 ``abort`` cancels an ongoing shutdown and cannot be combined with other flags. 

1291 

1292 For more information, see https://redis.io/commands/shutdown 

1293 """ 

1294 if save and nosave: 

1295 raise DataError("SHUTDOWN save and nosave cannot both be set") 

1296 args = ["SHUTDOWN"] 

1297 if save: 

1298 args.append("SAVE") 

1299 if nosave: 

1300 args.append("NOSAVE") 

1301 if now: 

1302 args.append("NOW") 

1303 if force: 

1304 args.append("FORCE") 

1305 if abort: 

1306 args.append("ABORT") 

1307 try: 

1308 self.execute_command(*args, **kwargs) 

1309 except ConnectionError: 

1310 # a ConnectionError here is expected 

1311 return 

1312 raise RedisError("SHUTDOWN seems to have failed.") 

1313 

1314 def slaveof( 

1315 self, host: Optional[str] = None, port: Optional[int] = None, **kwargs 

1316 ) -> ResponseT: 

1317 """ 

1318 Set the server to be a replicated slave of the instance identified 

1319 by the ``host`` and ``port``. If called without arguments, the 

1320 instance is promoted to a master instead. 

1321 

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

1323 """ 

1324 if host is None and port is None: 

1325 return self.execute_command("SLAVEOF", b"NO", b"ONE", **kwargs) 

1326 return self.execute_command("SLAVEOF", host, port, **kwargs) 

1327 

1328 def slowlog_get(self, num: Optional[int] = None, **kwargs) -> ResponseT: 

1329 """ 

1330 Get the entries from the slowlog. If ``num`` is specified, get the 

1331 most recent ``num`` items. 

1332 

1333 For more information, see https://redis.io/commands/slowlog-get 

1334 """ 

1335 from redis.client import NEVER_DECODE 

1336 

1337 args = ["SLOWLOG GET"] 

1338 if num is not None: 

1339 args.append(num) 

1340 decode_responses = self.get_connection_kwargs().get("decode_responses", False) 

1341 if decode_responses is True: 

1342 kwargs[NEVER_DECODE] = [] 

1343 return self.execute_command(*args, **kwargs) 

1344 

1345 def slowlog_len(self, **kwargs) -> ResponseT: 

1346 """ 

1347 Get the number of items in the slowlog 

1348 

1349 For more information, see https://redis.io/commands/slowlog-len 

1350 """ 

1351 return self.execute_command("SLOWLOG LEN", **kwargs) 

1352 

1353 def slowlog_reset(self, **kwargs) -> ResponseT: 

1354 """ 

1355 Remove all items in the slowlog 

1356 

1357 For more information, see https://redis.io/commands/slowlog-reset 

1358 """ 

1359 return self.execute_command("SLOWLOG RESET", **kwargs) 

1360 

1361 def time(self, **kwargs) -> ResponseT: 

1362 """ 

1363 Returns the server time as a 2-item tuple of ints: 

1364 (seconds since epoch, microseconds into this second). 

1365 

1366 For more information, see https://redis.io/commands/time 

1367 """ 

1368 return self.execute_command("TIME", **kwargs) 

1369 

1370 def wait(self, num_replicas: int, timeout: int, **kwargs) -> ResponseT: 

1371 """ 

1372 Redis synchronous replication 

1373 That returns the number of replicas that processed the query when 

1374 we finally have at least ``num_replicas``, or when the ``timeout`` was 

1375 reached. 

1376 

1377 For more information, see https://redis.io/commands/wait 

1378 """ 

1379 return self.execute_command("WAIT", num_replicas, timeout, **kwargs) 

1380 

1381 def waitaof( 

1382 self, num_local: int, num_replicas: int, timeout: int, **kwargs 

1383 ) -> ResponseT: 

1384 """ 

1385 This command blocks the current client until all previous write 

1386 commands by that client are acknowledged as having been fsynced 

1387 to the AOF of the local Redis and/or at least the specified number 

1388 of replicas. 

1389 

1390 For more information, see https://redis.io/commands/waitaof 

1391 """ 

1392 return self.execute_command( 

1393 "WAITAOF", num_local, num_replicas, timeout, **kwargs 

1394 ) 

1395 

1396 def hello(self): 

1397 """ 

1398 This function throws a NotImplementedError since it is intentionally 

1399 not supported. 

1400 """ 

1401 raise NotImplementedError( 

1402 "HELLO is intentionally not implemented in the client." 

1403 ) 

1404 

1405 def failover(self): 

1406 """ 

1407 This function throws a NotImplementedError since it is intentionally 

1408 not supported. 

1409 """ 

1410 raise NotImplementedError( 

1411 "FAILOVER is intentionally not implemented in the client." 

1412 ) 

1413 

1414 def hotkeys_start( 

1415 self, 

1416 metrics: List[HotkeysMetricsTypes], 

1417 count: Optional[int] = None, 

1418 duration: Optional[int] = None, 

1419 sample_ratio: Optional[int] = None, 

1420 slots: Optional[List[int]] = None, 

1421 **kwargs, 

1422 ) -> Union[Awaitable[Union[str, bytes]], Union[str, bytes]]: 

1423 """ 

1424 Start collecting hotkeys data. 

1425 Returns an error if there is an ongoing collection session. 

1426 

1427 Args: 

1428 count: The number of keys to collect in each criteria (CPU and network consumption) 

1429 metrics: List of metrics to track. Supported values: [HotkeysMetricsTypes.CPU, HotkeysMetricsTypes.NET] 

1430 duration: Automatically stop the collection after `duration` seconds 

1431 sample_ratio: Commands are sampled with probability 1/ratio (1 means no sampling) 

1432 slots: Only track keys on the specified hash slots 

1433 

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

1435 """ 

1436 args: List[Union[str, int]] = ["HOTKEYS", "START"] 

1437 

1438 # Add METRICS 

1439 args.extend(["METRICS", len(metrics)]) 

1440 args.extend([str(m.value) for m in metrics]) 

1441 

1442 # Add COUNT 

1443 if count is not None: 

1444 args.extend(["COUNT", count]) 

1445 

1446 # Add optional DURATION 

1447 if duration is not None: 

1448 args.extend(["DURATION", duration]) 

1449 

1450 # Add optional SAMPLE ratio 

1451 if sample_ratio is not None: 

1452 args.extend(["SAMPLE", sample_ratio]) 

1453 

1454 # Add optional SLOTS 

1455 if slots is not None: 

1456 args.append("SLOTS") 

1457 args.append(len(slots)) 

1458 args.extend(slots) 

1459 

1460 return self.execute_command(*args, **kwargs) 

1461 

1462 def hotkeys_stop( 

1463 self, **kwargs 

1464 ) -> Union[Awaitable[Union[str, bytes]], Union[str, bytes]]: 

1465 """ 

1466 Stop the ongoing hotkeys collection session (if any). 

1467 The results of the last collection session are kept for consumption with HOTKEYS GET. 

1468 

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

1470 """ 

1471 return self.execute_command("HOTKEYS STOP", **kwargs) 

1472 

1473 def hotkeys_reset( 

1474 self, **kwargs 

1475 ) -> Union[Awaitable[Union[str, bytes]], Union[str, bytes]]: 

1476 """ 

1477 Discard the last hotkeys collection session results (in order to save memory). 

1478 Error if there is an ongoing collection session. 

1479 

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

1481 """ 

1482 return self.execute_command("HOTKEYS RESET", **kwargs) 

1483 

1484 def hotkeys_get( 

1485 self, **kwargs 

1486 ) -> Union[ 

1487 Awaitable[list[dict[Union[str, bytes], Any]]], 

1488 list[dict[Union[str, bytes], Any]], 

1489 ]: 

1490 """ 

1491 Retrieve the result of the ongoing collection session (if any), 

1492 or the last collection session (if any). 

1493 

1494 HOTKEYS GET response is wrapped in an array for aggregation support. 

1495 Each node returns a single-element array, allowing multiple node 

1496 responses to be concatenated by DMC or other aggregators. 

1497 

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

1499 """ 

1500 return self.execute_command("HOTKEYS GET", **kwargs) 

1501 

1502 

1503class AsyncManagementCommands(ManagementCommands): 

1504 async def command_info(self, **kwargs) -> None: 

1505 return super().command_info(**kwargs) 

1506 

1507 async def debug_segfault(self, **kwargs) -> None: 

1508 return super().debug_segfault(**kwargs) 

1509 

1510 async def memory_doctor(self, **kwargs) -> None: 

1511 return super().memory_doctor(**kwargs) 

1512 

1513 async def memory_help(self, **kwargs) -> None: 

1514 return super().memory_help(**kwargs) 

1515 

1516 async def shutdown( 

1517 self, 

1518 save: bool = False, 

1519 nosave: bool = False, 

1520 now: bool = False, 

1521 force: bool = False, 

1522 abort: bool = False, 

1523 **kwargs, 

1524 ) -> None: 

1525 """Shutdown the Redis server. If Redis has persistence configured, 

1526 data will be flushed before shutdown. If the "save" option is set, 

1527 a data flush will be attempted even if there is no persistence 

1528 configured. If the "nosave" option is set, no data flush will be 

1529 attempted. The "save" and "nosave" options cannot both be set. 

1530 

1531 For more information, see https://redis.io/commands/shutdown 

1532 """ 

1533 if save and nosave: 

1534 raise DataError("SHUTDOWN save and nosave cannot both be set") 

1535 args = ["SHUTDOWN"] 

1536 if save: 

1537 args.append("SAVE") 

1538 if nosave: 

1539 args.append("NOSAVE") 

1540 if now: 

1541 args.append("NOW") 

1542 if force: 

1543 args.append("FORCE") 

1544 if abort: 

1545 args.append("ABORT") 

1546 try: 

1547 await self.execute_command(*args, **kwargs) 

1548 except ConnectionError: 

1549 # a ConnectionError here is expected 

1550 return 

1551 raise RedisError("SHUTDOWN seems to have failed.") 

1552 

1553 

1554class BitFieldOperation: 

1555 """ 

1556 Command builder for BITFIELD commands. 

1557 """ 

1558 

1559 def __init__( 

1560 self, 

1561 client: Union["redis.client.Redis", "redis.asyncio.client.Redis"], 

1562 key: str, 

1563 default_overflow: Optional[str] = None, 

1564 ): 

1565 self.client = client 

1566 self.key = key 

1567 self._default_overflow = default_overflow 

1568 # for typing purposes, run the following in constructor and in reset() 

1569 self.operations: list[tuple[EncodableT, ...]] = [] 

1570 self._last_overflow = "WRAP" 

1571 self.reset() 

1572 

1573 def reset(self): 

1574 """ 

1575 Reset the state of the instance to when it was constructed 

1576 """ 

1577 self.operations = [] 

1578 self._last_overflow = "WRAP" 

1579 self.overflow(self._default_overflow or self._last_overflow) 

1580 

1581 def overflow(self, overflow: str): 

1582 """ 

1583 Update the overflow algorithm of successive INCRBY operations 

1584 :param overflow: Overflow algorithm, one of WRAP, SAT, FAIL. See the 

1585 Redis docs for descriptions of these algorithmsself. 

1586 :returns: a :py:class:`BitFieldOperation` instance. 

1587 """ 

1588 overflow = overflow.upper() 

1589 if overflow != self._last_overflow: 

1590 self._last_overflow = overflow 

1591 self.operations.append(("OVERFLOW", overflow)) 

1592 return self 

1593 

1594 def incrby( 

1595 self, 

1596 fmt: str, 

1597 offset: BitfieldOffsetT, 

1598 increment: int, 

1599 overflow: Optional[str] = None, 

1600 ): 

1601 """ 

1602 Increment a bitfield by a given amount. 

1603 :param fmt: format-string for the bitfield being updated, e.g. 'u8' 

1604 for an unsigned 8-bit integer. 

1605 :param offset: offset (in number of bits). If prefixed with a 

1606 '#', this is an offset multiplier, e.g. given the arguments 

1607 fmt='u8', offset='#2', the offset will be 16. 

1608 :param int increment: value to increment the bitfield by. 

1609 :param str overflow: overflow algorithm. Defaults to WRAP, but other 

1610 acceptable values are SAT and FAIL. See the Redis docs for 

1611 descriptions of these algorithms. 

1612 :returns: a :py:class:`BitFieldOperation` instance. 

1613 """ 

1614 if overflow is not None: 

1615 self.overflow(overflow) 

1616 

1617 self.operations.append(("INCRBY", fmt, offset, increment)) 

1618 return self 

1619 

1620 def get(self, fmt: str, offset: BitfieldOffsetT): 

1621 """ 

1622 Get the value of a given bitfield. 

1623 :param fmt: format-string for the bitfield being read, e.g. 'u8' for 

1624 an unsigned 8-bit integer. 

1625 :param offset: offset (in number of bits). If prefixed with a 

1626 '#', this is an offset multiplier, e.g. given the arguments 

1627 fmt='u8', offset='#2', the offset will be 16. 

1628 :returns: a :py:class:`BitFieldOperation` instance. 

1629 """ 

1630 self.operations.append(("GET", fmt, offset)) 

1631 return self 

1632 

1633 def set(self, fmt: str, offset: BitfieldOffsetT, value: int): 

1634 """ 

1635 Set the value of a given bitfield. 

1636 :param fmt: format-string for the bitfield being read, e.g. 'u8' for 

1637 an unsigned 8-bit integer. 

1638 :param offset: offset (in number of bits). If prefixed with a 

1639 '#', this is an offset multiplier, e.g. given the arguments 

1640 fmt='u8', offset='#2', the offset will be 16. 

1641 :param int value: value to set at the given position. 

1642 :returns: a :py:class:`BitFieldOperation` instance. 

1643 """ 

1644 self.operations.append(("SET", fmt, offset, value)) 

1645 return self 

1646 

1647 @property 

1648 def command(self): 

1649 cmd = ["BITFIELD", self.key] 

1650 for ops in self.operations: 

1651 cmd.extend(ops) 

1652 return cmd 

1653 

1654 def execute(self) -> ResponseT: 

1655 """ 

1656 Execute the operation(s) in a single BITFIELD command. The return value 

1657 is a list of values corresponding to each operation. If the client 

1658 used to create this instance was a pipeline, the list of values 

1659 will be present within the pipeline's execute. 

1660 """ 

1661 command = self.command 

1662 self.reset() 

1663 return self.client.execute_command(*command) 

1664 

1665 

1666class DataPersistOptions(Enum): 

1667 # set the value for each provided key to each 

1668 # provided value only if all do not already exist. 

1669 NX = "NX" 

1670 

1671 # set the value for each provided key to each 

1672 # provided value only if all already exist. 

1673 XX = "XX" 

1674 

1675 

1676class BasicKeyCommands(CommandsProtocol): 

1677 """ 

1678 Redis basic key-based commands 

1679 """ 

1680 

1681 def append(self, key: KeyT, value: EncodableT) -> ResponseT: 

1682 """ 

1683 Appends the string ``value`` to the value at ``key``. If ``key`` 

1684 doesn't already exist, create it with a value of ``value``. 

1685 Returns the new length of the value at ``key``. 

1686 

1687 For more information, see https://redis.io/commands/append 

1688 """ 

1689 return self.execute_command("APPEND", key, value) 

1690 

1691 def bitcount( 

1692 self, 

1693 key: KeyT, 

1694 start: Optional[int] = None, 

1695 end: Optional[int] = None, 

1696 mode: Optional[str] = None, 

1697 ) -> ResponseT: 

1698 """ 

1699 Returns the count of set bits in the value of ``key``. Optional 

1700 ``start`` and ``end`` parameters indicate which bytes to consider 

1701 

1702 For more information, see https://redis.io/commands/bitcount 

1703 """ 

1704 params = [key] 

1705 if start is not None and end is not None: 

1706 params.append(start) 

1707 params.append(end) 

1708 elif (start is not None and end is None) or (end is not None and start is None): 

1709 raise DataError("Both start and end must be specified") 

1710 if mode is not None: 

1711 params.append(mode) 

1712 return self.execute_command("BITCOUNT", *params, keys=[key]) 

1713 

1714 def bitfield( 

1715 self: Union["redis.client.Redis", "redis.asyncio.client.Redis"], 

1716 key: KeyT, 

1717 default_overflow: Optional[str] = None, 

1718 ) -> BitFieldOperation: 

1719 """ 

1720 Return a BitFieldOperation instance to conveniently construct one or 

1721 more bitfield operations on ``key``. 

1722 

1723 For more information, see https://redis.io/commands/bitfield 

1724 """ 

1725 return BitFieldOperation(self, key, default_overflow=default_overflow) 

1726 

1727 def bitfield_ro( 

1728 self: Union["redis.client.Redis", "redis.asyncio.client.Redis"], 

1729 key: KeyT, 

1730 encoding: str, 

1731 offset: BitfieldOffsetT, 

1732 items: Optional[list] = None, 

1733 ) -> ResponseT: 

1734 """ 

1735 Return an array of the specified bitfield values 

1736 where the first value is found using ``encoding`` and ``offset`` 

1737 parameters and remaining values are result of corresponding 

1738 encoding/offset pairs in optional list ``items`` 

1739 Read-only variant of the BITFIELD command. 

1740 

1741 For more information, see https://redis.io/commands/bitfield_ro 

1742 """ 

1743 params = [key, "GET", encoding, offset] 

1744 

1745 items = items or [] 

1746 for encoding, offset in items: 

1747 params.extend(["GET", encoding, offset]) 

1748 return self.execute_command("BITFIELD_RO", *params, keys=[key]) 

1749 

1750 def bitop(self, operation: str, dest: KeyT, *keys: KeyT) -> ResponseT: 

1751 """ 

1752 Perform a bitwise operation using ``operation`` between ``keys`` and 

1753 store the result in ``dest``. 

1754 

1755 For more information, see https://redis.io/commands/bitop 

1756 """ 

1757 return self.execute_command("BITOP", operation, dest, *keys) 

1758 

1759 def bitpos( 

1760 self, 

1761 key: KeyT, 

1762 bit: int, 

1763 start: Optional[int] = None, 

1764 end: Optional[int] = None, 

1765 mode: Optional[str] = None, 

1766 ) -> ResponseT: 

1767 """ 

1768 Return the position of the first bit set to 1 or 0 in a string. 

1769 ``start`` and ``end`` defines search range. The range is interpreted 

1770 as a range of bytes and not a range of bits, so start=0 and end=2 

1771 means to look at the first three bytes. 

1772 

1773 For more information, see https://redis.io/commands/bitpos 

1774 """ 

1775 if bit not in (0, 1): 

1776 raise DataError("bit must be 0 or 1") 

1777 params = [key, bit] 

1778 

1779 start is not None and params.append(start) 

1780 

1781 if start is not None and end is not None: 

1782 params.append(end) 

1783 elif start is None and end is not None: 

1784 raise DataError("start argument is not set, when end is specified") 

1785 

1786 if mode is not None: 

1787 params.append(mode) 

1788 return self.execute_command("BITPOS", *params, keys=[key]) 

1789 

1790 def copy( 

1791 self, 

1792 source: str, 

1793 destination: str, 

1794 destination_db: Optional[str] = None, 

1795 replace: bool = False, 

1796 ) -> ResponseT: 

1797 """ 

1798 Copy the value stored in the ``source`` key to the ``destination`` key. 

1799 

1800 ``destination_db`` an alternative destination database. By default, 

1801 the ``destination`` key is created in the source Redis database. 

1802 

1803 ``replace`` whether the ``destination`` key should be removed before 

1804 copying the value to it. By default, the value is not copied if 

1805 the ``destination`` key already exists. 

1806 

1807 For more information, see https://redis.io/commands/copy 

1808 """ 

1809 params = [source, destination] 

1810 if destination_db is not None: 

1811 params.extend(["DB", destination_db]) 

1812 if replace: 

1813 params.append("REPLACE") 

1814 return self.execute_command("COPY", *params) 

1815 

1816 def decrby(self, name: KeyT, amount: int = 1) -> ResponseT: 

1817 """ 

1818 Decrements the value of ``key`` by ``amount``. If no key exists, 

1819 the value will be initialized as 0 - ``amount`` 

1820 

1821 For more information, see https://redis.io/commands/decrby 

1822 """ 

1823 return self.execute_command("DECRBY", name, amount) 

1824 

1825 decr = decrby 

1826 

1827 def delete(self, *names: KeyT) -> ResponseT: 

1828 """ 

1829 Delete one or more keys specified by ``names`` 

1830 """ 

1831 return self.execute_command("DEL", *names) 

1832 

1833 def __delitem__(self, name: KeyT): 

1834 self.delete(name) 

1835 

1836 @experimental_method() 

1837 def delex( 

1838 self, 

1839 name: KeyT, 

1840 ifeq: Optional[Union[bytes, str]] = None, 

1841 ifne: Optional[Union[bytes, str]] = None, 

1842 ifdeq: Optional[str] = None, # hex digest 

1843 ifdne: Optional[str] = None, # hex digest 

1844 ) -> int: 

1845 """ 

1846 Conditionally removes the specified key. 

1847 

1848 Warning: 

1849 **Experimental** since 7.1. 

1850 This API may change or be removed without notice. 

1851 The API may change based on feedback. 

1852 

1853 Arguments: 

1854 name: KeyT - the key to delete 

1855 ifeq match-valu: Optional[Union[bytes, str]] - Delete the key only if its value is equal to match-value 

1856 ifne match-value: Optional[Union[bytes, str]] - Delete the key only if its value is not equal to match-value 

1857 ifdeq match-digest: Optional[str] - Delete the key only if the digest of its value is equal to match-digest 

1858 ifdne match-digest: Optional[str] - Delete the key only if the digest of its value is not equal to match-digest 

1859 

1860 Returns: 

1861 int: 1 if the key was deleted, 0 otherwise. 

1862 Raises: 

1863 redis.exceptions.ResponseError: if key exists but is not a string 

1864 and a condition is specified. 

1865 ValueError: if more than one condition is provided. 

1866 

1867 

1868 Requires Redis 8.4 or greater. 

1869 For more information, see https://redis.io/commands/delex 

1870 """ 

1871 conds = [x is not None for x in (ifeq, ifne, ifdeq, ifdne)] 

1872 if sum(conds) > 1: 

1873 raise ValueError("Only one of IFEQ/IFNE/IFDEQ/IFDNE may be specified") 

1874 

1875 pieces = ["DELEX", name] 

1876 if ifeq is not None: 

1877 pieces += ["IFEQ", ifeq] 

1878 elif ifne is not None: 

1879 pieces += ["IFNE", ifne] 

1880 elif ifdeq is not None: 

1881 pieces += ["IFDEQ", ifdeq] 

1882 elif ifdne is not None: 

1883 pieces += ["IFDNE", ifdne] 

1884 

1885 return self.execute_command(*pieces) 

1886 

1887 def dump(self, name: KeyT) -> ResponseT: 

1888 """ 

1889 Return a serialized version of the value stored at the specified key. 

1890 If key does not exist a nil bulk reply is returned. 

1891 

1892 For more information, see https://redis.io/commands/dump 

1893 """ 

1894 from redis.client import NEVER_DECODE 

1895 

1896 options = {} 

1897 options[NEVER_DECODE] = [] 

1898 return self.execute_command("DUMP", name, **options) 

1899 

1900 def exists(self, *names: KeyT) -> ResponseT: 

1901 """ 

1902 Returns the number of ``names`` that exist 

1903 

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

1905 """ 

1906 return self.execute_command("EXISTS", *names, keys=names) 

1907 

1908 __contains__ = exists 

1909 

1910 def expire( 

1911 self, 

1912 name: KeyT, 

1913 time: ExpiryT, 

1914 nx: bool = False, 

1915 xx: bool = False, 

1916 gt: bool = False, 

1917 lt: bool = False, 

1918 ) -> ResponseT: 

1919 """ 

1920 Set an expire flag on key ``name`` for ``time`` seconds with given 

1921 ``option``. ``time`` can be represented by an integer or a Python timedelta 

1922 object. 

1923 

1924 Valid options are: 

1925 NX -> Set expiry only when the key has no expiry 

1926 XX -> Set expiry only when the key has an existing expiry 

1927 GT -> Set expiry only when the new expiry is greater than current one 

1928 LT -> Set expiry only when the new expiry is less than current one 

1929 

1930 For more information, see https://redis.io/commands/expire 

1931 """ 

1932 if isinstance(time, datetime.timedelta): 

1933 time = int(time.total_seconds()) 

1934 

1935 exp_option = list() 

1936 if nx: 

1937 exp_option.append("NX") 

1938 if xx: 

1939 exp_option.append("XX") 

1940 if gt: 

1941 exp_option.append("GT") 

1942 if lt: 

1943 exp_option.append("LT") 

1944 

1945 return self.execute_command("EXPIRE", name, time, *exp_option) 

1946 

1947 def expireat( 

1948 self, 

1949 name: KeyT, 

1950 when: AbsExpiryT, 

1951 nx: bool = False, 

1952 xx: bool = False, 

1953 gt: bool = False, 

1954 lt: bool = False, 

1955 ) -> ResponseT: 

1956 """ 

1957 Set an expire flag on key ``name`` with given ``option``. ``when`` 

1958 can be represented as an integer indicating unix time or a Python 

1959 datetime object. 

1960 

1961 Valid options are: 

1962 -> NX -- Set expiry only when the key has no expiry 

1963 -> XX -- Set expiry only when the key has an existing expiry 

1964 -> GT -- Set expiry only when the new expiry is greater than current one 

1965 -> LT -- Set expiry only when the new expiry is less than current one 

1966 

1967 For more information, see https://redis.io/commands/expireat 

1968 """ 

1969 if isinstance(when, datetime.datetime): 

1970 when = int(when.timestamp()) 

1971 

1972 exp_option = list() 

1973 if nx: 

1974 exp_option.append("NX") 

1975 if xx: 

1976 exp_option.append("XX") 

1977 if gt: 

1978 exp_option.append("GT") 

1979 if lt: 

1980 exp_option.append("LT") 

1981 

1982 return self.execute_command("EXPIREAT", name, when, *exp_option) 

1983 

1984 def expiretime(self, key: str) -> int: 

1985 """ 

1986 Returns the absolute Unix timestamp (since January 1, 1970) in seconds 

1987 at which the given key will expire. 

1988 

1989 For more information, see https://redis.io/commands/expiretime 

1990 """ 

1991 return self.execute_command("EXPIRETIME", key) 

1992 

1993 @experimental_method() 

1994 def digest_local(self, value: Union[bytes, str]) -> Union[bytes, str]: 

1995 """ 

1996 Compute the hexadecimal digest of the value locally, without sending it to the server. 

1997 

1998 This is useful for conditional operations like IFDEQ/IFDNE where you need to 

1999 compute the digest client-side before sending a command. 

2000 

2001 Warning: 

2002 **Experimental** - This API may change or be removed without notice. 

2003 

2004 Arguments: 

2005 - value: Union[bytes, str] - the value to compute the digest of. 

2006 If a string is provided, it will be encoded using UTF-8 before hashing, 

2007 which matches Redis's default encoding behavior. 

2008 

2009 Returns: 

2010 - (str | bytes) the XXH3 digest of the value as a hex string (16 hex characters). 

2011 Returns bytes if decode_responses is False, otherwise returns str. 

2012 

2013 For more information, see https://redis.io/commands/digest 

2014 """ 

2015 if not HAS_XXHASH: 

2016 raise NotImplementedError( 

2017 "XXHASH support requires the optional 'xxhash' library. " 

2018 "Install it with 'pip install xxhash' or use this package's extra with " 

2019 "'pip install redis[xxhash]' to enable this feature." 

2020 ) 

2021 

2022 local_digest = xxhash.xxh3_64(value).hexdigest() 

2023 

2024 # To align with digest, we want to return bytes if decode_responses is False. 

2025 # The following works because of Python's mixin-based client class hierarchy. 

2026 if not self.get_encoder().decode_responses: 

2027 local_digest = local_digest.encode() 

2028 

2029 return local_digest 

2030 

2031 @experimental_method() 

2032 def digest(self, name: KeyT) -> Union[str, bytes, None]: 

2033 """ 

2034 Return the digest of the value stored at the specified key. 

2035 

2036 Warning: 

2037 **Experimental** since 7.1. 

2038 This API may change or be removed without notice. 

2039 The API may change based on feedback. 

2040 

2041 Arguments: 

2042 - name: KeyT - the key to get the digest of 

2043 

2044 Returns: 

2045 - None if the key does not exist 

2046 - (bulk string) the XXH3 digest of the value as a hex string 

2047 Raises: 

2048 - ResponseError if key exists but is not a string 

2049 

2050 

2051 Requires Redis 8.4 or greater. 

2052 For more information, see https://redis.io/commands/digest 

2053 """ 

2054 # Bulk string response is already handled (bytes/str based on decode_responses) 

2055 return self.execute_command("DIGEST", name) 

2056 

2057 def get(self, name: KeyT) -> ResponseT: 

2058 """ 

2059 Return the value at key ``name``, or None if the key doesn't exist 

2060 

2061 For more information, see https://redis.io/commands/get 

2062 """ 

2063 return self.execute_command("GET", name, keys=[name]) 

2064 

2065 def getdel(self, name: KeyT) -> ResponseT: 

2066 """ 

2067 Get the value at key ``name`` and delete the key. This command 

2068 is similar to GET, except for the fact that it also deletes 

2069 the key on success (if and only if the key's value type 

2070 is a string). 

2071 

2072 For more information, see https://redis.io/commands/getdel 

2073 """ 

2074 return self.execute_command("GETDEL", name) 

2075 

2076 def getex( 

2077 self, 

2078 name: KeyT, 

2079 ex: Optional[ExpiryT] = None, 

2080 px: Optional[ExpiryT] = None, 

2081 exat: Optional[AbsExpiryT] = None, 

2082 pxat: Optional[AbsExpiryT] = None, 

2083 persist: bool = False, 

2084 ) -> ResponseT: 

2085 """ 

2086 Get the value of key and optionally set its expiration. 

2087 GETEX is similar to GET, but is a write command with 

2088 additional options. All time parameters can be given as 

2089 datetime.timedelta or integers. 

2090 

2091 ``ex`` sets an expire flag on key ``name`` for ``ex`` seconds. 

2092 

2093 ``px`` sets an expire flag on key ``name`` for ``px`` milliseconds. 

2094 

2095 ``exat`` sets an expire flag on key ``name`` for ``ex`` seconds, 

2096 specified in unix time. 

2097 

2098 ``pxat`` sets an expire flag on key ``name`` for ``ex`` milliseconds, 

2099 specified in unix time. 

2100 

2101 ``persist`` remove the time to live associated with ``name``. 

2102 

2103 For more information, see https://redis.io/commands/getex 

2104 """ 

2105 if not at_most_one_value_set((ex, px, exat, pxat, persist)): 

2106 raise DataError( 

2107 "``ex``, ``px``, ``exat``, ``pxat``, " 

2108 "and ``persist`` are mutually exclusive." 

2109 ) 

2110 

2111 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat) 

2112 

2113 if persist: 

2114 exp_options.append("PERSIST") 

2115 

2116 return self.execute_command("GETEX", name, *exp_options) 

2117 

2118 def __getitem__(self, name: KeyT): 

2119 """ 

2120 Return the value at key ``name``, raises a KeyError if the key 

2121 doesn't exist. 

2122 """ 

2123 value = self.get(name) 

2124 if value is not None: 

2125 return value 

2126 raise KeyError(name) 

2127 

2128 def getbit(self, name: KeyT, offset: int) -> ResponseT: 

2129 """ 

2130 Returns an integer indicating the value of ``offset`` in ``name`` 

2131 

2132 For more information, see https://redis.io/commands/getbit 

2133 """ 

2134 return self.execute_command("GETBIT", name, offset, keys=[name]) 

2135 

2136 def getrange(self, key: KeyT, start: int, end: int) -> ResponseT: 

2137 """ 

2138 Returns the substring of the string value stored at ``key``, 

2139 determined by the offsets ``start`` and ``end`` (both are inclusive) 

2140 

2141 For more information, see https://redis.io/commands/getrange 

2142 """ 

2143 return self.execute_command("GETRANGE", key, start, end, keys=[key]) 

2144 

2145 def getset(self, name: KeyT, value: EncodableT) -> ResponseT: 

2146 """ 

2147 Sets the value at key ``name`` to ``value`` 

2148 and returns the old value at key ``name`` atomically. 

2149 

2150 As per Redis 6.2, GETSET is considered deprecated. 

2151 Please use SET with GET parameter in new code. 

2152 

2153 For more information, see https://redis.io/commands/getset 

2154 """ 

2155 return self.execute_command("GETSET", name, value) 

2156 

2157 def incrby(self, name: KeyT, amount: int = 1) -> ResponseT: 

2158 """ 

2159 Increments the value of ``key`` by ``amount``. If no key exists, 

2160 the value will be initialized as ``amount`` 

2161 

2162 For more information, see https://redis.io/commands/incrby 

2163 """ 

2164 return self.execute_command("INCRBY", name, amount) 

2165 

2166 incr = incrby 

2167 

2168 def incrbyfloat(self, name: KeyT, amount: float = 1.0) -> ResponseT: 

2169 """ 

2170 Increments the value at key ``name`` by floating ``amount``. 

2171 If no key exists, the value will be initialized as ``amount`` 

2172 

2173 For more information, see https://redis.io/commands/incrbyfloat 

2174 """ 

2175 return self.execute_command("INCRBYFLOAT", name, amount) 

2176 

2177 def keys(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

2178 """ 

2179 Returns a list of keys matching ``pattern`` 

2180 

2181 For more information, see https://redis.io/commands/keys 

2182 """ 

2183 return self.execute_command("KEYS", pattern, **kwargs) 

2184 

2185 def lmove( 

2186 self, first_list: str, second_list: str, src: str = "LEFT", dest: str = "RIGHT" 

2187 ) -> ResponseT: 

2188 """ 

2189 Atomically returns and removes the first/last element of a list, 

2190 pushing it as the first/last element on the destination list. 

2191 Returns the element being popped and pushed. 

2192 

2193 For more information, see https://redis.io/commands/lmove 

2194 """ 

2195 params = [first_list, second_list, src, dest] 

2196 return self.execute_command("LMOVE", *params) 

2197 

2198 def blmove( 

2199 self, 

2200 first_list: str, 

2201 second_list: str, 

2202 timeout: int, 

2203 src: str = "LEFT", 

2204 dest: str = "RIGHT", 

2205 ) -> ResponseT: 

2206 """ 

2207 Blocking version of lmove. 

2208 

2209 For more information, see https://redis.io/commands/blmove 

2210 """ 

2211 params = [first_list, second_list, src, dest, timeout] 

2212 return self.execute_command("BLMOVE", *params) 

2213 

2214 def mget(self, keys: KeysT, *args: EncodableT) -> ResponseT: 

2215 """ 

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

2217 

2218 ** Important ** When this method is used with Cluster clients, all keys 

2219 must be in the same hash slot, otherwise a RedisClusterException 

2220 will be raised. 

2221 

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

2223 """ 

2224 from redis.client import EMPTY_RESPONSE 

2225 

2226 args = list_or_args(keys, args) 

2227 options = {} 

2228 if not args: 

2229 options[EMPTY_RESPONSE] = [] 

2230 options["keys"] = args 

2231 return self.execute_command("MGET", *args, **options) 

2232 

2233 def mset(self, mapping: Mapping[AnyKeyT, EncodableT]) -> ResponseT: 

2234 """ 

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

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

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

2238 

2239 ** Important ** When this method is used with Cluster clients, all keys 

2240 must be in the same hash slot, otherwise a RedisClusterException 

2241 will be raised. 

2242 

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

2244 """ 

2245 items = [] 

2246 for pair in mapping.items(): 

2247 items.extend(pair) 

2248 return self.execute_command("MSET", *items) 

2249 

2250 def msetex( 

2251 self, 

2252 mapping: Mapping[AnyKeyT, EncodableT], 

2253 data_persist_option: Optional[DataPersistOptions] = None, 

2254 ex: Optional[ExpiryT] = None, 

2255 px: Optional[ExpiryT] = None, 

2256 exat: Optional[AbsExpiryT] = None, 

2257 pxat: Optional[AbsExpiryT] = None, 

2258 keepttl: bool = False, 

2259 ) -> Union[Awaitable[int], int]: 

2260 """ 

2261 Sets key/values based on the provided ``mapping`` items. 

2262 

2263 ** Important ** When this method is used with Cluster clients, all keys 

2264 must be in the same hash slot, otherwise a RedisClusterException 

2265 will be raised. 

2266 

2267 ``mapping`` accepts a dict of key/value pairs that will be added to the database. 

2268 

2269 ``data_persist_option`` can be set to ``NX`` or ``XX`` to control the 

2270 behavior of the command. 

2271 ``NX`` will set the value for each provided key to each 

2272 provided value only if all do not already exist. 

2273 ``XX`` will set the value for each provided key to each 

2274 provided value only if all already exist. 

2275 

2276 ``ex`` sets an expire flag on the keys in ``mapping`` for ``ex`` seconds. 

2277 

2278 ``px`` sets an expire flag on the keys in ``mapping`` for ``px`` milliseconds. 

2279 

2280 ``exat`` sets an expire flag on the keys in ``mapping`` for ``exat`` seconds, 

2281 specified in unix time. 

2282 

2283 ``pxat`` sets an expire flag on the keys in ``mapping`` for ``pxat`` milliseconds, 

2284 specified in unix time. 

2285 

2286 ``keepttl`` if True, retain the time to live associated with the keys. 

2287 

2288 Returns the number of fields that were added. 

2289 

2290 Available since Redis 8.4 

2291 For more information, see https://redis.io/commands/msetex 

2292 """ 

2293 if not at_most_one_value_set((ex, px, exat, pxat, keepttl)): 

2294 raise DataError( 

2295 "``ex``, ``px``, ``exat``, ``pxat``, " 

2296 "and ``keepttl`` are mutually exclusive." 

2297 ) 

2298 

2299 exp_options: list[EncodableT] = [] 

2300 if data_persist_option: 

2301 exp_options.append(data_persist_option.value) 

2302 

2303 exp_options.extend(extract_expire_flags(ex, px, exat, pxat)) 

2304 

2305 if keepttl: 

2306 exp_options.append("KEEPTTL") 

2307 

2308 pieces = ["MSETEX", len(mapping)] 

2309 

2310 for pair in mapping.items(): 

2311 pieces.extend(pair) 

2312 

2313 return self.execute_command(*pieces, *exp_options) 

2314 

2315 def msetnx(self, mapping: Mapping[AnyKeyT, EncodableT]) -> ResponseT: 

2316 """ 

2317 Sets key/values based on a mapping if none of the keys are already set. 

2318 Mapping is a dictionary of key/value pairs. Both keys and values 

2319 should be strings or types that can be cast to a string via str(). 

2320 Returns a boolean indicating if the operation was successful. 

2321 

2322 ** Important ** When this method is used with Cluster clients, all keys 

2323 must be in the same hash slot, otherwise a RedisClusterException 

2324 will be raised. 

2325 

2326 For more information, see https://redis.io/commands/msetnx 

2327 """ 

2328 items = [] 

2329 for pair in mapping.items(): 

2330 items.extend(pair) 

2331 return self.execute_command("MSETNX", *items) 

2332 

2333 def move(self, name: KeyT, db: int) -> ResponseT: 

2334 """ 

2335 Moves the key ``name`` to a different Redis database ``db`` 

2336 

2337 For more information, see https://redis.io/commands/move 

2338 """ 

2339 return self.execute_command("MOVE", name, db) 

2340 

2341 def persist(self, name: KeyT) -> ResponseT: 

2342 """ 

2343 Removes an expiration on ``name`` 

2344 

2345 For more information, see https://redis.io/commands/persist 

2346 """ 

2347 return self.execute_command("PERSIST", name) 

2348 

2349 def pexpire( 

2350 self, 

2351 name: KeyT, 

2352 time: ExpiryT, 

2353 nx: bool = False, 

2354 xx: bool = False, 

2355 gt: bool = False, 

2356 lt: bool = False, 

2357 ) -> ResponseT: 

2358 """ 

2359 Set an expire flag on key ``name`` for ``time`` milliseconds 

2360 with given ``option``. ``time`` can be represented by an 

2361 integer or a Python timedelta object. 

2362 

2363 Valid options are: 

2364 NX -> Set expiry only when the key has no expiry 

2365 XX -> Set expiry only when the key has an existing expiry 

2366 GT -> Set expiry only when the new expiry is greater than current one 

2367 LT -> Set expiry only when the new expiry is less than current one 

2368 

2369 For more information, see https://redis.io/commands/pexpire 

2370 """ 

2371 if isinstance(time, datetime.timedelta): 

2372 time = int(time.total_seconds() * 1000) 

2373 

2374 exp_option = list() 

2375 if nx: 

2376 exp_option.append("NX") 

2377 if xx: 

2378 exp_option.append("XX") 

2379 if gt: 

2380 exp_option.append("GT") 

2381 if lt: 

2382 exp_option.append("LT") 

2383 return self.execute_command("PEXPIRE", name, time, *exp_option) 

2384 

2385 def pexpireat( 

2386 self, 

2387 name: KeyT, 

2388 when: AbsExpiryT, 

2389 nx: bool = False, 

2390 xx: bool = False, 

2391 gt: bool = False, 

2392 lt: bool = False, 

2393 ) -> ResponseT: 

2394 """ 

2395 Set an expire flag on key ``name`` with given ``option``. ``when`` 

2396 can be represented as an integer representing unix time in 

2397 milliseconds (unix time * 1000) or a Python datetime object. 

2398 

2399 Valid options are: 

2400 NX -> Set expiry only when the key has no expiry 

2401 XX -> Set expiry only when the key has an existing expiry 

2402 GT -> Set expiry only when the new expiry is greater than current one 

2403 LT -> Set expiry only when the new expiry is less than current one 

2404 

2405 For more information, see https://redis.io/commands/pexpireat 

2406 """ 

2407 if isinstance(when, datetime.datetime): 

2408 when = int(when.timestamp() * 1000) 

2409 exp_option = list() 

2410 if nx: 

2411 exp_option.append("NX") 

2412 if xx: 

2413 exp_option.append("XX") 

2414 if gt: 

2415 exp_option.append("GT") 

2416 if lt: 

2417 exp_option.append("LT") 

2418 return self.execute_command("PEXPIREAT", name, when, *exp_option) 

2419 

2420 def pexpiretime(self, key: str) -> int: 

2421 """ 

2422 Returns the absolute Unix timestamp (since January 1, 1970) in milliseconds 

2423 at which the given key will expire. 

2424 

2425 For more information, see https://redis.io/commands/pexpiretime 

2426 """ 

2427 return self.execute_command("PEXPIRETIME", key) 

2428 

2429 def psetex(self, name: KeyT, time_ms: ExpiryT, value: EncodableT): 

2430 """ 

2431 Set the value of key ``name`` to ``value`` that expires in ``time_ms`` 

2432 milliseconds. ``time_ms`` can be represented by an integer or a Python 

2433 timedelta object 

2434 

2435 For more information, see https://redis.io/commands/psetex 

2436 """ 

2437 if isinstance(time_ms, datetime.timedelta): 

2438 time_ms = int(time_ms.total_seconds() * 1000) 

2439 return self.execute_command("PSETEX", name, time_ms, value) 

2440 

2441 def pttl(self, name: KeyT) -> ResponseT: 

2442 """ 

2443 Returns the number of milliseconds until the key ``name`` will expire 

2444 

2445 For more information, see https://redis.io/commands/pttl 

2446 """ 

2447 return self.execute_command("PTTL", name) 

2448 

2449 def hrandfield( 

2450 self, key: str, count: Optional[int] = None, withvalues: bool = False 

2451 ) -> ResponseT: 

2452 """ 

2453 Return a random field from the hash value stored at key. 

2454 

2455 count: if the argument is positive, return an array of distinct fields. 

2456 If called with a negative count, the behavior changes and the command 

2457 is allowed to return the same field multiple times. In this case, 

2458 the number of returned fields is the absolute value of the 

2459 specified count. 

2460 withvalues: The optional WITHVALUES modifier changes the reply so it 

2461 includes the respective values of the randomly selected hash fields. 

2462 

2463 For more information, see https://redis.io/commands/hrandfield 

2464 """ 

2465 params = [] 

2466 if count is not None: 

2467 params.append(count) 

2468 if withvalues: 

2469 params.append("WITHVALUES") 

2470 

2471 return self.execute_command("HRANDFIELD", key, *params) 

2472 

2473 def randomkey(self, **kwargs) -> ResponseT: 

2474 """ 

2475 Returns the name of a random key 

2476 

2477 For more information, see https://redis.io/commands/randomkey 

2478 """ 

2479 return self.execute_command("RANDOMKEY", **kwargs) 

2480 

2481 def rename(self, src: KeyT, dst: KeyT) -> ResponseT: 

2482 """ 

2483 Rename key ``src`` to ``dst`` 

2484 

2485 For more information, see https://redis.io/commands/rename 

2486 """ 

2487 return self.execute_command("RENAME", src, dst) 

2488 

2489 def renamenx(self, src: KeyT, dst: KeyT): 

2490 """ 

2491 Rename key ``src`` to ``dst`` if ``dst`` doesn't already exist 

2492 

2493 For more information, see https://redis.io/commands/renamenx 

2494 """ 

2495 return self.execute_command("RENAMENX", src, dst) 

2496 

2497 def restore( 

2498 self, 

2499 name: KeyT, 

2500 ttl: float, 

2501 value: EncodableT, 

2502 replace: bool = False, 

2503 absttl: bool = False, 

2504 idletime: Optional[int] = None, 

2505 frequency: Optional[int] = None, 

2506 ) -> ResponseT: 

2507 """ 

2508 Create a key using the provided serialized value, previously obtained 

2509 using DUMP. 

2510 

2511 ``replace`` allows an existing key on ``name`` to be overridden. If 

2512 it's not specified an error is raised on collision. 

2513 

2514 ``absttl`` if True, specified ``ttl`` should represent an absolute Unix 

2515 timestamp in milliseconds in which the key will expire. (Redis 5.0 or 

2516 greater). 

2517 

2518 ``idletime`` Used for eviction, this is the number of seconds the 

2519 key must be idle, prior to execution. 

2520 

2521 ``frequency`` Used for eviction, this is the frequency counter of 

2522 the object stored at the key, prior to execution. 

2523 

2524 For more information, see https://redis.io/commands/restore 

2525 """ 

2526 params = [name, ttl, value] 

2527 if replace: 

2528 params.append("REPLACE") 

2529 if absttl: 

2530 params.append("ABSTTL") 

2531 if idletime is not None: 

2532 params.append("IDLETIME") 

2533 try: 

2534 params.append(int(idletime)) 

2535 except ValueError: 

2536 raise DataError("idletimemust be an integer") 

2537 

2538 if frequency is not None: 

2539 params.append("FREQ") 

2540 try: 

2541 params.append(int(frequency)) 

2542 except ValueError: 

2543 raise DataError("frequency must be an integer") 

2544 

2545 return self.execute_command("RESTORE", *params) 

2546 

2547 @experimental_args(["ifeq", "ifne", "ifdeq", "ifdne"]) 

2548 def set( 

2549 self, 

2550 name: KeyT, 

2551 value: EncodableT, 

2552 ex: Optional[ExpiryT] = None, 

2553 px: Optional[ExpiryT] = None, 

2554 nx: bool = False, 

2555 xx: bool = False, 

2556 keepttl: bool = False, 

2557 get: bool = False, 

2558 exat: Optional[AbsExpiryT] = None, 

2559 pxat: Optional[AbsExpiryT] = None, 

2560 ifeq: Optional[Union[bytes, str]] = None, 

2561 ifne: Optional[Union[bytes, str]] = None, 

2562 ifdeq: Optional[str] = None, # hex digest of current value 

2563 ifdne: Optional[str] = None, # hex digest of current value 

2564 ) -> ResponseT: 

2565 """ 

2566 Set the value at key ``name`` to ``value`` 

2567 

2568 Warning: 

2569 **Experimental** since 7.1. 

2570 The usage of the arguments ``ifeq``, ``ifne``, ``ifdeq``, and ``ifdne`` 

2571 is experimental. The API or returned results when those parameters are used 

2572 may change based on feedback. 

2573 

2574 ``ex`` sets an expire flag on key ``name`` for ``ex`` seconds. 

2575 

2576 ``px`` sets an expire flag on key ``name`` for ``px`` milliseconds. 

2577 

2578 ``nx`` if set to True, set the value at key ``name`` to ``value`` only 

2579 if it does not exist. 

2580 

2581 ``xx`` if set to True, set the value at key ``name`` to ``value`` only 

2582 if it already exists. 

2583 

2584 ``keepttl`` if True, retain the time to live associated with the key. 

2585 (Available since Redis 6.0) 

2586 

2587 ``get`` if True, set the value at key ``name`` to ``value`` and return 

2588 the old value stored at key, or None if the key did not exist. 

2589 (Available since Redis 6.2) 

2590 

2591 ``exat`` sets an expire flag on key ``name`` for ``ex`` seconds, 

2592 specified in unix time. 

2593 

2594 ``pxat`` sets an expire flag on key ``name`` for ``ex`` milliseconds, 

2595 specified in unix time. 

2596 

2597 ``ifeq`` set the value at key ``name`` to ``value`` only if the current 

2598 value exactly matches the argument. 

2599 If key doesn’t exist - it won’t be created. 

2600 (Requires Redis 8.4 or greater) 

2601 

2602 ``ifne`` set the value at key ``name`` to ``value`` only if the current 

2603 value does not exactly match the argument. 

2604 If key doesn’t exist - it will be created. 

2605 (Requires Redis 8.4 or greater) 

2606 

2607 ``ifdeq`` set the value at key ``name`` to ``value`` only if the current 

2608 value XXH3 hex digest exactly matches the argument. 

2609 If key doesn’t exist - it won’t be created. 

2610 (Requires Redis 8.4 or greater) 

2611 

2612 ``ifdne`` set the value at key ``name`` to ``value`` only if the current 

2613 value XXH3 hex digest does not exactly match the argument. 

2614 If key doesn’t exist - it will be created. 

2615 (Requires Redis 8.4 or greater) 

2616 

2617 For more information, see https://redis.io/commands/set 

2618 """ 

2619 

2620 if not at_most_one_value_set((ex, px, exat, pxat, keepttl)): 

2621 raise DataError( 

2622 "``ex``, ``px``, ``exat``, ``pxat``, " 

2623 "and ``keepttl`` are mutually exclusive." 

2624 ) 

2625 

2626 # Enforce mutual exclusivity among all conditional switches. 

2627 if not at_most_one_value_set((nx, xx, ifeq, ifne, ifdeq, ifdne)): 

2628 raise DataError( 

2629 "``nx``, ``xx``, ``ifeq``, ``ifne``, ``ifdeq``, ``ifdne`` are mutually exclusive." 

2630 ) 

2631 

2632 pieces: list[EncodableT] = [name, value] 

2633 options = {} 

2634 

2635 # Conditional modifier (exactly one at most) 

2636 if nx: 

2637 pieces.append("NX") 

2638 elif xx: 

2639 pieces.append("XX") 

2640 elif ifeq is not None: 

2641 pieces.extend(("IFEQ", ifeq)) 

2642 elif ifne is not None: 

2643 pieces.extend(("IFNE", ifne)) 

2644 elif ifdeq is not None: 

2645 pieces.extend(("IFDEQ", ifdeq)) 

2646 elif ifdne is not None: 

2647 pieces.extend(("IFDNE", ifdne)) 

2648 

2649 if get: 

2650 pieces.append("GET") 

2651 options["get"] = True 

2652 

2653 pieces.extend(extract_expire_flags(ex, px, exat, pxat)) 

2654 

2655 if keepttl: 

2656 pieces.append("KEEPTTL") 

2657 

2658 return self.execute_command("SET", *pieces, **options) 

2659 

2660 def __setitem__(self, name: KeyT, value: EncodableT): 

2661 self.set(name, value) 

2662 

2663 def setbit(self, name: KeyT, offset: int, value: int) -> ResponseT: 

2664 """ 

2665 Flag the ``offset`` in ``name`` as ``value``. Returns an integer 

2666 indicating the previous value of ``offset``. 

2667 

2668 For more information, see https://redis.io/commands/setbit 

2669 """ 

2670 value = value and 1 or 0 

2671 return self.execute_command("SETBIT", name, offset, value) 

2672 

2673 def setex(self, name: KeyT, time: ExpiryT, value: EncodableT) -> ResponseT: 

2674 """ 

2675 Set the value of key ``name`` to ``value`` that expires in ``time`` 

2676 seconds. ``time`` can be represented by an integer or a Python 

2677 timedelta object. 

2678 

2679 For more information, see https://redis.io/commands/setex 

2680 """ 

2681 if isinstance(time, datetime.timedelta): 

2682 time = int(time.total_seconds()) 

2683 return self.execute_command("SETEX", name, time, value) 

2684 

2685 def setnx(self, name: KeyT, value: EncodableT) -> ResponseT: 

2686 """ 

2687 Set the value of key ``name`` to ``value`` if key doesn't exist 

2688 

2689 For more information, see https://redis.io/commands/setnx 

2690 """ 

2691 return self.execute_command("SETNX", name, value) 

2692 

2693 def setrange(self, name: KeyT, offset: int, value: EncodableT) -> ResponseT: 

2694 """ 

2695 Overwrite bytes in the value of ``name`` starting at ``offset`` with 

2696 ``value``. If ``offset`` plus the length of ``value`` exceeds the 

2697 length of the original value, the new value will be larger than before. 

2698 If ``offset`` exceeds the length of the original value, null bytes 

2699 will be used to pad between the end of the previous value and the start 

2700 of what's being injected. 

2701 

2702 Returns the length of the new string. 

2703 

2704 For more information, see https://redis.io/commands/setrange 

2705 """ 

2706 return self.execute_command("SETRANGE", name, offset, value) 

2707 

2708 def stralgo( 

2709 self, 

2710 algo: Literal["LCS"], 

2711 value1: KeyT, 

2712 value2: KeyT, 

2713 specific_argument: Union[Literal["strings"], Literal["keys"]] = "strings", 

2714 len: bool = False, 

2715 idx: bool = False, 

2716 minmatchlen: Optional[int] = None, 

2717 withmatchlen: bool = False, 

2718 **kwargs, 

2719 ) -> ResponseT: 

2720 """ 

2721 Implements complex algorithms that operate on strings. 

2722 Right now the only algorithm implemented is the LCS algorithm 

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

2724 implemented in the future. 

2725 

2726 ``algo`` Right now must be LCS 

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

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

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

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

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

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

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

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

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

2736 

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

2738 """ 

2739 # check validity 

2740 supported_algo = ["LCS"] 

2741 if algo not in supported_algo: 

2742 supported_algos_str = ", ".join(supported_algo) 

2743 raise DataError(f"The supported algorithms are: {supported_algos_str}") 

2744 if specific_argument not in ["keys", "strings"]: 

2745 raise DataError("specific_argument can be only keys or strings") 

2746 if len and idx: 

2747 raise DataError("len and idx cannot be provided together.") 

2748 

2749 pieces: list[EncodableT] = [algo, specific_argument.upper(), value1, value2] 

2750 if len: 

2751 pieces.append(b"LEN") 

2752 if idx: 

2753 pieces.append(b"IDX") 

2754 try: 

2755 int(minmatchlen) 

2756 pieces.extend([b"MINMATCHLEN", minmatchlen]) 

2757 except TypeError: 

2758 pass 

2759 if withmatchlen: 

2760 pieces.append(b"WITHMATCHLEN") 

2761 

2762 return self.execute_command( 

2763 "STRALGO", 

2764 *pieces, 

2765 len=len, 

2766 idx=idx, 

2767 minmatchlen=minmatchlen, 

2768 withmatchlen=withmatchlen, 

2769 **kwargs, 

2770 ) 

2771 

2772 def strlen(self, name: KeyT) -> ResponseT: 

2773 """ 

2774 Return the number of bytes stored in the value of ``name`` 

2775 

2776 For more information, see https://redis.io/commands/strlen 

2777 """ 

2778 return self.execute_command("STRLEN", name, keys=[name]) 

2779 

2780 def substr(self, name: KeyT, start: int, end: int = -1) -> ResponseT: 

2781 """ 

2782 Return a substring of the string at key ``name``. ``start`` and ``end`` 

2783 are 0-based integers specifying the portion of the string to return. 

2784 """ 

2785 return self.execute_command("SUBSTR", name, start, end, keys=[name]) 

2786 

2787 def touch(self, *args: KeyT) -> ResponseT: 

2788 """ 

2789 Alters the last access time of a key(s) ``*args``. A key is ignored 

2790 if it does not exist. 

2791 

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

2793 """ 

2794 return self.execute_command("TOUCH", *args) 

2795 

2796 def ttl(self, name: KeyT) -> ResponseT: 

2797 """ 

2798 Returns the number of seconds until the key ``name`` will expire 

2799 

2800 For more information, see https://redis.io/commands/ttl 

2801 """ 

2802 return self.execute_command("TTL", name) 

2803 

2804 def type(self, name: KeyT) -> ResponseT: 

2805 """ 

2806 Returns the type of key ``name`` 

2807 

2808 For more information, see https://redis.io/commands/type 

2809 """ 

2810 return self.execute_command("TYPE", name, keys=[name]) 

2811 

2812 def watch(self, *names: KeyT) -> None: 

2813 """ 

2814 Watches the values at keys ``names``, or None if the key doesn't exist 

2815 

2816 For more information, see https://redis.io/commands/watch 

2817 """ 

2818 warnings.warn(DeprecationWarning("Call WATCH from a Pipeline object")) 

2819 

2820 def unwatch(self) -> None: 

2821 """ 

2822 Unwatches all previously watched keys for a transaction 

2823 

2824 For more information, see https://redis.io/commands/unwatch 

2825 """ 

2826 warnings.warn(DeprecationWarning("Call UNWATCH from a Pipeline object")) 

2827 

2828 def unlink(self, *names: KeyT) -> ResponseT: 

2829 """ 

2830 Unlink one or more keys specified by ``names`` 

2831 

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

2833 """ 

2834 return self.execute_command("UNLINK", *names) 

2835 

2836 def lcs( 

2837 self, 

2838 key1: str, 

2839 key2: str, 

2840 len: Optional[bool] = False, 

2841 idx: Optional[bool] = False, 

2842 minmatchlen: Optional[int] = 0, 

2843 withmatchlen: Optional[bool] = False, 

2844 ) -> Union[str, int, list]: 

2845 """ 

2846 Find the longest common subsequence between ``key1`` and ``key2``. 

2847 If ``len`` is true the length of the match will will be returned. 

2848 If ``idx`` is true the match position in each strings will be returned. 

2849 ``minmatchlen`` restrict the list of matches to the ones of 

2850 the given ``minmatchlen``. 

2851 If ``withmatchlen`` the length of the match also will be returned. 

2852 For more information, see https://redis.io/commands/lcs 

2853 """ 

2854 pieces = [key1, key2] 

2855 if len: 

2856 pieces.append("LEN") 

2857 if idx: 

2858 pieces.append("IDX") 

2859 if minmatchlen != 0: 

2860 pieces.extend(["MINMATCHLEN", minmatchlen]) 

2861 if withmatchlen: 

2862 pieces.append("WITHMATCHLEN") 

2863 return self.execute_command("LCS", *pieces, keys=[key1, key2]) 

2864 

2865 

2866class AsyncBasicKeyCommands(BasicKeyCommands): 

2867 def __delitem__(self, name: KeyT): 

2868 raise TypeError("Async Redis client does not support class deletion") 

2869 

2870 def __contains__(self, name: KeyT): 

2871 raise TypeError("Async Redis client does not support class inclusion") 

2872 

2873 def __getitem__(self, name: KeyT): 

2874 raise TypeError("Async Redis client does not support class retrieval") 

2875 

2876 def __setitem__(self, name: KeyT, value: EncodableT): 

2877 raise TypeError("Async Redis client does not support class assignment") 

2878 

2879 async def watch(self, *names: KeyT) -> None: 

2880 return super().watch(*names) 

2881 

2882 async def unwatch(self) -> None: 

2883 return super().unwatch() 

2884 

2885 

2886class ListCommands(CommandsProtocol): 

2887 """ 

2888 Redis commands for List data type. 

2889 see: https://redis.io/topics/data-types#lists 

2890 """ 

2891 

2892 def blpop( 

2893 self, keys: List, timeout: Optional[Number] = 0 

2894 ) -> Union[Awaitable[list], list]: 

2895 """ 

2896 LPOP a value off of the first non-empty list 

2897 named in the ``keys`` list. 

2898 

2899 If none of the lists in ``keys`` has a value to LPOP, then block 

2900 for ``timeout`` seconds, or until a value gets pushed on to one 

2901 of the lists. 

2902 

2903 If timeout is 0, then block indefinitely. 

2904 

2905 For more information, see https://redis.io/commands/blpop 

2906 """ 

2907 if timeout is None: 

2908 timeout = 0 

2909 keys = list_or_args(keys, None) 

2910 keys.append(timeout) 

2911 return self.execute_command("BLPOP", *keys) 

2912 

2913 def brpop( 

2914 self, keys: List, timeout: Optional[Number] = 0 

2915 ) -> Union[Awaitable[list], list]: 

2916 """ 

2917 RPOP a value off of the first non-empty list 

2918 named in the ``keys`` list. 

2919 

2920 If none of the lists in ``keys`` has a value to RPOP, then block 

2921 for ``timeout`` seconds, or until a value gets pushed on to one 

2922 of the lists. 

2923 

2924 If timeout is 0, then block indefinitely. 

2925 

2926 For more information, see https://redis.io/commands/brpop 

2927 """ 

2928 if timeout is None: 

2929 timeout = 0 

2930 keys = list_or_args(keys, None) 

2931 keys.append(timeout) 

2932 return self.execute_command("BRPOP", *keys) 

2933 

2934 def brpoplpush( 

2935 self, src: KeyT, dst: KeyT, timeout: Optional[Number] = 0 

2936 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

2937 """ 

2938 Pop a value off the tail of ``src``, push it on the head of ``dst`` 

2939 and then return it. 

2940 

2941 This command blocks until a value is in ``src`` or until ``timeout`` 

2942 seconds elapse, whichever is first. A ``timeout`` value of 0 blocks 

2943 forever. 

2944 

2945 For more information, see https://redis.io/commands/brpoplpush 

2946 """ 

2947 if timeout is None: 

2948 timeout = 0 

2949 return self.execute_command("BRPOPLPUSH", src, dst, timeout) 

2950 

2951 def blmpop( 

2952 self, 

2953 timeout: float, 

2954 numkeys: int, 

2955 *args: str, 

2956 direction: str, 

2957 count: Optional[int] = 1, 

2958 ) -> Optional[list]: 

2959 """ 

2960 Pop ``count`` values (default 1) from first non-empty in the list 

2961 of provided key names. 

2962 

2963 When all lists are empty this command blocks the connection until another 

2964 client pushes to it or until the timeout, timeout of 0 blocks indefinitely 

2965 

2966 For more information, see https://redis.io/commands/blmpop 

2967 """ 

2968 cmd_args = [timeout, numkeys, *args, direction, "COUNT", count] 

2969 

2970 return self.execute_command("BLMPOP", *cmd_args) 

2971 

2972 def lmpop( 

2973 self, 

2974 num_keys: int, 

2975 *args: str, 

2976 direction: str, 

2977 count: Optional[int] = 1, 

2978 ) -> Union[Awaitable[list], list]: 

2979 """ 

2980 Pop ``count`` values (default 1) first non-empty list key from the list 

2981 of args provided key names. 

2982 

2983 For more information, see https://redis.io/commands/lmpop 

2984 """ 

2985 cmd_args = [num_keys] + list(args) + [direction] 

2986 if count != 1: 

2987 cmd_args.extend(["COUNT", count]) 

2988 

2989 return self.execute_command("LMPOP", *cmd_args) 

2990 

2991 def lindex( 

2992 self, name: KeyT, index: int 

2993 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

2994 """ 

2995 Return the item from list ``name`` at position ``index`` 

2996 

2997 Negative indexes are supported and will return an item at the 

2998 end of the list 

2999 

3000 For more information, see https://redis.io/commands/lindex 

3001 """ 

3002 return self.execute_command("LINDEX", name, index, keys=[name]) 

3003 

3004 def linsert( 

3005 self, name: KeyT, where: str, refvalue: str, value: str 

3006 ) -> Union[Awaitable[int], int]: 

3007 """ 

3008 Insert ``value`` in list ``name`` either immediately before or after 

3009 [``where``] ``refvalue`` 

3010 

3011 Returns the new length of the list on success or -1 if ``refvalue`` 

3012 is not in the list. 

3013 

3014 For more information, see https://redis.io/commands/linsert 

3015 """ 

3016 return self.execute_command("LINSERT", name, where, refvalue, value) 

3017 

3018 def llen(self, name: KeyT) -> Union[Awaitable[int], int]: 

3019 """ 

3020 Return the length of the list ``name`` 

3021 

3022 For more information, see https://redis.io/commands/llen 

3023 """ 

3024 return self.execute_command("LLEN", name, keys=[name]) 

3025 

3026 def lpop( 

3027 self, 

3028 name: KeyT, 

3029 count: Optional[int] = None, 

3030 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]: 

3031 """ 

3032 Removes and returns the first elements of the list ``name``. 

3033 

3034 By default, the command pops a single element from the beginning of 

3035 the list. When provided with the optional ``count`` argument, the reply 

3036 will consist of up to count elements, depending on the list's length. 

3037 

3038 For more information, see https://redis.io/commands/lpop 

3039 """ 

3040 if count is not None: 

3041 return self.execute_command("LPOP", name, count) 

3042 else: 

3043 return self.execute_command("LPOP", name) 

3044 

3045 def lpush(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]: 

3046 """ 

3047 Push ``values`` onto the head of the list ``name`` 

3048 

3049 For more information, see https://redis.io/commands/lpush 

3050 """ 

3051 return self.execute_command("LPUSH", name, *values) 

3052 

3053 def lpushx(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]: 

3054 """ 

3055 Push ``value`` onto the head of the list ``name`` if ``name`` exists 

3056 

3057 For more information, see https://redis.io/commands/lpushx 

3058 """ 

3059 return self.execute_command("LPUSHX", name, *values) 

3060 

3061 def lrange(self, name: KeyT, start: int, end: int) -> Union[Awaitable[list], list]: 

3062 """ 

3063 Return a slice of the list ``name`` between 

3064 position ``start`` and ``end`` 

3065 

3066 ``start`` and ``end`` can be negative numbers just like 

3067 Python slicing notation 

3068 

3069 For more information, see https://redis.io/commands/lrange 

3070 """ 

3071 return self.execute_command("LRANGE", name, start, end, keys=[name]) 

3072 

3073 def lrem(self, name: KeyT, count: int, value: str) -> Union[Awaitable[int], int]: 

3074 """ 

3075 Remove the first ``count`` occurrences of elements equal to ``value`` 

3076 from the list stored at ``name``. 

3077 

3078 The count argument influences the operation in the following ways: 

3079 count > 0: Remove elements equal to value moving from head to tail. 

3080 count < 0: Remove elements equal to value moving from tail to head. 

3081 count = 0: Remove all elements equal to value. 

3082 

3083 For more information, see https://redis.io/commands/lrem 

3084 """ 

3085 return self.execute_command("LREM", name, count, value) 

3086 

3087 def lset(self, name: KeyT, index: int, value: str) -> Union[Awaitable[str], str]: 

3088 """ 

3089 Set element at ``index`` of list ``name`` to ``value`` 

3090 

3091 For more information, see https://redis.io/commands/lset 

3092 """ 

3093 return self.execute_command("LSET", name, index, value) 

3094 

3095 def ltrim(self, name: KeyT, start: int, end: int) -> Union[Awaitable[str], str]: 

3096 """ 

3097 Trim the list ``name``, removing all values not within the slice 

3098 between ``start`` and ``end`` 

3099 

3100 ``start`` and ``end`` can be negative numbers just like 

3101 Python slicing notation 

3102 

3103 For more information, see https://redis.io/commands/ltrim 

3104 """ 

3105 return self.execute_command("LTRIM", name, start, end) 

3106 

3107 def rpop( 

3108 self, 

3109 name: KeyT, 

3110 count: Optional[int] = None, 

3111 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]: 

3112 """ 

3113 Removes and returns the last elements of the list ``name``. 

3114 

3115 By default, the command pops a single element from the end of the list. 

3116 When provided with the optional ``count`` argument, the reply will 

3117 consist of up to count elements, depending on the list's length. 

3118 

3119 For more information, see https://redis.io/commands/rpop 

3120 """ 

3121 if count is not None: 

3122 return self.execute_command("RPOP", name, count) 

3123 else: 

3124 return self.execute_command("RPOP", name) 

3125 

3126 def rpoplpush(self, src: KeyT, dst: KeyT) -> Union[Awaitable[str], str]: 

3127 """ 

3128 RPOP a value off of the ``src`` list and atomically LPUSH it 

3129 on to the ``dst`` list. Returns the value. 

3130 

3131 For more information, see https://redis.io/commands/rpoplpush 

3132 """ 

3133 return self.execute_command("RPOPLPUSH", src, dst) 

3134 

3135 def rpush(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]: 

3136 """ 

3137 Push ``values`` onto the tail of the list ``name`` 

3138 

3139 For more information, see https://redis.io/commands/rpush 

3140 """ 

3141 return self.execute_command("RPUSH", name, *values) 

3142 

3143 def rpushx(self, name: KeyT, *values: str) -> Union[Awaitable[int], int]: 

3144 """ 

3145 Push ``value`` onto the tail of the list ``name`` if ``name`` exists 

3146 

3147 For more information, see https://redis.io/commands/rpushx 

3148 """ 

3149 return self.execute_command("RPUSHX", name, *values) 

3150 

3151 def lpos( 

3152 self, 

3153 name: KeyT, 

3154 value: str, 

3155 rank: Optional[int] = None, 

3156 count: Optional[int] = None, 

3157 maxlen: Optional[int] = None, 

3158 ) -> Union[str, List, None]: 

3159 """ 

3160 Get position of ``value`` within the list ``name`` 

3161 

3162 If specified, ``rank`` indicates the "rank" of the first element to 

3163 return in case there are multiple copies of ``value`` in the list. 

3164 By default, LPOS returns the position of the first occurrence of 

3165 ``value`` in the list. When ``rank`` 2, LPOS returns the position of 

3166 the second ``value`` in the list. If ``rank`` is negative, LPOS 

3167 searches the list in reverse. For example, -1 would return the 

3168 position of the last occurrence of ``value`` and -2 would return the 

3169 position of the next to last occurrence of ``value``. 

3170 

3171 If specified, ``count`` indicates that LPOS should return a list of 

3172 up to ``count`` positions. A ``count`` of 2 would return a list of 

3173 up to 2 positions. A ``count`` of 0 returns a list of all positions 

3174 matching ``value``. When ``count`` is specified and but ``value`` 

3175 does not exist in the list, an empty list is returned. 

3176 

3177 If specified, ``maxlen`` indicates the maximum number of list 

3178 elements to scan. A ``maxlen`` of 1000 will only return the 

3179 position(s) of items within the first 1000 entries in the list. 

3180 A ``maxlen`` of 0 (the default) will scan the entire list. 

3181 

3182 For more information, see https://redis.io/commands/lpos 

3183 """ 

3184 pieces: list[EncodableT] = [name, value] 

3185 if rank is not None: 

3186 pieces.extend(["RANK", rank]) 

3187 

3188 if count is not None: 

3189 pieces.extend(["COUNT", count]) 

3190 

3191 if maxlen is not None: 

3192 pieces.extend(["MAXLEN", maxlen]) 

3193 

3194 return self.execute_command("LPOS", *pieces, keys=[name]) 

3195 

3196 def sort( 

3197 self, 

3198 name: KeyT, 

3199 start: Optional[int] = None, 

3200 num: Optional[int] = None, 

3201 by: Optional[str] = None, 

3202 get: Optional[List[str]] = None, 

3203 desc: bool = False, 

3204 alpha: bool = False, 

3205 store: Optional[str] = None, 

3206 groups: Optional[bool] = False, 

3207 ) -> Union[List, int]: 

3208 """ 

3209 Sort and return the list, set or sorted set at ``name``. 

3210 

3211 ``start`` and ``num`` allow for paging through the sorted data 

3212 

3213 ``by`` allows using an external key to weight and sort the items. 

3214 Use an "*" to indicate where in the key the item value is located 

3215 

3216 ``get`` allows for returning items from external keys rather than the 

3217 sorted data itself. Use an "*" to indicate where in the key 

3218 the item value is located 

3219 

3220 ``desc`` allows for reversing the sort 

3221 

3222 ``alpha`` allows for sorting lexicographically rather than numerically 

3223 

3224 ``store`` allows for storing the result of the sort into 

3225 the key ``store`` 

3226 

3227 ``groups`` if set to True and if ``get`` contains at least two 

3228 elements, sort will return a list of tuples, each containing the 

3229 values fetched from the arguments to ``get``. 

3230 

3231 For more information, see https://redis.io/commands/sort 

3232 """ 

3233 if (start is not None and num is None) or (num is not None and start is None): 

3234 raise DataError("``start`` and ``num`` must both be specified") 

3235 

3236 pieces: list[EncodableT] = [name] 

3237 if by is not None: 

3238 pieces.extend([b"BY", by]) 

3239 if start is not None and num is not None: 

3240 pieces.extend([b"LIMIT", start, num]) 

3241 if get is not None: 

3242 # If get is a string assume we want to get a single value. 

3243 # Otherwise assume it's an interable and we want to get multiple 

3244 # values. We can't just iterate blindly because strings are 

3245 # iterable. 

3246 if isinstance(get, (bytes, str)): 

3247 pieces.extend([b"GET", get]) 

3248 else: 

3249 for g in get: 

3250 pieces.extend([b"GET", g]) 

3251 if desc: 

3252 pieces.append(b"DESC") 

3253 if alpha: 

3254 pieces.append(b"ALPHA") 

3255 if store is not None: 

3256 pieces.extend([b"STORE", store]) 

3257 if groups: 

3258 if not get or isinstance(get, (bytes, str)) or len(get) < 2: 

3259 raise DataError( 

3260 'when using "groups" the "get" argument ' 

3261 "must be specified and contain at least " 

3262 "two keys" 

3263 ) 

3264 

3265 options = {"groups": len(get) if groups else None} 

3266 options["keys"] = [name] 

3267 return self.execute_command("SORT", *pieces, **options) 

3268 

3269 def sort_ro( 

3270 self, 

3271 key: str, 

3272 start: Optional[int] = None, 

3273 num: Optional[int] = None, 

3274 by: Optional[str] = None, 

3275 get: Optional[List[str]] = None, 

3276 desc: bool = False, 

3277 alpha: bool = False, 

3278 ) -> list: 

3279 """ 

3280 Returns the elements contained in the list, set or sorted set at key. 

3281 (read-only variant of the SORT command) 

3282 

3283 ``start`` and ``num`` allow for paging through the sorted data 

3284 

3285 ``by`` allows using an external key to weight and sort the items. 

3286 Use an "*" to indicate where in the key the item value is located 

3287 

3288 ``get`` allows for returning items from external keys rather than the 

3289 sorted data itself. Use an "*" to indicate where in the key 

3290 the item value is located 

3291 

3292 ``desc`` allows for reversing the sort 

3293 

3294 ``alpha`` allows for sorting lexicographically rather than numerically 

3295 

3296 For more information, see https://redis.io/commands/sort_ro 

3297 """ 

3298 return self.sort( 

3299 key, start=start, num=num, by=by, get=get, desc=desc, alpha=alpha 

3300 ) 

3301 

3302 

3303AsyncListCommands = ListCommands 

3304 

3305 

3306class ScanCommands(CommandsProtocol): 

3307 """ 

3308 Redis SCAN commands. 

3309 see: https://redis.io/commands/scan 

3310 """ 

3311 

3312 def scan( 

3313 self, 

3314 cursor: int = 0, 

3315 match: Union[PatternT, None] = None, 

3316 count: Optional[int] = None, 

3317 _type: Optional[str] = None, 

3318 **kwargs, 

3319 ) -> ResponseT: 

3320 """ 

3321 Incrementally return lists of key names. Also return a cursor 

3322 indicating the scan position. 

3323 

3324 ``match`` allows for filtering the keys by pattern 

3325 

3326 ``count`` provides a hint to Redis about the number of keys to 

3327 return per batch. 

3328 

3329 ``_type`` filters the returned values by a particular Redis type. 

3330 Stock Redis instances allow for the following types: 

3331 HASH, LIST, SET, STREAM, STRING, ZSET 

3332 Additionally, Redis modules can expose other types as well. 

3333 

3334 For more information, see https://redis.io/commands/scan 

3335 """ 

3336 pieces: list[EncodableT] = [cursor] 

3337 if match is not None: 

3338 pieces.extend([b"MATCH", match]) 

3339 if count is not None: 

3340 pieces.extend([b"COUNT", count]) 

3341 if _type is not None: 

3342 pieces.extend([b"TYPE", _type]) 

3343 return self.execute_command("SCAN", *pieces, **kwargs) 

3344 

3345 def scan_iter( 

3346 self, 

3347 match: Union[PatternT, None] = None, 

3348 count: Optional[int] = None, 

3349 _type: Optional[str] = None, 

3350 **kwargs, 

3351 ) -> Iterator: 

3352 """ 

3353 Make an iterator using the SCAN command so that the client doesn't 

3354 need to remember the cursor position. 

3355 

3356 ``match`` allows for filtering the keys by pattern 

3357 

3358 ``count`` provides a hint to Redis about the number of keys to 

3359 return per batch. 

3360 

3361 ``_type`` filters the returned values by a particular Redis type. 

3362 Stock Redis instances allow for the following types: 

3363 HASH, LIST, SET, STREAM, STRING, ZSET 

3364 Additionally, Redis modules can expose other types as well. 

3365 """ 

3366 cursor = "0" 

3367 while cursor != 0: 

3368 cursor, data = self.scan( 

3369 cursor=cursor, match=match, count=count, _type=_type, **kwargs 

3370 ) 

3371 yield from data 

3372 

3373 def sscan( 

3374 self, 

3375 name: KeyT, 

3376 cursor: int = 0, 

3377 match: Union[PatternT, None] = None, 

3378 count: Optional[int] = None, 

3379 ) -> ResponseT: 

3380 """ 

3381 Incrementally return lists of elements in a set. Also return a cursor 

3382 indicating the scan position. 

3383 

3384 ``match`` allows for filtering the keys by pattern 

3385 

3386 ``count`` allows for hint the minimum number of returns 

3387 

3388 For more information, see https://redis.io/commands/sscan 

3389 """ 

3390 pieces: list[EncodableT] = [name, cursor] 

3391 if match is not None: 

3392 pieces.extend([b"MATCH", match]) 

3393 if count is not None: 

3394 pieces.extend([b"COUNT", count]) 

3395 return self.execute_command("SSCAN", *pieces) 

3396 

3397 def sscan_iter( 

3398 self, 

3399 name: KeyT, 

3400 match: Union[PatternT, None] = None, 

3401 count: Optional[int] = None, 

3402 ) -> Iterator: 

3403 """ 

3404 Make an iterator using the SSCAN command so that the client doesn't 

3405 need to remember the cursor position. 

3406 

3407 ``match`` allows for filtering the keys by pattern 

3408 

3409 ``count`` allows for hint the minimum number of returns 

3410 """ 

3411 cursor = "0" 

3412 while cursor != 0: 

3413 cursor, data = self.sscan(name, cursor=cursor, match=match, count=count) 

3414 yield from data 

3415 

3416 def hscan( 

3417 self, 

3418 name: KeyT, 

3419 cursor: int = 0, 

3420 match: Union[PatternT, None] = None, 

3421 count: Optional[int] = None, 

3422 no_values: Union[bool, None] = None, 

3423 ) -> ResponseT: 

3424 """ 

3425 Incrementally return key/value slices in a hash. Also return a cursor 

3426 indicating the scan position. 

3427 

3428 ``match`` allows for filtering the keys by pattern 

3429 

3430 ``count`` allows for hint the minimum number of returns 

3431 

3432 ``no_values`` indicates to return only the keys, without values. 

3433 

3434 For more information, see https://redis.io/commands/hscan 

3435 """ 

3436 pieces: list[EncodableT] = [name, cursor] 

3437 if match is not None: 

3438 pieces.extend([b"MATCH", match]) 

3439 if count is not None: 

3440 pieces.extend([b"COUNT", count]) 

3441 if no_values is not None: 

3442 pieces.extend([b"NOVALUES"]) 

3443 return self.execute_command("HSCAN", *pieces, no_values=no_values) 

3444 

3445 def hscan_iter( 

3446 self, 

3447 name: str, 

3448 match: Union[PatternT, None] = None, 

3449 count: Optional[int] = None, 

3450 no_values: Union[bool, None] = None, 

3451 ) -> Iterator: 

3452 """ 

3453 Make an iterator using the HSCAN command so that the client doesn't 

3454 need to remember the cursor position. 

3455 

3456 ``match`` allows for filtering the keys by pattern 

3457 

3458 ``count`` allows for hint the minimum number of returns 

3459 

3460 ``no_values`` indicates to return only the keys, without values 

3461 """ 

3462 cursor = "0" 

3463 while cursor != 0: 

3464 cursor, data = self.hscan( 

3465 name, cursor=cursor, match=match, count=count, no_values=no_values 

3466 ) 

3467 if no_values: 

3468 yield from data 

3469 else: 

3470 yield from data.items() 

3471 

3472 def zscan( 

3473 self, 

3474 name: KeyT, 

3475 cursor: int = 0, 

3476 match: Union[PatternT, None] = None, 

3477 count: Optional[int] = None, 

3478 score_cast_func: Union[type, Callable] = float, 

3479 ) -> ResponseT: 

3480 """ 

3481 Incrementally return lists of elements in a sorted set. Also return a 

3482 cursor indicating the scan position. 

3483 

3484 ``match`` allows for filtering the keys by pattern 

3485 

3486 ``count`` allows for hint the minimum number of returns 

3487 

3488 ``score_cast_func`` a callable used to cast the score return value 

3489 

3490 For more information, see https://redis.io/commands/zscan 

3491 """ 

3492 pieces = [name, cursor] 

3493 if match is not None: 

3494 pieces.extend([b"MATCH", match]) 

3495 if count is not None: 

3496 pieces.extend([b"COUNT", count]) 

3497 options = {"score_cast_func": score_cast_func} 

3498 return self.execute_command("ZSCAN", *pieces, **options) 

3499 

3500 def zscan_iter( 

3501 self, 

3502 name: KeyT, 

3503 match: Union[PatternT, None] = None, 

3504 count: Optional[int] = None, 

3505 score_cast_func: Union[type, Callable] = float, 

3506 ) -> Iterator: 

3507 """ 

3508 Make an iterator using the ZSCAN command so that the client doesn't 

3509 need to remember the cursor position. 

3510 

3511 ``match`` allows for filtering the keys by pattern 

3512 

3513 ``count`` allows for hint the minimum number of returns 

3514 

3515 ``score_cast_func`` a callable used to cast the score return value 

3516 """ 

3517 cursor = "0" 

3518 while cursor != 0: 

3519 cursor, data = self.zscan( 

3520 name, 

3521 cursor=cursor, 

3522 match=match, 

3523 count=count, 

3524 score_cast_func=score_cast_func, 

3525 ) 

3526 yield from data 

3527 

3528 

3529class AsyncScanCommands(ScanCommands): 

3530 async def scan_iter( 

3531 self, 

3532 match: Union[PatternT, None] = None, 

3533 count: Optional[int] = None, 

3534 _type: Optional[str] = None, 

3535 **kwargs, 

3536 ) -> AsyncIterator: 

3537 """ 

3538 Make an iterator using the SCAN command so that the client doesn't 

3539 need to remember the cursor position. 

3540 

3541 ``match`` allows for filtering the keys by pattern 

3542 

3543 ``count`` provides a hint to Redis about the number of keys to 

3544 return per batch. 

3545 

3546 ``_type`` filters the returned values by a particular Redis type. 

3547 Stock Redis instances allow for the following types: 

3548 HASH, LIST, SET, STREAM, STRING, ZSET 

3549 Additionally, Redis modules can expose other types as well. 

3550 """ 

3551 cursor = "0" 

3552 while cursor != 0: 

3553 cursor, data = await self.scan( 

3554 cursor=cursor, match=match, count=count, _type=_type, **kwargs 

3555 ) 

3556 for d in data: 

3557 yield d 

3558 

3559 async def sscan_iter( 

3560 self, 

3561 name: KeyT, 

3562 match: Union[PatternT, None] = None, 

3563 count: Optional[int] = None, 

3564 ) -> AsyncIterator: 

3565 """ 

3566 Make an iterator using the SSCAN command so that the client doesn't 

3567 need to remember the cursor position. 

3568 

3569 ``match`` allows for filtering the keys by pattern 

3570 

3571 ``count`` allows for hint the minimum number of returns 

3572 """ 

3573 cursor = "0" 

3574 while cursor != 0: 

3575 cursor, data = await self.sscan( 

3576 name, cursor=cursor, match=match, count=count 

3577 ) 

3578 for d in data: 

3579 yield d 

3580 

3581 async def hscan_iter( 

3582 self, 

3583 name: str, 

3584 match: Union[PatternT, None] = None, 

3585 count: Optional[int] = None, 

3586 no_values: Union[bool, None] = None, 

3587 ) -> AsyncIterator: 

3588 """ 

3589 Make an iterator using the HSCAN command so that the client doesn't 

3590 need to remember the cursor position. 

3591 

3592 ``match`` allows for filtering the keys by pattern 

3593 

3594 ``count`` allows for hint the minimum number of returns 

3595 

3596 ``no_values`` indicates to return only the keys, without values 

3597 """ 

3598 cursor = "0" 

3599 while cursor != 0: 

3600 cursor, data = await self.hscan( 

3601 name, cursor=cursor, match=match, count=count, no_values=no_values 

3602 ) 

3603 if no_values: 

3604 for it in data: 

3605 yield it 

3606 else: 

3607 for it in data.items(): 

3608 yield it 

3609 

3610 async def zscan_iter( 

3611 self, 

3612 name: KeyT, 

3613 match: Union[PatternT, None] = None, 

3614 count: Optional[int] = None, 

3615 score_cast_func: Union[type, Callable] = float, 

3616 ) -> AsyncIterator: 

3617 """ 

3618 Make an iterator using the ZSCAN command so that the client doesn't 

3619 need to remember the cursor position. 

3620 

3621 ``match`` allows for filtering the keys by pattern 

3622 

3623 ``count`` allows for hint the minimum number of returns 

3624 

3625 ``score_cast_func`` a callable used to cast the score return value 

3626 """ 

3627 cursor = "0" 

3628 while cursor != 0: 

3629 cursor, data = await self.zscan( 

3630 name, 

3631 cursor=cursor, 

3632 match=match, 

3633 count=count, 

3634 score_cast_func=score_cast_func, 

3635 ) 

3636 for d in data: 

3637 yield d 

3638 

3639 

3640class SetCommands(CommandsProtocol): 

3641 """ 

3642 Redis commands for Set data type. 

3643 see: https://redis.io/topics/data-types#sets 

3644 """ 

3645 

3646 def sadd(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]: 

3647 """ 

3648 Add ``value(s)`` to set ``name`` 

3649 

3650 For more information, see https://redis.io/commands/sadd 

3651 """ 

3652 return self.execute_command("SADD", name, *values) 

3653 

3654 def scard(self, name: KeyT) -> Union[Awaitable[int], int]: 

3655 """ 

3656 Return the number of elements in set ``name`` 

3657 

3658 For more information, see https://redis.io/commands/scard 

3659 """ 

3660 return self.execute_command("SCARD", name, keys=[name]) 

3661 

3662 def sdiff(self, keys: List, *args: List) -> Union[Awaitable[list], list]: 

3663 """ 

3664 Return the difference of sets specified by ``keys`` 

3665 

3666 For more information, see https://redis.io/commands/sdiff 

3667 """ 

3668 args = list_or_args(keys, args) 

3669 return self.execute_command("SDIFF", *args, keys=args) 

3670 

3671 def sdiffstore( 

3672 self, dest: str, keys: List, *args: List 

3673 ) -> Union[Awaitable[int], int]: 

3674 """ 

3675 Store the difference of sets specified by ``keys`` into a new 

3676 set named ``dest``. Returns the number of keys in the new set. 

3677 

3678 For more information, see https://redis.io/commands/sdiffstore 

3679 """ 

3680 args = list_or_args(keys, args) 

3681 return self.execute_command("SDIFFSTORE", dest, *args) 

3682 

3683 def sinter(self, keys: List, *args: List) -> Union[Awaitable[list], list]: 

3684 """ 

3685 Return the intersection of sets specified by ``keys`` 

3686 

3687 For more information, see https://redis.io/commands/sinter 

3688 """ 

3689 args = list_or_args(keys, args) 

3690 return self.execute_command("SINTER", *args, keys=args) 

3691 

3692 def sintercard( 

3693 self, numkeys: int, keys: List[KeyT], limit: int = 0 

3694 ) -> Union[Awaitable[int], int]: 

3695 """ 

3696 Return the cardinality of the intersect of multiple sets specified by ``keys``. 

3697 

3698 When LIMIT provided (defaults to 0 and means unlimited), if the intersection 

3699 cardinality reaches limit partway through the computation, the algorithm will 

3700 exit and yield limit as the cardinality 

3701 

3702 For more information, see https://redis.io/commands/sintercard 

3703 """ 

3704 args = [numkeys, *keys, "LIMIT", limit] 

3705 return self.execute_command("SINTERCARD", *args, keys=keys) 

3706 

3707 def sinterstore( 

3708 self, dest: KeyT, keys: List, *args: List 

3709 ) -> Union[Awaitable[int], int]: 

3710 """ 

3711 Store the intersection of sets specified by ``keys`` into a new 

3712 set named ``dest``. Returns the number of keys in the new set. 

3713 

3714 For more information, see https://redis.io/commands/sinterstore 

3715 """ 

3716 args = list_or_args(keys, args) 

3717 return self.execute_command("SINTERSTORE", dest, *args) 

3718 

3719 def sismember( 

3720 self, name: KeyT, value: str 

3721 ) -> Union[Awaitable[Union[Literal[0], Literal[1]]], Union[Literal[0], Literal[1]]]: 

3722 """ 

3723 Return whether ``value`` is a member of set ``name``: 

3724 - 1 if the value is a member of the set. 

3725 - 0 if the value is not a member of the set or if key does not exist. 

3726 

3727 For more information, see https://redis.io/commands/sismember 

3728 """ 

3729 return self.execute_command("SISMEMBER", name, value, keys=[name]) 

3730 

3731 def smembers(self, name: KeyT) -> Union[Awaitable[Set], Set]: 

3732 """ 

3733 Return all members of the set ``name`` 

3734 

3735 For more information, see https://redis.io/commands/smembers 

3736 """ 

3737 return self.execute_command("SMEMBERS", name, keys=[name]) 

3738 

3739 def smismember( 

3740 self, name: KeyT, values: List, *args: List 

3741 ) -> Union[ 

3742 Awaitable[List[Union[Literal[0], Literal[1]]]], 

3743 List[Union[Literal[0], Literal[1]]], 

3744 ]: 

3745 """ 

3746 Return whether each value in ``values`` is a member of the set ``name`` 

3747 as a list of ``int`` in the order of ``values``: 

3748 - 1 if the value is a member of the set. 

3749 - 0 if the value is not a member of the set or if key does not exist. 

3750 

3751 For more information, see https://redis.io/commands/smismember 

3752 """ 

3753 args = list_or_args(values, args) 

3754 return self.execute_command("SMISMEMBER", name, *args, keys=[name]) 

3755 

3756 def smove(self, src: KeyT, dst: KeyT, value: str) -> Union[Awaitable[bool], bool]: 

3757 """ 

3758 Move ``value`` from set ``src`` to set ``dst`` atomically 

3759 

3760 For more information, see https://redis.io/commands/smove 

3761 """ 

3762 return self.execute_command("SMOVE", src, dst, value) 

3763 

3764 def spop(self, name: KeyT, count: Optional[int] = None) -> Union[str, List, None]: 

3765 """ 

3766 Remove and return a random member of set ``name`` 

3767 

3768 For more information, see https://redis.io/commands/spop 

3769 """ 

3770 args = (count is not None) and [count] or [] 

3771 return self.execute_command("SPOP", name, *args) 

3772 

3773 def srandmember( 

3774 self, name: KeyT, number: Optional[int] = None 

3775 ) -> Union[str, List, None]: 

3776 """ 

3777 If ``number`` is None, returns a random member of set ``name``. 

3778 

3779 If ``number`` is supplied, returns a list of ``number`` random 

3780 members of set ``name``. Note this is only available when running 

3781 Redis 2.6+. 

3782 

3783 For more information, see https://redis.io/commands/srandmember 

3784 """ 

3785 args = (number is not None) and [number] or [] 

3786 return self.execute_command("SRANDMEMBER", name, *args) 

3787 

3788 def srem(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]: 

3789 """ 

3790 Remove ``values`` from set ``name`` 

3791 

3792 For more information, see https://redis.io/commands/srem 

3793 """ 

3794 return self.execute_command("SREM", name, *values) 

3795 

3796 def sunion(self, keys: List, *args: List) -> Union[Awaitable[List], List]: 

3797 """ 

3798 Return the union of sets specified by ``keys`` 

3799 

3800 For more information, see https://redis.io/commands/sunion 

3801 """ 

3802 args = list_or_args(keys, args) 

3803 return self.execute_command("SUNION", *args, keys=args) 

3804 

3805 def sunionstore( 

3806 self, dest: KeyT, keys: List, *args: List 

3807 ) -> Union[Awaitable[int], int]: 

3808 """ 

3809 Store the union of sets specified by ``keys`` into a new 

3810 set named ``dest``. Returns the number of keys in the new set. 

3811 

3812 For more information, see https://redis.io/commands/sunionstore 

3813 """ 

3814 args = list_or_args(keys, args) 

3815 return self.execute_command("SUNIONSTORE", dest, *args) 

3816 

3817 

3818AsyncSetCommands = SetCommands 

3819 

3820 

3821class StreamCommands(CommandsProtocol): 

3822 """ 

3823 Redis commands for Stream data type. 

3824 see: https://redis.io/topics/streams-intro 

3825 """ 

3826 

3827 def xack(self, name: KeyT, groupname: GroupT, *ids: StreamIdT) -> ResponseT: 

3828 """ 

3829 Acknowledges the successful processing of one or more messages. 

3830 

3831 Args: 

3832 name: name of the stream. 

3833 groupname: name of the consumer group. 

3834 *ids: message ids to acknowledge. 

3835 

3836 For more information, see https://redis.io/commands/xack 

3837 """ 

3838 return self.execute_command("XACK", name, groupname, *ids) 

3839 

3840 def xackdel( 

3841 self, 

3842 name: KeyT, 

3843 groupname: GroupT, 

3844 *ids: StreamIdT, 

3845 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF", 

3846 ) -> ResponseT: 

3847 """ 

3848 Combines the functionality of XACK and XDEL. Acknowledges the specified 

3849 message IDs in the given consumer group and simultaneously attempts to 

3850 delete the corresponding entries from the stream. 

3851 """ 

3852 if not ids: 

3853 raise DataError("XACKDEL requires at least one message ID") 

3854 

3855 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

3856 raise DataError("XACKDEL ref_policy must be one of: KEEPREF, DELREF, ACKED") 

3857 

3858 pieces = [name, groupname, ref_policy, "IDS", len(ids)] 

3859 pieces.extend(ids) 

3860 return self.execute_command("XACKDEL", *pieces) 

3861 

3862 def xadd( 

3863 self, 

3864 name: KeyT, 

3865 fields: Dict[FieldT, EncodableT], 

3866 id: StreamIdT = "*", 

3867 maxlen: Optional[int] = None, 

3868 approximate: bool = True, 

3869 nomkstream: bool = False, 

3870 minid: Union[StreamIdT, None] = None, 

3871 limit: Optional[int] = None, 

3872 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None, 

3873 idmpauto: Optional[str] = None, 

3874 idmp: Optional[tuple[str, bytes]] = None, 

3875 ) -> ResponseT: 

3876 """ 

3877 Add to a stream. 

3878 name: name of the stream 

3879 fields: dict of field/value pairs to insert into the stream 

3880 id: Location to insert this record. By default it is appended. 

3881 maxlen: truncate old stream members beyond this size. 

3882 Can't be specified with minid. 

3883 approximate: actual stream length may be slightly more than maxlen 

3884 nomkstream: When set to true, do not make a stream 

3885 minid: the minimum id in the stream to query. 

3886 Can't be specified with maxlen. 

3887 limit: specifies the maximum number of entries to retrieve 

3888 ref_policy: optional reference policy for consumer groups when trimming: 

3889 - KEEPREF (default): When trimming, preserves references in consumer groups' PEL 

3890 - DELREF: When trimming, removes all references from consumer groups' PEL 

3891 - ACKED: When trimming, only removes entries acknowledged by all consumer groups 

3892 idmpauto: Producer ID for automatic idempotent ID calculation. 

3893 Automatically calculates an idempotent ID based on entry content to prevent 

3894 duplicate entries. Can only be used with id='*'. Creates an IDMP map if it 

3895 doesn't exist yet. The producer ID must be unique per producer and consistent 

3896 across restarts. 

3897 idmp: Tuple of (producer_id, idempotent_id) for explicit idempotent ID. 

3898 Uses a specific idempotent ID to prevent duplicate entries. Can only be used 

3899 with id='*'. The producer ID must be unique per producer and consistent across 

3900 restarts. The idempotent ID must be unique per message and per producer. 

3901 Shorter idempotent IDs require less memory and allow faster processing. 

3902 Creates an IDMP map if it doesn't exist yet. 

3903 

3904 For more information, see https://redis.io/commands/xadd 

3905 """ 

3906 pieces: list[EncodableT] = [] 

3907 if maxlen is not None and minid is not None: 

3908 raise DataError("Only one of ```maxlen``` or ```minid``` may be specified") 

3909 

3910 if idmpauto is not None and idmp is not None: 

3911 raise DataError("Only one of ```idmpauto``` or ```idmp``` may be specified") 

3912 

3913 if (idmpauto is not None or idmp is not None) and id != "*": 

3914 raise DataError("IDMPAUTO and IDMP can only be used with id='*'") 

3915 

3916 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

3917 raise DataError("XADD ref_policy must be one of: KEEPREF, DELREF, ACKED") 

3918 

3919 if nomkstream: 

3920 pieces.append(b"NOMKSTREAM") 

3921 if ref_policy is not None: 

3922 pieces.append(ref_policy) 

3923 if idmpauto is not None: 

3924 pieces.extend([b"IDMPAUTO", idmpauto]) 

3925 if idmp is not None: 

3926 if not isinstance(idmp, tuple) or len(idmp) != 2: 

3927 raise DataError( 

3928 "XADD idmp must be a tuple of (producer_id, idempotent_id)" 

3929 ) 

3930 pieces.extend([b"IDMP", idmp[0], idmp[1]]) 

3931 if maxlen is not None: 

3932 if not isinstance(maxlen, int) or maxlen < 0: 

3933 raise DataError("XADD maxlen must be non-negative integer") 

3934 pieces.append(b"MAXLEN") 

3935 if approximate: 

3936 pieces.append(b"~") 

3937 pieces.append(str(maxlen)) 

3938 if minid is not None: 

3939 pieces.append(b"MINID") 

3940 if approximate: 

3941 pieces.append(b"~") 

3942 pieces.append(minid) 

3943 if limit is not None: 

3944 pieces.extend([b"LIMIT", limit]) 

3945 pieces.append(id) 

3946 if not isinstance(fields, dict) or len(fields) == 0: 

3947 raise DataError("XADD fields must be a non-empty dict") 

3948 for pair in fields.items(): 

3949 pieces.extend(pair) 

3950 return self.execute_command("XADD", name, *pieces) 

3951 

3952 def xcfgset( 

3953 self, 

3954 name: KeyT, 

3955 idmp_duration: Optional[int] = None, 

3956 idmp_maxsize: Optional[int] = None, 

3957 ) -> ResponseT: 

3958 """ 

3959 Configure the idempotency parameters for a stream's IDMP map. 

3960 

3961 Sets how long Redis remembers each idempotent ID (iid) and the maximum 

3962 number of iids to track. This command clears the existing IDMP map 

3963 (Redis forgets all previously stored iids), but only if the configuration 

3964 value actually changes. 

3965 

3966 Args: 

3967 name: The name of the stream. 

3968 idmp_duration: How long Redis remembers each iid in seconds. 

3969 Default: 100 seconds (or value set by stream-idmp-duration config). 

3970 Minimum: 1 second, Maximum: 300 seconds. 

3971 Redis won't forget an iid for this duration (unless maxsize is reached). 

3972 Should accommodate application crash recovery time. 

3973 idmp_maxsize: Maximum number of iids Redis remembers per producer ID (pid). 

3974 Default: 100 iids (or value set by stream-idmp-maxsize config). 

3975 Minimum: 1 iid, Maximum: 1,000,000 (1M) iids. 

3976 Should be set to: mark-delay [in msec] × (messages/msec) + margin. 

3977 Example: 10K msgs/sec (10 msgs/msec), 80 msec mark-delay 

3978 → maxsize = 10 × 80 + margin = 1000 iids. 

3979 

3980 Returns: 

3981 OK on success. 

3982 

3983 For more information, see https://redis.io/commands/xcfgset 

3984 """ 

3985 if idmp_duration is None and idmp_maxsize is None: 

3986 raise DataError( 

3987 "XCFGSET requires at least one of idmp_duration or idmp_maxsize" 

3988 ) 

3989 

3990 pieces: list[EncodableT] = [] 

3991 

3992 if idmp_duration is not None: 

3993 if ( 

3994 not isinstance(idmp_duration, int) 

3995 or idmp_duration < 1 

3996 or idmp_duration > 300 

3997 ): 

3998 raise DataError( 

3999 "XCFGSET idmp_duration must be an integer between 1 and 300" 

4000 ) 

4001 pieces.extend([b"IDMP-DURATION", idmp_duration]) 

4002 

4003 if idmp_maxsize is not None: 

4004 if ( 

4005 not isinstance(idmp_maxsize, int) 

4006 or idmp_maxsize < 1 

4007 or idmp_maxsize > 1000000 

4008 ): 

4009 raise DataError( 

4010 "XCFGSET idmp_maxsize must be an integer between 1 and 1,000,000" 

4011 ) 

4012 pieces.extend([b"IDMP-MAXSIZE", idmp_maxsize]) 

4013 

4014 return self.execute_command("XCFGSET", name, *pieces) 

4015 

4016 def xautoclaim( 

4017 self, 

4018 name: KeyT, 

4019 groupname: GroupT, 

4020 consumername: ConsumerT, 

4021 min_idle_time: int, 

4022 start_id: StreamIdT = "0-0", 

4023 count: Optional[int] = None, 

4024 justid: bool = False, 

4025 ) -> ResponseT: 

4026 """ 

4027 Transfers ownership of pending stream entries that match the specified 

4028 criteria. Conceptually, equivalent to calling XPENDING and then XCLAIM, 

4029 but provides a more straightforward way to deal with message delivery 

4030 failures via SCAN-like semantics. 

4031 name: name of the stream. 

4032 groupname: name of the consumer group. 

4033 consumername: name of a consumer that claims the message. 

4034 min_idle_time: filter messages that were idle less than this amount of 

4035 milliseconds. 

4036 start_id: filter messages with equal or greater ID. 

4037 count: optional integer, upper limit of the number of entries that the 

4038 command attempts to claim. Set to 100 by default. 

4039 justid: optional boolean, false by default. Return just an array of IDs 

4040 of messages successfully claimed, without returning the actual message 

4041 

4042 For more information, see https://redis.io/commands/xautoclaim 

4043 """ 

4044 try: 

4045 if int(min_idle_time) < 0: 

4046 raise DataError( 

4047 "XAUTOCLAIM min_idle_time must be a nonnegative integer" 

4048 ) 

4049 except TypeError: 

4050 pass 

4051 

4052 kwargs = {} 

4053 pieces = [name, groupname, consumername, min_idle_time, start_id] 

4054 

4055 try: 

4056 if int(count) < 0: 

4057 raise DataError("XPENDING count must be a integer >= 0") 

4058 pieces.extend([b"COUNT", count]) 

4059 except TypeError: 

4060 pass 

4061 if justid: 

4062 pieces.append(b"JUSTID") 

4063 kwargs["parse_justid"] = True 

4064 

4065 return self.execute_command("XAUTOCLAIM", *pieces, **kwargs) 

4066 

4067 def xclaim( 

4068 self, 

4069 name: KeyT, 

4070 groupname: GroupT, 

4071 consumername: ConsumerT, 

4072 min_idle_time: int, 

4073 message_ids: Union[List[StreamIdT], Tuple[StreamIdT]], 

4074 idle: Optional[int] = None, 

4075 time: Optional[int] = None, 

4076 retrycount: Optional[int] = None, 

4077 force: bool = False, 

4078 justid: bool = False, 

4079 ) -> ResponseT: 

4080 """ 

4081 Changes the ownership of a pending message. 

4082 

4083 name: name of the stream. 

4084 

4085 groupname: name of the consumer group. 

4086 

4087 consumername: name of a consumer that claims the message. 

4088 

4089 min_idle_time: filter messages that were idle less than this amount of 

4090 milliseconds 

4091 

4092 message_ids: non-empty list or tuple of message IDs to claim 

4093 

4094 idle: optional. Set the idle time (last time it was delivered) of the 

4095 message in ms 

4096 

4097 time: optional integer. This is the same as idle but instead of a 

4098 relative amount of milliseconds, it sets the idle time to a specific 

4099 Unix time (in milliseconds). 

4100 

4101 retrycount: optional integer. set the retry counter to the specified 

4102 value. This counter is incremented every time a message is delivered 

4103 again. 

4104 

4105 force: optional boolean, false by default. Creates the pending message 

4106 entry in the PEL even if certain specified IDs are not already in the 

4107 PEL assigned to a different client. 

4108 

4109 justid: optional boolean, false by default. Return just an array of IDs 

4110 of messages successfully claimed, without returning the actual message 

4111 

4112 For more information, see https://redis.io/commands/xclaim 

4113 """ 

4114 if not isinstance(min_idle_time, int) or min_idle_time < 0: 

4115 raise DataError("XCLAIM min_idle_time must be a non negative integer") 

4116 if not isinstance(message_ids, (list, tuple)) or not message_ids: 

4117 raise DataError( 

4118 "XCLAIM message_ids must be a non empty list or " 

4119 "tuple of message IDs to claim" 

4120 ) 

4121 

4122 kwargs = {} 

4123 pieces: list[EncodableT] = [name, groupname, consumername, str(min_idle_time)] 

4124 pieces.extend(list(message_ids)) 

4125 

4126 if idle is not None: 

4127 if not isinstance(idle, int): 

4128 raise DataError("XCLAIM idle must be an integer") 

4129 pieces.extend((b"IDLE", str(idle))) 

4130 if time is not None: 

4131 if not isinstance(time, int): 

4132 raise DataError("XCLAIM time must be an integer") 

4133 pieces.extend((b"TIME", str(time))) 

4134 if retrycount is not None: 

4135 if not isinstance(retrycount, int): 

4136 raise DataError("XCLAIM retrycount must be an integer") 

4137 pieces.extend((b"RETRYCOUNT", str(retrycount))) 

4138 

4139 if force: 

4140 if not isinstance(force, bool): 

4141 raise DataError("XCLAIM force must be a boolean") 

4142 pieces.append(b"FORCE") 

4143 if justid: 

4144 if not isinstance(justid, bool): 

4145 raise DataError("XCLAIM justid must be a boolean") 

4146 pieces.append(b"JUSTID") 

4147 kwargs["parse_justid"] = True 

4148 return self.execute_command("XCLAIM", *pieces, **kwargs) 

4149 

4150 def xdel(self, name: KeyT, *ids: StreamIdT) -> ResponseT: 

4151 """ 

4152 Deletes one or more messages from a stream. 

4153 

4154 Args: 

4155 name: name of the stream. 

4156 *ids: message ids to delete. 

4157 

4158 For more information, see https://redis.io/commands/xdel 

4159 """ 

4160 return self.execute_command("XDEL", name, *ids) 

4161 

4162 def xdelex( 

4163 self, 

4164 name: KeyT, 

4165 *ids: StreamIdT, 

4166 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF", 

4167 ) -> ResponseT: 

4168 """ 

4169 Extended version of XDEL that provides more control over how message entries 

4170 are deleted concerning consumer groups. 

4171 """ 

4172 if not ids: 

4173 raise DataError("XDELEX requires at least one message ID") 

4174 

4175 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

4176 raise DataError("XDELEX ref_policy must be one of: KEEPREF, DELREF, ACKED") 

4177 

4178 pieces = [name, ref_policy, "IDS", len(ids)] 

4179 pieces.extend(ids) 

4180 return self.execute_command("XDELEX", *pieces) 

4181 

4182 def xgroup_create( 

4183 self, 

4184 name: KeyT, 

4185 groupname: GroupT, 

4186 id: StreamIdT = "$", 

4187 mkstream: bool = False, 

4188 entries_read: Optional[int] = None, 

4189 ) -> ResponseT: 

4190 """ 

4191 Create a new consumer group associated with a stream. 

4192 name: name of the stream. 

4193 groupname: name of the consumer group. 

4194 id: ID of the last item in the stream to consider already delivered. 

4195 

4196 For more information, see https://redis.io/commands/xgroup-create 

4197 """ 

4198 pieces: list[EncodableT] = ["XGROUP CREATE", name, groupname, id] 

4199 if mkstream: 

4200 pieces.append(b"MKSTREAM") 

4201 if entries_read is not None: 

4202 pieces.extend(["ENTRIESREAD", entries_read]) 

4203 

4204 return self.execute_command(*pieces) 

4205 

4206 def xgroup_delconsumer( 

4207 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

4208 ) -> ResponseT: 

4209 """ 

4210 Remove a specific consumer from a consumer group. 

4211 Returns the number of pending messages that the consumer had before it 

4212 was deleted. 

4213 name: name of the stream. 

4214 groupname: name of the consumer group. 

4215 consumername: name of consumer to delete 

4216 

4217 For more information, see https://redis.io/commands/xgroup-delconsumer 

4218 """ 

4219 return self.execute_command("XGROUP DELCONSUMER", name, groupname, consumername) 

4220 

4221 def xgroup_destroy(self, name: KeyT, groupname: GroupT) -> ResponseT: 

4222 """ 

4223 Destroy a consumer group. 

4224 name: name of the stream. 

4225 groupname: name of the consumer group. 

4226 

4227 For more information, see https://redis.io/commands/xgroup-destroy 

4228 """ 

4229 return self.execute_command("XGROUP DESTROY", name, groupname) 

4230 

4231 def xgroup_createconsumer( 

4232 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

4233 ) -> ResponseT: 

4234 """ 

4235 Consumers in a consumer group are auto-created every time a new 

4236 consumer name is mentioned by some command. 

4237 They can be explicitly created by using this command. 

4238 name: name of the stream. 

4239 groupname: name of the consumer group. 

4240 consumername: name of consumer to create. 

4241 

4242 See: https://redis.io/commands/xgroup-createconsumer 

4243 """ 

4244 return self.execute_command( 

4245 "XGROUP CREATECONSUMER", name, groupname, consumername 

4246 ) 

4247 

4248 def xgroup_setid( 

4249 self, 

4250 name: KeyT, 

4251 groupname: GroupT, 

4252 id: StreamIdT, 

4253 entries_read: Optional[int] = None, 

4254 ) -> ResponseT: 

4255 """ 

4256 Set the consumer group last delivered ID to something else. 

4257 name: name of the stream. 

4258 groupname: name of the consumer group. 

4259 id: ID of the last item in the stream to consider already delivered. 

4260 

4261 For more information, see https://redis.io/commands/xgroup-setid 

4262 """ 

4263 pieces = [name, groupname, id] 

4264 if entries_read is not None: 

4265 pieces.extend(["ENTRIESREAD", entries_read]) 

4266 return self.execute_command("XGROUP SETID", *pieces) 

4267 

4268 def xinfo_consumers(self, name: KeyT, groupname: GroupT) -> ResponseT: 

4269 """ 

4270 Returns general information about the consumers in the group. 

4271 name: name of the stream. 

4272 groupname: name of the consumer group. 

4273 

4274 For more information, see https://redis.io/commands/xinfo-consumers 

4275 """ 

4276 return self.execute_command("XINFO CONSUMERS", name, groupname) 

4277 

4278 def xinfo_groups(self, name: KeyT) -> ResponseT: 

4279 """ 

4280 Returns general information about the consumer groups of the stream. 

4281 name: name of the stream. 

4282 

4283 For more information, see https://redis.io/commands/xinfo-groups 

4284 """ 

4285 return self.execute_command("XINFO GROUPS", name) 

4286 

4287 def xinfo_stream(self, name: KeyT, full: bool = False) -> ResponseT: 

4288 """ 

4289 Returns general information about the stream. 

4290 name: name of the stream. 

4291 full: optional boolean, false by default. Return full summary 

4292 

4293 For more information, see https://redis.io/commands/xinfo-stream 

4294 """ 

4295 pieces = [name] 

4296 options = {} 

4297 if full: 

4298 pieces.append(b"FULL") 

4299 options = {"full": full} 

4300 return self.execute_command("XINFO STREAM", *pieces, **options) 

4301 

4302 def xlen(self, name: KeyT) -> ResponseT: 

4303 """ 

4304 Returns the number of elements in a given stream. 

4305 

4306 For more information, see https://redis.io/commands/xlen 

4307 """ 

4308 return self.execute_command("XLEN", name, keys=[name]) 

4309 

4310 def xpending(self, name: KeyT, groupname: GroupT) -> ResponseT: 

4311 """ 

4312 Returns information about pending messages of a group. 

4313 name: name of the stream. 

4314 groupname: name of the consumer group. 

4315 

4316 For more information, see https://redis.io/commands/xpending 

4317 """ 

4318 return self.execute_command("XPENDING", name, groupname, keys=[name]) 

4319 

4320 def xpending_range( 

4321 self, 

4322 name: KeyT, 

4323 groupname: GroupT, 

4324 min: StreamIdT, 

4325 max: StreamIdT, 

4326 count: int, 

4327 consumername: Union[ConsumerT, None] = None, 

4328 idle: Optional[int] = None, 

4329 ) -> ResponseT: 

4330 """ 

4331 Returns information about pending messages, in a range. 

4332 

4333 name: name of the stream. 

4334 groupname: name of the consumer group. 

4335 idle: available from version 6.2. filter entries by their 

4336 idle-time, given in milliseconds (optional). 

4337 min: minimum stream ID. 

4338 max: maximum stream ID. 

4339 count: number of messages to return 

4340 consumername: name of a consumer to filter by (optional). 

4341 """ 

4342 if {min, max, count} == {None}: 

4343 if idle is not None or consumername is not None: 

4344 raise DataError( 

4345 "if XPENDING is provided with idle time" 

4346 " or consumername, it must be provided" 

4347 " with min, max and count parameters" 

4348 ) 

4349 return self.xpending(name, groupname) 

4350 

4351 pieces = [name, groupname] 

4352 if min is None or max is None or count is None: 

4353 raise DataError( 

4354 "XPENDING must be provided with min, max " 

4355 "and count parameters, or none of them." 

4356 ) 

4357 # idle 

4358 try: 

4359 if int(idle) < 0: 

4360 raise DataError("XPENDING idle must be a integer >= 0") 

4361 pieces.extend(["IDLE", idle]) 

4362 except TypeError: 

4363 pass 

4364 # count 

4365 try: 

4366 if int(count) < 0: 

4367 raise DataError("XPENDING count must be a integer >= 0") 

4368 pieces.extend([min, max, count]) 

4369 except TypeError: 

4370 pass 

4371 # consumername 

4372 if consumername: 

4373 pieces.append(consumername) 

4374 

4375 return self.execute_command("XPENDING", *pieces, parse_detail=True) 

4376 

4377 def xrange( 

4378 self, 

4379 name: KeyT, 

4380 min: StreamIdT = "-", 

4381 max: StreamIdT = "+", 

4382 count: Optional[int] = None, 

4383 ) -> ResponseT: 

4384 """ 

4385 Read stream values within an interval. 

4386 

4387 name: name of the stream. 

4388 

4389 start: first stream ID. defaults to '-', 

4390 meaning the earliest available. 

4391 

4392 finish: last stream ID. defaults to '+', 

4393 meaning the latest available. 

4394 

4395 count: if set, only return this many items, beginning with the 

4396 earliest available. 

4397 

4398 For more information, see https://redis.io/commands/xrange 

4399 """ 

4400 pieces = [min, max] 

4401 if count is not None: 

4402 if not isinstance(count, int) or count < 1: 

4403 raise DataError("XRANGE count must be a positive integer") 

4404 pieces.append(b"COUNT") 

4405 pieces.append(str(count)) 

4406 

4407 return self.execute_command("XRANGE", name, *pieces, keys=[name]) 

4408 

4409 def xread( 

4410 self, 

4411 streams: Dict[KeyT, StreamIdT], 

4412 count: Optional[int] = None, 

4413 block: Optional[int] = None, 

4414 ) -> ResponseT: 

4415 """ 

4416 Block and monitor multiple streams for new data. 

4417 

4418 streams: a dict of stream names to stream IDs, where 

4419 IDs indicate the last ID already seen. 

4420 

4421 count: if set, only return this many items, beginning with the 

4422 earliest available. 

4423 

4424 block: number of milliseconds to wait, if nothing already present. 

4425 

4426 For more information, see https://redis.io/commands/xread 

4427 """ 

4428 pieces = [] 

4429 if block is not None: 

4430 if not isinstance(block, int) or block < 0: 

4431 raise DataError("XREAD block must be a non-negative integer") 

4432 pieces.append(b"BLOCK") 

4433 pieces.append(str(block)) 

4434 if count is not None: 

4435 if not isinstance(count, int) or count < 1: 

4436 raise DataError("XREAD count must be a positive integer") 

4437 pieces.append(b"COUNT") 

4438 pieces.append(str(count)) 

4439 if not isinstance(streams, dict) or len(streams) == 0: 

4440 raise DataError("XREAD streams must be a non empty dict") 

4441 pieces.append(b"STREAMS") 

4442 keys, values = zip(*streams.items()) 

4443 pieces.extend(keys) 

4444 pieces.extend(values) 

4445 return self.execute_command("XREAD", *pieces, keys=keys) 

4446 

4447 def xreadgroup( 

4448 self, 

4449 groupname: str, 

4450 consumername: str, 

4451 streams: Dict[KeyT, StreamIdT], 

4452 count: Optional[int] = None, 

4453 block: Optional[int] = None, 

4454 noack: bool = False, 

4455 claim_min_idle_time: Optional[int] = None, 

4456 ) -> ResponseT: 

4457 """ 

4458 Read from a stream via a consumer group. 

4459 

4460 groupname: name of the consumer group. 

4461 

4462 consumername: name of the requesting consumer. 

4463 

4464 streams: a dict of stream names to stream IDs, where 

4465 IDs indicate the last ID already seen. 

4466 

4467 count: if set, only return this many items, beginning with the 

4468 earliest available. 

4469 

4470 block: number of milliseconds to wait, if nothing already present. 

4471 noack: do not add messages to the PEL 

4472 

4473 claim_min_idle_time: accepts an integer type and represents a 

4474 time interval in milliseconds 

4475 

4476 For more information, see https://redis.io/commands/xreadgroup 

4477 """ 

4478 options = {} 

4479 pieces: list[EncodableT] = [b"GROUP", groupname, consumername] 

4480 if count is not None: 

4481 if not isinstance(count, int) or count < 1: 

4482 raise DataError("XREADGROUP count must be a positive integer") 

4483 pieces.append(b"COUNT") 

4484 pieces.append(str(count)) 

4485 if block is not None: 

4486 if not isinstance(block, int) or block < 0: 

4487 raise DataError("XREADGROUP block must be a non-negative integer") 

4488 pieces.append(b"BLOCK") 

4489 pieces.append(str(block)) 

4490 if noack: 

4491 pieces.append(b"NOACK") 

4492 if claim_min_idle_time is not None: 

4493 if not isinstance(claim_min_idle_time, int) or claim_min_idle_time < 0: 

4494 raise DataError( 

4495 "XREADGROUP claim_min_idle_time must be a non-negative integer" 

4496 ) 

4497 pieces.append(b"CLAIM") 

4498 pieces.append(claim_min_idle_time) 

4499 options["claim_min_idle_time"] = claim_min_idle_time 

4500 if not isinstance(streams, dict) or len(streams) == 0: 

4501 raise DataError("XREADGROUP streams must be a non empty dict") 

4502 pieces.append(b"STREAMS") 

4503 pieces.extend(streams.keys()) 

4504 pieces.extend(streams.values()) 

4505 return self.execute_command("XREADGROUP", *pieces, **options) 

4506 

4507 def xrevrange( 

4508 self, 

4509 name: KeyT, 

4510 max: StreamIdT = "+", 

4511 min: StreamIdT = "-", 

4512 count: Optional[int] = None, 

4513 ) -> ResponseT: 

4514 """ 

4515 Read stream values within an interval, in reverse order. 

4516 

4517 name: name of the stream 

4518 

4519 start: first stream ID. defaults to '+', 

4520 meaning the latest available. 

4521 

4522 finish: last stream ID. defaults to '-', 

4523 meaning the earliest available. 

4524 

4525 count: if set, only return this many items, beginning with the 

4526 latest available. 

4527 

4528 For more information, see https://redis.io/commands/xrevrange 

4529 """ 

4530 pieces: list[EncodableT] = [max, min] 

4531 if count is not None: 

4532 if not isinstance(count, int) or count < 1: 

4533 raise DataError("XREVRANGE count must be a positive integer") 

4534 pieces.append(b"COUNT") 

4535 pieces.append(str(count)) 

4536 

4537 return self.execute_command("XREVRANGE", name, *pieces, keys=[name]) 

4538 

4539 def xtrim( 

4540 self, 

4541 name: KeyT, 

4542 maxlen: Optional[int] = None, 

4543 approximate: bool = True, 

4544 minid: Union[StreamIdT, None] = None, 

4545 limit: Optional[int] = None, 

4546 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None, 

4547 ) -> ResponseT: 

4548 """ 

4549 Trims old messages from a stream. 

4550 name: name of the stream. 

4551 maxlen: truncate old stream messages beyond this size 

4552 Can't be specified with minid. 

4553 approximate: actual stream length may be slightly more than maxlen 

4554 minid: the minimum id in the stream to query 

4555 Can't be specified with maxlen. 

4556 limit: specifies the maximum number of entries to retrieve 

4557 ref_policy: optional reference policy for consumer groups: 

4558 - KEEPREF (default): Trims entries but preserves references in consumer groups' PEL 

4559 - DELREF: Trims entries and removes all references from consumer groups' PEL 

4560 - ACKED: Only trims entries that were read and acknowledged by all consumer groups 

4561 

4562 For more information, see https://redis.io/commands/xtrim 

4563 """ 

4564 pieces: list[EncodableT] = [] 

4565 if maxlen is not None and minid is not None: 

4566 raise DataError("Only one of ``maxlen`` or ``minid`` may be specified") 

4567 

4568 if maxlen is None and minid is None: 

4569 raise DataError("One of ``maxlen`` or ``minid`` must be specified") 

4570 

4571 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

4572 raise DataError("XTRIM ref_policy must be one of: KEEPREF, DELREF, ACKED") 

4573 

4574 if maxlen is not None: 

4575 pieces.append(b"MAXLEN") 

4576 if minid is not None: 

4577 pieces.append(b"MINID") 

4578 if approximate: 

4579 pieces.append(b"~") 

4580 if maxlen is not None: 

4581 pieces.append(maxlen) 

4582 if minid is not None: 

4583 pieces.append(minid) 

4584 if limit is not None: 

4585 pieces.append(b"LIMIT") 

4586 pieces.append(limit) 

4587 if ref_policy is not None: 

4588 pieces.append(ref_policy) 

4589 

4590 return self.execute_command("XTRIM", name, *pieces) 

4591 

4592 

4593AsyncStreamCommands = StreamCommands 

4594 

4595 

4596class SortedSetCommands(CommandsProtocol): 

4597 """ 

4598 Redis commands for Sorted Sets data type. 

4599 see: https://redis.io/topics/data-types-intro#redis-sorted-sets 

4600 """ 

4601 

4602 def zadd( 

4603 self, 

4604 name: KeyT, 

4605 mapping: Mapping[AnyKeyT, EncodableT], 

4606 nx: bool = False, 

4607 xx: bool = False, 

4608 ch: bool = False, 

4609 incr: bool = False, 

4610 gt: bool = False, 

4611 lt: bool = False, 

4612 ) -> ResponseT: 

4613 """ 

4614 Set any number of element-name, score pairs to the key ``name``. Pairs 

4615 are specified as a dict of element-names keys to score values. 

4616 

4617 ``nx`` forces ZADD to only create new elements and not to update 

4618 scores for elements that already exist. 

4619 

4620 ``xx`` forces ZADD to only update scores of elements that already 

4621 exist. New elements will not be added. 

4622 

4623 ``ch`` modifies the return value to be the numbers of elements changed. 

4624 Changed elements include new elements that were added and elements 

4625 whose scores changed. 

4626 

4627 ``incr`` modifies ZADD to behave like ZINCRBY. In this mode only a 

4628 single element/score pair can be specified and the score is the amount 

4629 the existing score will be incremented by. When using this mode the 

4630 return value of ZADD will be the new score of the element. 

4631 

4632 ``lt`` only updates existing elements if the new score is less than 

4633 the current score. This flag doesn't prevent adding new elements. 

4634 

4635 ``gt`` only updates existing elements if the new score is greater than 

4636 the current score. This flag doesn't prevent adding new elements. 

4637 

4638 The return value of ZADD varies based on the mode specified. With no 

4639 options, ZADD returns the number of new elements added to the sorted 

4640 set. 

4641 

4642 ``nx``, ``lt``, and ``gt`` are mutually exclusive options. 

4643 

4644 See: https://redis.io/commands/ZADD 

4645 """ 

4646 if not mapping: 

4647 raise DataError("ZADD requires at least one element/score pair") 

4648 if nx and xx: 

4649 raise DataError("ZADD allows either 'nx' or 'xx', not both") 

4650 if gt and lt: 

4651 raise DataError("ZADD allows either 'gt' or 'lt', not both") 

4652 if incr and len(mapping) != 1: 

4653 raise DataError( 

4654 "ZADD option 'incr' only works when passing a single element/score pair" 

4655 ) 

4656 if nx and (gt or lt): 

4657 raise DataError("Only one of 'nx', 'lt', or 'gr' may be defined.") 

4658 

4659 pieces: list[EncodableT] = [] 

4660 options = {} 

4661 if nx: 

4662 pieces.append(b"NX") 

4663 if xx: 

4664 pieces.append(b"XX") 

4665 if ch: 

4666 pieces.append(b"CH") 

4667 if incr: 

4668 pieces.append(b"INCR") 

4669 options["as_score"] = True 

4670 if gt: 

4671 pieces.append(b"GT") 

4672 if lt: 

4673 pieces.append(b"LT") 

4674 for pair in mapping.items(): 

4675 pieces.append(pair[1]) 

4676 pieces.append(pair[0]) 

4677 return self.execute_command("ZADD", name, *pieces, **options) 

4678 

4679 def zcard(self, name: KeyT) -> ResponseT: 

4680 """ 

4681 Return the number of elements in the sorted set ``name`` 

4682 

4683 For more information, see https://redis.io/commands/zcard 

4684 """ 

4685 return self.execute_command("ZCARD", name, keys=[name]) 

4686 

4687 def zcount(self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT) -> ResponseT: 

4688 """ 

4689 Returns the number of elements in the sorted set at key ``name`` with 

4690 a score between ``min`` and ``max``. 

4691 

4692 For more information, see https://redis.io/commands/zcount 

4693 """ 

4694 return self.execute_command("ZCOUNT", name, min, max, keys=[name]) 

4695 

4696 def zdiff(self, keys: KeysT, withscores: bool = False) -> ResponseT: 

4697 """ 

4698 Returns the difference between the first and all successive input 

4699 sorted sets provided in ``keys``. 

4700 

4701 For more information, see https://redis.io/commands/zdiff 

4702 """ 

4703 pieces = [len(keys), *keys] 

4704 if withscores: 

4705 pieces.append("WITHSCORES") 

4706 return self.execute_command("ZDIFF", *pieces, keys=keys) 

4707 

4708 def zdiffstore(self, dest: KeyT, keys: KeysT) -> ResponseT: 

4709 """ 

4710 Computes the difference between the first and all successive input 

4711 sorted sets provided in ``keys`` and stores the result in ``dest``. 

4712 

4713 For more information, see https://redis.io/commands/zdiffstore 

4714 """ 

4715 pieces = [len(keys), *keys] 

4716 return self.execute_command("ZDIFFSTORE", dest, *pieces) 

4717 

4718 def zincrby(self, name: KeyT, amount: float, value: EncodableT) -> ResponseT: 

4719 """ 

4720 Increment the score of ``value`` in sorted set ``name`` by ``amount`` 

4721 

4722 For more information, see https://redis.io/commands/zincrby 

4723 """ 

4724 return self.execute_command("ZINCRBY", name, amount, value) 

4725 

4726 def zinter( 

4727 self, keys: KeysT, aggregate: Optional[str] = None, withscores: bool = False 

4728 ) -> ResponseT: 

4729 """ 

4730 Return the intersect of multiple sorted sets specified by ``keys``. 

4731 With the ``aggregate`` option, it is possible to specify how the 

4732 results of the union are aggregated. This option defaults to SUM, 

4733 where the score of an element is summed across the inputs where it 

4734 exists. When this option is set to either MIN or MAX, the resulting 

4735 set will contain the minimum or maximum score of an element across 

4736 the inputs where it exists. 

4737 

4738 For more information, see https://redis.io/commands/zinter 

4739 """ 

4740 return self._zaggregate("ZINTER", None, keys, aggregate, withscores=withscores) 

4741 

4742 def zinterstore( 

4743 self, 

4744 dest: KeyT, 

4745 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4746 aggregate: Optional[str] = None, 

4747 ) -> ResponseT: 

4748 """ 

4749 Intersect multiple sorted sets specified by ``keys`` into a new 

4750 sorted set, ``dest``. Scores in the destination will be aggregated 

4751 based on the ``aggregate``. This option defaults to SUM, where the 

4752 score of an element is summed across the inputs where it exists. 

4753 When this option is set to either MIN or MAX, the resulting set will 

4754 contain the minimum or maximum score of an element across the inputs 

4755 where it exists. 

4756 

4757 For more information, see https://redis.io/commands/zinterstore 

4758 """ 

4759 return self._zaggregate("ZINTERSTORE", dest, keys, aggregate) 

4760 

4761 def zintercard( 

4762 self, numkeys: int, keys: List[str], limit: int = 0 

4763 ) -> Union[Awaitable[int], int]: 

4764 """ 

4765 Return the cardinality of the intersect of multiple sorted sets 

4766 specified by ``keys``. 

4767 When LIMIT provided (defaults to 0 and means unlimited), if the intersection 

4768 cardinality reaches limit partway through the computation, the algorithm will 

4769 exit and yield limit as the cardinality 

4770 

4771 For more information, see https://redis.io/commands/zintercard 

4772 """ 

4773 args = [numkeys, *keys, "LIMIT", limit] 

4774 return self.execute_command("ZINTERCARD", *args, keys=keys) 

4775 

4776 def zlexcount(self, name, min, max): 

4777 """ 

4778 Return the number of items in the sorted set ``name`` between the 

4779 lexicographical range ``min`` and ``max``. 

4780 

4781 For more information, see https://redis.io/commands/zlexcount 

4782 """ 

4783 return self.execute_command("ZLEXCOUNT", name, min, max, keys=[name]) 

4784 

4785 def zpopmax(self, name: KeyT, count: Optional[int] = None) -> ResponseT: 

4786 """ 

4787 Remove and return up to ``count`` members with the highest scores 

4788 from the sorted set ``name``. 

4789 

4790 For more information, see https://redis.io/commands/zpopmax 

4791 """ 

4792 args = (count is not None) and [count] or [] 

4793 options = {"withscores": True} 

4794 return self.execute_command("ZPOPMAX", name, *args, **options) 

4795 

4796 def zpopmin(self, name: KeyT, count: Optional[int] = None) -> ResponseT: 

4797 """ 

4798 Remove and return up to ``count`` members with the lowest scores 

4799 from the sorted set ``name``. 

4800 

4801 For more information, see https://redis.io/commands/zpopmin 

4802 """ 

4803 args = (count is not None) and [count] or [] 

4804 options = {"withscores": True} 

4805 return self.execute_command("ZPOPMIN", name, *args, **options) 

4806 

4807 def zrandmember( 

4808 self, key: KeyT, count: Optional[int] = None, withscores: bool = False 

4809 ) -> ResponseT: 

4810 """ 

4811 Return a random element from the sorted set value stored at key. 

4812 

4813 ``count`` if the argument is positive, return an array of distinct 

4814 fields. If called with a negative count, the behavior changes and 

4815 the command is allowed to return the same field multiple times. 

4816 In this case, the number of returned fields is the absolute value 

4817 of the specified count. 

4818 

4819 ``withscores`` The optional WITHSCORES modifier changes the reply so it 

4820 includes the respective scores of the randomly selected elements from 

4821 the sorted set. 

4822 

4823 For more information, see https://redis.io/commands/zrandmember 

4824 """ 

4825 params = [] 

4826 if count is not None: 

4827 params.append(count) 

4828 if withscores: 

4829 params.append("WITHSCORES") 

4830 

4831 return self.execute_command("ZRANDMEMBER", key, *params) 

4832 

4833 def bzpopmax(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4834 """ 

4835 ZPOPMAX a value off of the first non-empty sorted set 

4836 named in the ``keys`` list. 

4837 

4838 If none of the sorted sets in ``keys`` has a value to ZPOPMAX, 

4839 then block for ``timeout`` seconds, or until a member gets added 

4840 to one of the sorted sets. 

4841 

4842 If timeout is 0, then block indefinitely. 

4843 

4844 For more information, see https://redis.io/commands/bzpopmax 

4845 """ 

4846 if timeout is None: 

4847 timeout = 0 

4848 keys = list_or_args(keys, None) 

4849 keys.append(timeout) 

4850 return self.execute_command("BZPOPMAX", *keys) 

4851 

4852 def bzpopmin(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4853 """ 

4854 ZPOPMIN a value off of the first non-empty sorted set 

4855 named in the ``keys`` list. 

4856 

4857 If none of the sorted sets in ``keys`` has a value to ZPOPMIN, 

4858 then block for ``timeout`` seconds, or until a member gets added 

4859 to one of the sorted sets. 

4860 

4861 If timeout is 0, then block indefinitely. 

4862 

4863 For more information, see https://redis.io/commands/bzpopmin 

4864 """ 

4865 if timeout is None: 

4866 timeout = 0 

4867 keys: list[EncodableT] = list_or_args(keys, None) 

4868 keys.append(timeout) 

4869 return self.execute_command("BZPOPMIN", *keys) 

4870 

4871 def zmpop( 

4872 self, 

4873 num_keys: int, 

4874 keys: List[str], 

4875 min: Optional[bool] = False, 

4876 max: Optional[bool] = False, 

4877 count: Optional[int] = 1, 

4878 ) -> Union[Awaitable[list], list]: 

4879 """ 

4880 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4881 named in the ``keys`` list. 

4882 For more information, see https://redis.io/commands/zmpop 

4883 """ 

4884 args = [num_keys] + keys 

4885 if (min and max) or (not min and not max): 

4886 raise DataError 

4887 elif min: 

4888 args.append("MIN") 

4889 else: 

4890 args.append("MAX") 

4891 if count != 1: 

4892 args.extend(["COUNT", count]) 

4893 

4894 return self.execute_command("ZMPOP", *args) 

4895 

4896 def bzmpop( 

4897 self, 

4898 timeout: float, 

4899 numkeys: int, 

4900 keys: List[str], 

4901 min: Optional[bool] = False, 

4902 max: Optional[bool] = False, 

4903 count: Optional[int] = 1, 

4904 ) -> Optional[list]: 

4905 """ 

4906 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4907 named in the ``keys`` list. 

4908 

4909 If none of the sorted sets in ``keys`` has a value to pop, 

4910 then block for ``timeout`` seconds, or until a member gets added 

4911 to one of the sorted sets. 

4912 

4913 If timeout is 0, then block indefinitely. 

4914 

4915 For more information, see https://redis.io/commands/bzmpop 

4916 """ 

4917 args = [timeout, numkeys, *keys] 

4918 if (min and max) or (not min and not max): 

4919 raise DataError("Either min or max, but not both must be set") 

4920 elif min: 

4921 args.append("MIN") 

4922 else: 

4923 args.append("MAX") 

4924 args.extend(["COUNT", count]) 

4925 

4926 return self.execute_command("BZMPOP", *args) 

4927 

4928 def _zrange( 

4929 self, 

4930 command, 

4931 dest: Union[KeyT, None], 

4932 name: KeyT, 

4933 start: EncodableT, 

4934 end: EncodableT, 

4935 desc: bool = False, 

4936 byscore: bool = False, 

4937 bylex: bool = False, 

4938 withscores: bool = False, 

4939 score_cast_func: Union[type, Callable, None] = float, 

4940 offset: Optional[int] = None, 

4941 num: Optional[int] = None, 

4942 ) -> ResponseT: 

4943 if byscore and bylex: 

4944 raise DataError("``byscore`` and ``bylex`` can not be specified together.") 

4945 if (offset is not None and num is None) or (num is not None and offset is None): 

4946 raise DataError("``offset`` and ``num`` must both be specified.") 

4947 if bylex and withscores: 

4948 raise DataError( 

4949 "``withscores`` not supported in combination with ``bylex``." 

4950 ) 

4951 pieces = [command] 

4952 if dest: 

4953 pieces.append(dest) 

4954 pieces.extend([name, start, end]) 

4955 if byscore: 

4956 pieces.append("BYSCORE") 

4957 if bylex: 

4958 pieces.append("BYLEX") 

4959 if desc: 

4960 pieces.append("REV") 

4961 if offset is not None and num is not None: 

4962 pieces.extend(["LIMIT", offset, num]) 

4963 if withscores: 

4964 pieces.append("WITHSCORES") 

4965 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4966 options["keys"] = [name] 

4967 return self.execute_command(*pieces, **options) 

4968 

4969 def zrange( 

4970 self, 

4971 name: KeyT, 

4972 start: EncodableT, 

4973 end: EncodableT, 

4974 desc: bool = False, 

4975 withscores: bool = False, 

4976 score_cast_func: Union[type, Callable] = float, 

4977 byscore: bool = False, 

4978 bylex: bool = False, 

4979 offset: Optional[int] = None, 

4980 num: Optional[int] = None, 

4981 ) -> ResponseT: 

4982 """ 

4983 Return a range of values from sorted set ``name`` between 

4984 ``start`` and ``end`` sorted in ascending order. 

4985 

4986 ``start`` and ``end`` can be negative, indicating the end of the range. 

4987 

4988 ``desc`` a boolean indicating whether to sort the results in reversed 

4989 order. 

4990 

4991 ``withscores`` indicates to return the scores along with the values. 

4992 The return type is a list of (value, score) pairs. 

4993 

4994 ``score_cast_func`` a callable used to cast the score return value. 

4995 

4996 ``byscore`` when set to True, returns the range of elements from the 

4997 sorted set having scores equal or between ``start`` and ``end``. 

4998 

4999 ``bylex`` when set to True, returns the range of elements from the 

5000 sorted set between the ``start`` and ``end`` lexicographical closed 

5001 range intervals. 

5002 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

5003 whether the range interval is exclusive or inclusive, respectively. 

5004 

5005 ``offset`` and ``num`` are specified, then return a slice of the range. 

5006 Can't be provided when using ``bylex``. 

5007 

5008 For more information, see https://redis.io/commands/zrange 

5009 """ 

5010 # Need to support ``desc`` also when using old redis version 

5011 # because it was supported in 3.5.3 (of redis-py) 

5012 if not byscore and not bylex and (offset is None and num is None) and desc: 

5013 return self.zrevrange(name, start, end, withscores, score_cast_func) 

5014 

5015 return self._zrange( 

5016 "ZRANGE", 

5017 None, 

5018 name, 

5019 start, 

5020 end, 

5021 desc, 

5022 byscore, 

5023 bylex, 

5024 withscores, 

5025 score_cast_func, 

5026 offset, 

5027 num, 

5028 ) 

5029 

5030 def zrevrange( 

5031 self, 

5032 name: KeyT, 

5033 start: int, 

5034 end: int, 

5035 withscores: bool = False, 

5036 score_cast_func: Union[type, Callable] = float, 

5037 ) -> ResponseT: 

5038 """ 

5039 Return a range of values from sorted set ``name`` between 

5040 ``start`` and ``end`` sorted in descending order. 

5041 

5042 ``start`` and ``end`` can be negative, indicating the end of the range. 

5043 

5044 ``withscores`` indicates to return the scores along with the values 

5045 The return type is a list of (value, score) pairs 

5046 

5047 ``score_cast_func`` a callable used to cast the score return value 

5048 

5049 For more information, see https://redis.io/commands/zrevrange 

5050 """ 

5051 pieces = ["ZREVRANGE", name, start, end] 

5052 if withscores: 

5053 pieces.append(b"WITHSCORES") 

5054 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

5055 options["keys"] = name 

5056 return self.execute_command(*pieces, **options) 

5057 

5058 def zrangestore( 

5059 self, 

5060 dest: KeyT, 

5061 name: KeyT, 

5062 start: EncodableT, 

5063 end: EncodableT, 

5064 byscore: bool = False, 

5065 bylex: bool = False, 

5066 desc: bool = False, 

5067 offset: Optional[int] = None, 

5068 num: Optional[int] = None, 

5069 ) -> ResponseT: 

5070 """ 

5071 Stores in ``dest`` the result of a range of values from sorted set 

5072 ``name`` between ``start`` and ``end`` sorted in ascending order. 

5073 

5074 ``start`` and ``end`` can be negative, indicating the end of the range. 

5075 

5076 ``byscore`` when set to True, returns the range of elements from the 

5077 sorted set having scores equal or between ``start`` and ``end``. 

5078 

5079 ``bylex`` when set to True, returns the range of elements from the 

5080 sorted set between the ``start`` and ``end`` lexicographical closed 

5081 range intervals. 

5082 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

5083 whether the range interval is exclusive or inclusive, respectively. 

5084 

5085 ``desc`` a boolean indicating whether to sort the results in reversed 

5086 order. 

5087 

5088 ``offset`` and ``num`` are specified, then return a slice of the range. 

5089 Can't be provided when using ``bylex``. 

5090 

5091 For more information, see https://redis.io/commands/zrangestore 

5092 """ 

5093 return self._zrange( 

5094 "ZRANGESTORE", 

5095 dest, 

5096 name, 

5097 start, 

5098 end, 

5099 desc, 

5100 byscore, 

5101 bylex, 

5102 False, 

5103 None, 

5104 offset, 

5105 num, 

5106 ) 

5107 

5108 def zrangebylex( 

5109 self, 

5110 name: KeyT, 

5111 min: EncodableT, 

5112 max: EncodableT, 

5113 start: Optional[int] = None, 

5114 num: Optional[int] = None, 

5115 ) -> ResponseT: 

5116 """ 

5117 Return the lexicographical range of values from sorted set ``name`` 

5118 between ``min`` and ``max``. 

5119 

5120 If ``start`` and ``num`` are specified, then return a slice of the 

5121 range. 

5122 

5123 For more information, see https://redis.io/commands/zrangebylex 

5124 """ 

5125 if (start is not None and num is None) or (num is not None and start is None): 

5126 raise DataError("``start`` and ``num`` must both be specified") 

5127 pieces = ["ZRANGEBYLEX", name, min, max] 

5128 if start is not None and num is not None: 

5129 pieces.extend([b"LIMIT", start, num]) 

5130 return self.execute_command(*pieces, keys=[name]) 

5131 

5132 def zrevrangebylex( 

5133 self, 

5134 name: KeyT, 

5135 max: EncodableT, 

5136 min: EncodableT, 

5137 start: Optional[int] = None, 

5138 num: Optional[int] = None, 

5139 ) -> ResponseT: 

5140 """ 

5141 Return the reversed lexicographical range of values from sorted set 

5142 ``name`` between ``max`` and ``min``. 

5143 

5144 If ``start`` and ``num`` are specified, then return a slice of the 

5145 range. 

5146 

5147 For more information, see https://redis.io/commands/zrevrangebylex 

5148 """ 

5149 if (start is not None and num is None) or (num is not None and start is None): 

5150 raise DataError("``start`` and ``num`` must both be specified") 

5151 pieces = ["ZREVRANGEBYLEX", name, max, min] 

5152 if start is not None and num is not None: 

5153 pieces.extend(["LIMIT", start, num]) 

5154 return self.execute_command(*pieces, keys=[name]) 

5155 

5156 def zrangebyscore( 

5157 self, 

5158 name: KeyT, 

5159 min: ZScoreBoundT, 

5160 max: ZScoreBoundT, 

5161 start: Optional[int] = None, 

5162 num: Optional[int] = None, 

5163 withscores: bool = False, 

5164 score_cast_func: Union[type, Callable] = float, 

5165 ) -> ResponseT: 

5166 """ 

5167 Return a range of values from the sorted set ``name`` with scores 

5168 between ``min`` and ``max``. 

5169 

5170 If ``start`` and ``num`` are specified, then return a slice 

5171 of the range. 

5172 

5173 ``withscores`` indicates to return the scores along with the values. 

5174 The return type is a list of (value, score) pairs 

5175 

5176 `score_cast_func`` a callable used to cast the score return value 

5177 

5178 For more information, see https://redis.io/commands/zrangebyscore 

5179 """ 

5180 if (start is not None and num is None) or (num is not None and start is None): 

5181 raise DataError("``start`` and ``num`` must both be specified") 

5182 pieces = ["ZRANGEBYSCORE", name, min, max] 

5183 if start is not None and num is not None: 

5184 pieces.extend(["LIMIT", start, num]) 

5185 if withscores: 

5186 pieces.append("WITHSCORES") 

5187 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

5188 options["keys"] = [name] 

5189 return self.execute_command(*pieces, **options) 

5190 

5191 def zrevrangebyscore( 

5192 self, 

5193 name: KeyT, 

5194 max: ZScoreBoundT, 

5195 min: ZScoreBoundT, 

5196 start: Optional[int] = None, 

5197 num: Optional[int] = None, 

5198 withscores: bool = False, 

5199 score_cast_func: Union[type, Callable] = float, 

5200 ): 

5201 """ 

5202 Return a range of values from the sorted set ``name`` with scores 

5203 between ``min`` and ``max`` in descending order. 

5204 

5205 If ``start`` and ``num`` are specified, then return a slice 

5206 of the range. 

5207 

5208 ``withscores`` indicates to return the scores along with the values. 

5209 The return type is a list of (value, score) pairs 

5210 

5211 ``score_cast_func`` a callable used to cast the score return value 

5212 

5213 For more information, see https://redis.io/commands/zrevrangebyscore 

5214 """ 

5215 if (start is not None and num is None) or (num is not None and start is None): 

5216 raise DataError("``start`` and ``num`` must both be specified") 

5217 pieces = ["ZREVRANGEBYSCORE", name, max, min] 

5218 if start is not None and num is not None: 

5219 pieces.extend(["LIMIT", start, num]) 

5220 if withscores: 

5221 pieces.append("WITHSCORES") 

5222 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

5223 options["keys"] = [name] 

5224 return self.execute_command(*pieces, **options) 

5225 

5226 def zrank( 

5227 self, 

5228 name: KeyT, 

5229 value: EncodableT, 

5230 withscore: bool = False, 

5231 score_cast_func: Union[type, Callable] = float, 

5232 ) -> ResponseT: 

5233 """ 

5234 Returns a 0-based value indicating the rank of ``value`` in sorted set 

5235 ``name``. 

5236 The optional WITHSCORE argument supplements the command's 

5237 reply with the score of the element returned. 

5238 

5239 ``score_cast_func`` a callable used to cast the score return value 

5240 

5241 For more information, see https://redis.io/commands/zrank 

5242 """ 

5243 pieces = ["ZRANK", name, value] 

5244 if withscore: 

5245 pieces.append("WITHSCORE") 

5246 

5247 options = {"withscore": withscore, "score_cast_func": score_cast_func} 

5248 

5249 return self.execute_command(*pieces, **options) 

5250 

5251 def zrem(self, name: KeyT, *values: FieldT) -> ResponseT: 

5252 """ 

5253 Remove member ``values`` from sorted set ``name`` 

5254 

5255 For more information, see https://redis.io/commands/zrem 

5256 """ 

5257 return self.execute_command("ZREM", name, *values) 

5258 

5259 def zremrangebylex(self, name: KeyT, min: EncodableT, max: EncodableT) -> ResponseT: 

5260 """ 

5261 Remove all elements in the sorted set ``name`` between the 

5262 lexicographical range specified by ``min`` and ``max``. 

5263 

5264 Returns the number of elements removed. 

5265 

5266 For more information, see https://redis.io/commands/zremrangebylex 

5267 """ 

5268 return self.execute_command("ZREMRANGEBYLEX", name, min, max) 

5269 

5270 def zremrangebyrank(self, name: KeyT, min: int, max: int) -> ResponseT: 

5271 """ 

5272 Remove all elements in the sorted set ``name`` with ranks between 

5273 ``min`` and ``max``. Values are 0-based, ordered from smallest score 

5274 to largest. Values can be negative indicating the highest scores. 

5275 Returns the number of elements removed 

5276 

5277 For more information, see https://redis.io/commands/zremrangebyrank 

5278 """ 

5279 return self.execute_command("ZREMRANGEBYRANK", name, min, max) 

5280 

5281 def zremrangebyscore( 

5282 self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT 

5283 ) -> ResponseT: 

5284 """ 

5285 Remove all elements in the sorted set ``name`` with scores 

5286 between ``min`` and ``max``. Returns the number of elements removed. 

5287 

5288 For more information, see https://redis.io/commands/zremrangebyscore 

5289 """ 

5290 return self.execute_command("ZREMRANGEBYSCORE", name, min, max) 

5291 

5292 def zrevrank( 

5293 self, 

5294 name: KeyT, 

5295 value: EncodableT, 

5296 withscore: bool = False, 

5297 score_cast_func: Union[type, Callable] = float, 

5298 ) -> ResponseT: 

5299 """ 

5300 Returns a 0-based value indicating the descending rank of 

5301 ``value`` in sorted set ``name``. 

5302 The optional ``withscore`` argument supplements the command's 

5303 reply with the score of the element returned. 

5304 

5305 ``score_cast_func`` a callable used to cast the score return value 

5306 

5307 For more information, see https://redis.io/commands/zrevrank 

5308 """ 

5309 pieces = ["ZREVRANK", name, value] 

5310 if withscore: 

5311 pieces.append("WITHSCORE") 

5312 

5313 options = {"withscore": withscore, "score_cast_func": score_cast_func} 

5314 

5315 return self.execute_command(*pieces, **options) 

5316 

5317 def zscore(self, name: KeyT, value: EncodableT) -> ResponseT: 

5318 """ 

5319 Return the score of element ``value`` in sorted set ``name`` 

5320 

5321 For more information, see https://redis.io/commands/zscore 

5322 """ 

5323 return self.execute_command("ZSCORE", name, value, keys=[name]) 

5324 

5325 def zunion( 

5326 self, 

5327 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

5328 aggregate: Optional[str] = None, 

5329 withscores: bool = False, 

5330 score_cast_func: Union[type, Callable] = float, 

5331 ) -> ResponseT: 

5332 """ 

5333 Return the union of multiple sorted sets specified by ``keys``. 

5334 ``keys`` can be provided as dictionary of keys and their weights. 

5335 Scores will be aggregated based on the ``aggregate``, or SUM if 

5336 none is provided. 

5337 

5338 ``score_cast_func`` a callable used to cast the score return value 

5339 

5340 For more information, see https://redis.io/commands/zunion 

5341 """ 

5342 return self._zaggregate( 

5343 "ZUNION", 

5344 None, 

5345 keys, 

5346 aggregate, 

5347 withscores=withscores, 

5348 score_cast_func=score_cast_func, 

5349 ) 

5350 

5351 def zunionstore( 

5352 self, 

5353 dest: KeyT, 

5354 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

5355 aggregate: Optional[str] = None, 

5356 ) -> ResponseT: 

5357 """ 

5358 Union multiple sorted sets specified by ``keys`` into 

5359 a new sorted set, ``dest``. Scores in the destination will be 

5360 aggregated based on the ``aggregate``, or SUM if none is provided. 

5361 

5362 For more information, see https://redis.io/commands/zunionstore 

5363 """ 

5364 return self._zaggregate("ZUNIONSTORE", dest, keys, aggregate) 

5365 

5366 def zmscore(self, key: KeyT, members: List[str]) -> ResponseT: 

5367 """ 

5368 Returns the scores associated with the specified members 

5369 in the sorted set stored at key. 

5370 ``members`` should be a list of the member name. 

5371 Return type is a list of score. 

5372 If the member does not exist, a None will be returned 

5373 in corresponding position. 

5374 

5375 For more information, see https://redis.io/commands/zmscore 

5376 """ 

5377 if not members: 

5378 raise DataError("ZMSCORE members must be a non-empty list") 

5379 pieces = [key] + members 

5380 return self.execute_command("ZMSCORE", *pieces, keys=[key]) 

5381 

5382 def _zaggregate( 

5383 self, 

5384 command: str, 

5385 dest: Union[KeyT, None], 

5386 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

5387 aggregate: Optional[str] = None, 

5388 **options, 

5389 ) -> ResponseT: 

5390 pieces: list[EncodableT] = [command] 

5391 if dest is not None: 

5392 pieces.append(dest) 

5393 pieces.append(len(keys)) 

5394 if isinstance(keys, dict): 

5395 keys, weights = keys.keys(), keys.values() 

5396 else: 

5397 weights = None 

5398 pieces.extend(keys) 

5399 if weights: 

5400 pieces.append(b"WEIGHTS") 

5401 pieces.extend(weights) 

5402 if aggregate: 

5403 if aggregate.upper() in ["SUM", "MIN", "MAX"]: 

5404 pieces.append(b"AGGREGATE") 

5405 pieces.append(aggregate) 

5406 else: 

5407 raise DataError("aggregate can be sum, min or max.") 

5408 if options.get("withscores", False): 

5409 pieces.append(b"WITHSCORES") 

5410 options["keys"] = keys 

5411 return self.execute_command(*pieces, **options) 

5412 

5413 

5414AsyncSortedSetCommands = SortedSetCommands 

5415 

5416 

5417class HyperlogCommands(CommandsProtocol): 

5418 """ 

5419 Redis commands of HyperLogLogs data type. 

5420 see: https://redis.io/topics/data-types-intro#hyperloglogs 

5421 """ 

5422 

5423 def pfadd(self, name: KeyT, *values: FieldT) -> ResponseT: 

5424 """ 

5425 Adds the specified elements to the specified HyperLogLog. 

5426 

5427 For more information, see https://redis.io/commands/pfadd 

5428 """ 

5429 return self.execute_command("PFADD", name, *values) 

5430 

5431 def pfcount(self, *sources: KeyT) -> ResponseT: 

5432 """ 

5433 Return the approximated cardinality of 

5434 the set observed by the HyperLogLog at key(s). 

5435 

5436 For more information, see https://redis.io/commands/pfcount 

5437 """ 

5438 return self.execute_command("PFCOUNT", *sources) 

5439 

5440 def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT: 

5441 """ 

5442 Merge N different HyperLogLogs into a single one. 

5443 

5444 For more information, see https://redis.io/commands/pfmerge 

5445 """ 

5446 return self.execute_command("PFMERGE", dest, *sources) 

5447 

5448 

5449AsyncHyperlogCommands = HyperlogCommands 

5450 

5451 

5452class HashDataPersistOptions(Enum): 

5453 # set the value for each provided key to each 

5454 # provided value only if all do not already exist. 

5455 FNX = "FNX" 

5456 

5457 # set the value for each provided key to each 

5458 # provided value only if all already exist. 

5459 FXX = "FXX" 

5460 

5461 

5462class HashCommands(CommandsProtocol): 

5463 """ 

5464 Redis commands for Hash data type. 

5465 see: https://redis.io/topics/data-types-intro#redis-hashes 

5466 """ 

5467 

5468 def hdel(self, name: str, *keys: str) -> Union[Awaitable[int], int]: 

5469 """ 

5470 Delete ``keys`` from hash ``name`` 

5471 

5472 For more information, see https://redis.io/commands/hdel 

5473 """ 

5474 return self.execute_command("HDEL", name, *keys) 

5475 

5476 def hexists(self, name: str, key: str) -> Union[Awaitable[bool], bool]: 

5477 """ 

5478 Returns a boolean indicating if ``key`` exists within hash ``name`` 

5479 

5480 For more information, see https://redis.io/commands/hexists 

5481 """ 

5482 return self.execute_command("HEXISTS", name, key, keys=[name]) 

5483 

5484 def hget( 

5485 self, name: str, key: str 

5486 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

5487 """ 

5488 Return the value of ``key`` within the hash ``name`` 

5489 

5490 For more information, see https://redis.io/commands/hget 

5491 """ 

5492 return self.execute_command("HGET", name, key, keys=[name]) 

5493 

5494 def hgetall(self, name: str) -> Union[Awaitable[dict], dict]: 

5495 """ 

5496 Return a Python dict of the hash's name/value pairs 

5497 

5498 For more information, see https://redis.io/commands/hgetall 

5499 """ 

5500 return self.execute_command("HGETALL", name, keys=[name]) 

5501 

5502 def hgetdel( 

5503 self, name: str, *keys: str 

5504 ) -> Union[ 

5505 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]] 

5506 ]: 

5507 """ 

5508 Return the value of ``key`` within the hash ``name`` and 

5509 delete the field in the hash. 

5510 This command is similar to HGET, except for the fact that it also deletes 

5511 the key on success from the hash with the provided ```name```. 

5512 

5513 Available since Redis 8.0 

5514 For more information, see https://redis.io/commands/hgetdel 

5515 """ 

5516 if len(keys) == 0: 

5517 raise DataError("'hgetdel' should have at least one key provided") 

5518 

5519 return self.execute_command("HGETDEL", name, "FIELDS", len(keys), *keys) 

5520 

5521 def hgetex( 

5522 self, 

5523 name: KeyT, 

5524 *keys: str, 

5525 ex: Optional[ExpiryT] = None, 

5526 px: Optional[ExpiryT] = None, 

5527 exat: Optional[AbsExpiryT] = None, 

5528 pxat: Optional[AbsExpiryT] = None, 

5529 persist: bool = False, 

5530 ) -> Union[ 

5531 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]] 

5532 ]: 

5533 """ 

5534 Return the values of ``key`` and ``keys`` within the hash ``name`` 

5535 and optionally set their expiration. 

5536 

5537 ``ex`` sets an expire flag on ``kyes`` for ``ex`` seconds. 

5538 

5539 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds. 

5540 

5541 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds, 

5542 specified in unix time. 

5543 

5544 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds, 

5545 specified in unix time. 

5546 

5547 ``persist`` remove the time to live associated with the ``keys``. 

5548 

5549 Available since Redis 8.0 

5550 For more information, see https://redis.io/commands/hgetex 

5551 """ 

5552 if not keys: 

5553 raise DataError("'hgetex' should have at least one key provided") 

5554 

5555 if not at_most_one_value_set((ex, px, exat, pxat, persist)): 

5556 raise DataError( 

5557 "``ex``, ``px``, ``exat``, ``pxat``, " 

5558 "and ``persist`` are mutually exclusive." 

5559 ) 

5560 

5561 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat) 

5562 

5563 if persist: 

5564 exp_options.append("PERSIST") 

5565 

5566 return self.execute_command( 

5567 "HGETEX", 

5568 name, 

5569 *exp_options, 

5570 "FIELDS", 

5571 len(keys), 

5572 *keys, 

5573 ) 

5574 

5575 def hincrby( 

5576 self, name: str, key: str, amount: int = 1 

5577 ) -> Union[Awaitable[int], int]: 

5578 """ 

5579 Increment the value of ``key`` in hash ``name`` by ``amount`` 

5580 

5581 For more information, see https://redis.io/commands/hincrby 

5582 """ 

5583 return self.execute_command("HINCRBY", name, key, amount) 

5584 

5585 def hincrbyfloat( 

5586 self, name: str, key: str, amount: float = 1.0 

5587 ) -> Union[Awaitable[float], float]: 

5588 """ 

5589 Increment the value of ``key`` in hash ``name`` by floating ``amount`` 

5590 

5591 For more information, see https://redis.io/commands/hincrbyfloat 

5592 """ 

5593 return self.execute_command("HINCRBYFLOAT", name, key, amount) 

5594 

5595 def hkeys(self, name: str) -> Union[Awaitable[List], List]: 

5596 """ 

5597 Return the list of keys within hash ``name`` 

5598 

5599 For more information, see https://redis.io/commands/hkeys 

5600 """ 

5601 return self.execute_command("HKEYS", name, keys=[name]) 

5602 

5603 def hlen(self, name: str) -> Union[Awaitable[int], int]: 

5604 """ 

5605 Return the number of elements in hash ``name`` 

5606 

5607 For more information, see https://redis.io/commands/hlen 

5608 """ 

5609 return self.execute_command("HLEN", name, keys=[name]) 

5610 

5611 def hset( 

5612 self, 

5613 name: str, 

5614 key: Optional[str] = None, 

5615 value: Optional[str] = None, 

5616 mapping: Optional[dict] = None, 

5617 items: Optional[list] = None, 

5618 ) -> Union[Awaitable[int], int]: 

5619 """ 

5620 Set ``key`` to ``value`` within hash ``name``, 

5621 ``mapping`` accepts a dict of key/value pairs that will be 

5622 added to hash ``name``. 

5623 ``items`` accepts a list of key/value pairs that will be 

5624 added to hash ``name``. 

5625 Returns the number of fields that were added. 

5626 

5627 For more information, see https://redis.io/commands/hset 

5628 """ 

5629 

5630 if key is None and not mapping and not items: 

5631 raise DataError("'hset' with no key value pairs") 

5632 

5633 pieces = [] 

5634 if items: 

5635 pieces.extend(items) 

5636 if key is not None: 

5637 pieces.extend((key, value)) 

5638 if mapping: 

5639 for pair in mapping.items(): 

5640 pieces.extend(pair) 

5641 

5642 return self.execute_command("HSET", name, *pieces) 

5643 

5644 def hsetex( 

5645 self, 

5646 name: str, 

5647 key: Optional[str] = None, 

5648 value: Optional[str] = None, 

5649 mapping: Optional[dict] = None, 

5650 items: Optional[list] = None, 

5651 ex: Optional[ExpiryT] = None, 

5652 px: Optional[ExpiryT] = None, 

5653 exat: Optional[AbsExpiryT] = None, 

5654 pxat: Optional[AbsExpiryT] = None, 

5655 data_persist_option: Optional[HashDataPersistOptions] = None, 

5656 keepttl: bool = False, 

5657 ) -> Union[Awaitable[int], int]: 

5658 """ 

5659 Set ``key`` to ``value`` within hash ``name`` 

5660 

5661 ``mapping`` accepts a dict of key/value pairs that will be 

5662 added to hash ``name``. 

5663 

5664 ``items`` accepts a list of key/value pairs that will be 

5665 added to hash ``name``. 

5666 

5667 ``ex`` sets an expire flag on ``keys`` for ``ex`` seconds. 

5668 

5669 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds. 

5670 

5671 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds, 

5672 specified in unix time. 

5673 

5674 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds, 

5675 specified in unix time. 

5676 

5677 ``data_persist_option`` can be set to ``FNX`` or ``FXX`` to control the 

5678 behavior of the command. 

5679 ``FNX`` will set the value for each provided key to each 

5680 provided value only if all do not already exist. 

5681 ``FXX`` will set the value for each provided key to each 

5682 provided value only if all already exist. 

5683 

5684 ``keepttl`` if True, retain the time to live associated with the keys. 

5685 

5686 Returns the number of fields that were added. 

5687 

5688 Available since Redis 8.0 

5689 For more information, see https://redis.io/commands/hsetex 

5690 """ 

5691 if key is None and not mapping and not items: 

5692 raise DataError("'hsetex' with no key value pairs") 

5693 

5694 if items and len(items) % 2 != 0: 

5695 raise DataError( 

5696 "'hsetex' with odd number of items. " 

5697 "'items' must contain a list of key/value pairs." 

5698 ) 

5699 

5700 if not at_most_one_value_set((ex, px, exat, pxat, keepttl)): 

5701 raise DataError( 

5702 "``ex``, ``px``, ``exat``, ``pxat``, " 

5703 "and ``keepttl`` are mutually exclusive." 

5704 ) 

5705 

5706 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat) 

5707 if data_persist_option: 

5708 exp_options.append(data_persist_option.value) 

5709 

5710 if keepttl: 

5711 exp_options.append("KEEPTTL") 

5712 

5713 pieces = [] 

5714 if items: 

5715 pieces.extend(items) 

5716 if key is not None: 

5717 pieces.extend((key, value)) 

5718 if mapping: 

5719 for pair in mapping.items(): 

5720 pieces.extend(pair) 

5721 

5722 return self.execute_command( 

5723 "HSETEX", name, *exp_options, "FIELDS", int(len(pieces) / 2), *pieces 

5724 ) 

5725 

5726 def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool]: 

5727 """ 

5728 Set ``key`` to ``value`` within hash ``name`` if ``key`` does not 

5729 exist. Returns 1 if HSETNX created a field, otherwise 0. 

5730 

5731 For more information, see https://redis.io/commands/hsetnx 

5732 """ 

5733 return self.execute_command("HSETNX", name, key, value) 

5734 

5735 @deprecated_function( 

5736 version="4.0.0", 

5737 reason="Use 'hset' instead.", 

5738 name="hmset", 

5739 ) 

5740 def hmset(self, name: str, mapping: dict) -> Union[Awaitable[str], str]: 

5741 """ 

5742 Set key to value within hash ``name`` for each corresponding 

5743 key and value from the ``mapping`` dict. 

5744 

5745 For more information, see https://redis.io/commands/hmset 

5746 """ 

5747 if not mapping: 

5748 raise DataError("'hmset' with 'mapping' of length 0") 

5749 items = [] 

5750 for pair in mapping.items(): 

5751 items.extend(pair) 

5752 return self.execute_command("HMSET", name, *items) 

5753 

5754 def hmget(self, name: str, keys: List, *args: List) -> Union[Awaitable[List], List]: 

5755 """ 

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

5757 

5758 For more information, see https://redis.io/commands/hmget 

5759 """ 

5760 args = list_or_args(keys, args) 

5761 return self.execute_command("HMGET", name, *args, keys=[name]) 

5762 

5763 def hvals(self, name: str) -> Union[Awaitable[List], List]: 

5764 """ 

5765 Return the list of values within hash ``name`` 

5766 

5767 For more information, see https://redis.io/commands/hvals 

5768 """ 

5769 return self.execute_command("HVALS", name, keys=[name]) 

5770 

5771 def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]: 

5772 """ 

5773 Return the number of bytes stored in the value of ``key`` 

5774 within hash ``name`` 

5775 

5776 For more information, see https://redis.io/commands/hstrlen 

5777 """ 

5778 return self.execute_command("HSTRLEN", name, key, keys=[name]) 

5779 

5780 def hexpire( 

5781 self, 

5782 name: KeyT, 

5783 seconds: ExpiryT, 

5784 *fields: str, 

5785 nx: bool = False, 

5786 xx: bool = False, 

5787 gt: bool = False, 

5788 lt: bool = False, 

5789 ) -> ResponseT: 

5790 """ 

5791 Sets or updates the expiration time for fields within a hash key, using relative 

5792 time in seconds. 

5793 

5794 If a field already has an expiration time, the behavior of the update can be 

5795 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5796 

5797 The return value provides detailed information about the outcome for each field. 

5798 

5799 For more information, see https://redis.io/commands/hexpire 

5800 

5801 Args: 

5802 name: The name of the hash key. 

5803 seconds: Expiration time in seconds, relative. Can be an integer, or a 

5804 Python `timedelta` object. 

5805 fields: List of fields within the hash to apply the expiration time to. 

5806 nx: Set expiry only when the field has no expiry. 

5807 xx: Set expiry only when the field has an existing expiry. 

5808 gt: Set expiry only when the new expiry is greater than the current one. 

5809 lt: Set expiry only when the new expiry is less than the current one. 

5810 

5811 Returns: 

5812 Returns a list which contains for each field in the request: 

5813 - `-2` if the field does not exist, or if the key does not exist. 

5814 - `0` if the specified NX | XX | GT | LT condition was not met. 

5815 - `1` if the expiration time was set or updated. 

5816 - `2` if the field was deleted because the specified expiration time is 

5817 in the past. 

5818 """ 

5819 conditions = [nx, xx, gt, lt] 

5820 if sum(conditions) > 1: 

5821 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5822 

5823 if isinstance(seconds, datetime.timedelta): 

5824 seconds = int(seconds.total_seconds()) 

5825 

5826 options = [] 

5827 if nx: 

5828 options.append("NX") 

5829 if xx: 

5830 options.append("XX") 

5831 if gt: 

5832 options.append("GT") 

5833 if lt: 

5834 options.append("LT") 

5835 

5836 return self.execute_command( 

5837 "HEXPIRE", name, seconds, *options, "FIELDS", len(fields), *fields 

5838 ) 

5839 

5840 def hpexpire( 

5841 self, 

5842 name: KeyT, 

5843 milliseconds: ExpiryT, 

5844 *fields: str, 

5845 nx: bool = False, 

5846 xx: bool = False, 

5847 gt: bool = False, 

5848 lt: bool = False, 

5849 ) -> ResponseT: 

5850 """ 

5851 Sets or updates the expiration time for fields within a hash key, using relative 

5852 time in milliseconds. 

5853 

5854 If a field already has an expiration time, the behavior of the update can be 

5855 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5856 

5857 The return value provides detailed information about the outcome for each field. 

5858 

5859 For more information, see https://redis.io/commands/hpexpire 

5860 

5861 Args: 

5862 name: The name of the hash key. 

5863 milliseconds: Expiration time in milliseconds, relative. Can be an integer, 

5864 or a Python `timedelta` object. 

5865 fields: List of fields within the hash to apply the expiration time to. 

5866 nx: Set expiry only when the field has no expiry. 

5867 xx: Set expiry only when the field has an existing expiry. 

5868 gt: Set expiry only when the new expiry is greater than the current one. 

5869 lt: Set expiry only when the new expiry is less than the current one. 

5870 

5871 Returns: 

5872 Returns a list which contains for each field in the request: 

5873 - `-2` if the field does not exist, or if the key does not exist. 

5874 - `0` if the specified NX | XX | GT | LT condition was not met. 

5875 - `1` if the expiration time was set or updated. 

5876 - `2` if the field was deleted because the specified expiration time is 

5877 in the past. 

5878 """ 

5879 conditions = [nx, xx, gt, lt] 

5880 if sum(conditions) > 1: 

5881 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5882 

5883 if isinstance(milliseconds, datetime.timedelta): 

5884 milliseconds = int(milliseconds.total_seconds() * 1000) 

5885 

5886 options = [] 

5887 if nx: 

5888 options.append("NX") 

5889 if xx: 

5890 options.append("XX") 

5891 if gt: 

5892 options.append("GT") 

5893 if lt: 

5894 options.append("LT") 

5895 

5896 return self.execute_command( 

5897 "HPEXPIRE", name, milliseconds, *options, "FIELDS", len(fields), *fields 

5898 ) 

5899 

5900 def hexpireat( 

5901 self, 

5902 name: KeyT, 

5903 unix_time_seconds: AbsExpiryT, 

5904 *fields: str, 

5905 nx: bool = False, 

5906 xx: bool = False, 

5907 gt: bool = False, 

5908 lt: bool = False, 

5909 ) -> ResponseT: 

5910 """ 

5911 Sets or updates the expiration time for fields within a hash key, using an 

5912 absolute Unix timestamp in seconds. 

5913 

5914 If a field already has an expiration time, the behavior of the update can be 

5915 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5916 

5917 The return value provides detailed information about the outcome for each field. 

5918 

5919 For more information, see https://redis.io/commands/hexpireat 

5920 

5921 Args: 

5922 name: The name of the hash key. 

5923 unix_time_seconds: Expiration time as Unix timestamp in seconds. Can be an 

5924 integer or a Python `datetime` object. 

5925 fields: List of fields within the hash to apply the expiration time to. 

5926 nx: Set expiry only when the field has no expiry. 

5927 xx: Set expiry only when the field has an existing expiration time. 

5928 gt: Set expiry only when the new expiry is greater than the current one. 

5929 lt: Set expiry only when the new expiry is less than the current one. 

5930 

5931 Returns: 

5932 Returns a list which contains for each field in the request: 

5933 - `-2` if the field does not exist, or if the key does not exist. 

5934 - `0` if the specified NX | XX | GT | LT condition was not met. 

5935 - `1` if the expiration time was set or updated. 

5936 - `2` if the field was deleted because the specified expiration time is 

5937 in the past. 

5938 """ 

5939 conditions = [nx, xx, gt, lt] 

5940 if sum(conditions) > 1: 

5941 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5942 

5943 if isinstance(unix_time_seconds, datetime.datetime): 

5944 unix_time_seconds = int(unix_time_seconds.timestamp()) 

5945 

5946 options = [] 

5947 if nx: 

5948 options.append("NX") 

5949 if xx: 

5950 options.append("XX") 

5951 if gt: 

5952 options.append("GT") 

5953 if lt: 

5954 options.append("LT") 

5955 

5956 return self.execute_command( 

5957 "HEXPIREAT", 

5958 name, 

5959 unix_time_seconds, 

5960 *options, 

5961 "FIELDS", 

5962 len(fields), 

5963 *fields, 

5964 ) 

5965 

5966 def hpexpireat( 

5967 self, 

5968 name: KeyT, 

5969 unix_time_milliseconds: AbsExpiryT, 

5970 *fields: str, 

5971 nx: bool = False, 

5972 xx: bool = False, 

5973 gt: bool = False, 

5974 lt: bool = False, 

5975 ) -> ResponseT: 

5976 """ 

5977 Sets or updates the expiration time for fields within a hash key, using an 

5978 absolute Unix timestamp in milliseconds. 

5979 

5980 If a field already has an expiration time, the behavior of the update can be 

5981 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5982 

5983 The return value provides detailed information about the outcome for each field. 

5984 

5985 For more information, see https://redis.io/commands/hpexpireat 

5986 

5987 Args: 

5988 name: The name of the hash key. 

5989 unix_time_milliseconds: Expiration time as Unix timestamp in milliseconds. 

5990 Can be an integer or a Python `datetime` object. 

5991 fields: List of fields within the hash to apply the expiry. 

5992 nx: Set expiry only when the field has no expiry. 

5993 xx: Set expiry only when the field has an existing expiry. 

5994 gt: Set expiry only when the new expiry is greater than the current one. 

5995 lt: Set expiry only when the new expiry is less than the current one. 

5996 

5997 Returns: 

5998 Returns a list which contains for each field in the request: 

5999 - `-2` if the field does not exist, or if the key does not exist. 

6000 - `0` if the specified NX | XX | GT | LT condition was not met. 

6001 - `1` if the expiration time was set or updated. 

6002 - `2` if the field was deleted because the specified expiration time is 

6003 in the past. 

6004 """ 

6005 conditions = [nx, xx, gt, lt] 

6006 if sum(conditions) > 1: 

6007 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

6008 

6009 if isinstance(unix_time_milliseconds, datetime.datetime): 

6010 unix_time_milliseconds = int(unix_time_milliseconds.timestamp() * 1000) 

6011 

6012 options = [] 

6013 if nx: 

6014 options.append("NX") 

6015 if xx: 

6016 options.append("XX") 

6017 if gt: 

6018 options.append("GT") 

6019 if lt: 

6020 options.append("LT") 

6021 

6022 return self.execute_command( 

6023 "HPEXPIREAT", 

6024 name, 

6025 unix_time_milliseconds, 

6026 *options, 

6027 "FIELDS", 

6028 len(fields), 

6029 *fields, 

6030 ) 

6031 

6032 def hpersist(self, name: KeyT, *fields: str) -> ResponseT: 

6033 """ 

6034 Removes the expiration time for each specified field in a hash. 

6035 

6036 For more information, see https://redis.io/commands/hpersist 

6037 

6038 Args: 

6039 name: The name of the hash key. 

6040 fields: A list of fields within the hash from which to remove the 

6041 expiration time. 

6042 

6043 Returns: 

6044 Returns a list which contains for each field in the request: 

6045 - `-2` if the field does not exist, or if the key does not exist. 

6046 - `-1` if the field exists but has no associated expiration time. 

6047 - `1` if the expiration time was successfully removed from the field. 

6048 """ 

6049 return self.execute_command("HPERSIST", name, "FIELDS", len(fields), *fields) 

6050 

6051 def hexpiretime(self, key: KeyT, *fields: str) -> ResponseT: 

6052 """ 

6053 Returns the expiration times of hash fields as Unix timestamps in seconds. 

6054 

6055 For more information, see https://redis.io/commands/hexpiretime 

6056 

6057 Args: 

6058 key: The hash key. 

6059 fields: A list of fields within the hash for which to get the expiration 

6060 time. 

6061 

6062 Returns: 

6063 Returns a list which contains for each field in the request: 

6064 - `-2` if the field does not exist, or if the key does not exist. 

6065 - `-1` if the field exists but has no associated expire time. 

6066 - A positive integer representing the expiration Unix timestamp in 

6067 seconds, if the field has an associated expiration time. 

6068 """ 

6069 return self.execute_command( 

6070 "HEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key] 

6071 ) 

6072 

6073 def hpexpiretime(self, key: KeyT, *fields: str) -> ResponseT: 

6074 """ 

6075 Returns the expiration times of hash fields as Unix timestamps in milliseconds. 

6076 

6077 For more information, see https://redis.io/commands/hpexpiretime 

6078 

6079 Args: 

6080 key: The hash key. 

6081 fields: A list of fields within the hash for which to get the expiration 

6082 time. 

6083 

6084 Returns: 

6085 Returns a list which contains for each field in the request: 

6086 - `-2` if the field does not exist, or if the key does not exist. 

6087 - `-1` if the field exists but has no associated expire time. 

6088 - A positive integer representing the expiration Unix timestamp in 

6089 milliseconds, if the field has an associated expiration time. 

6090 """ 

6091 return self.execute_command( 

6092 "HPEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key] 

6093 ) 

6094 

6095 def httl(self, key: KeyT, *fields: str) -> ResponseT: 

6096 """ 

6097 Returns the TTL (Time To Live) in seconds for each specified field within a hash 

6098 key. 

6099 

6100 For more information, see https://redis.io/commands/httl 

6101 

6102 Args: 

6103 key: The hash key. 

6104 fields: A list of fields within the hash for which to get the TTL. 

6105 

6106 Returns: 

6107 Returns a list which contains for each field in the request: 

6108 - `-2` if the field does not exist, or if the key does not exist. 

6109 - `-1` if the field exists but has no associated expire time. 

6110 - A positive integer representing the TTL in seconds if the field has 

6111 an associated expiration time. 

6112 """ 

6113 return self.execute_command( 

6114 "HTTL", key, "FIELDS", len(fields), *fields, keys=[key] 

6115 ) 

6116 

6117 def hpttl(self, key: KeyT, *fields: str) -> ResponseT: 

6118 """ 

6119 Returns the TTL (Time To Live) in milliseconds for each specified field within a 

6120 hash key. 

6121 

6122 For more information, see https://redis.io/commands/hpttl 

6123 

6124 Args: 

6125 key: The hash key. 

6126 fields: A list of fields within the hash for which to get the TTL. 

6127 

6128 Returns: 

6129 Returns a list which contains for each field in the request: 

6130 - `-2` if the field does not exist, or if the key does not exist. 

6131 - `-1` if the field exists but has no associated expire time. 

6132 - A positive integer representing the TTL in milliseconds if the field 

6133 has an associated expiration time. 

6134 """ 

6135 return self.execute_command( 

6136 "HPTTL", key, "FIELDS", len(fields), *fields, keys=[key] 

6137 ) 

6138 

6139 

6140AsyncHashCommands = HashCommands 

6141 

6142 

6143class Script: 

6144 """ 

6145 An executable Lua script object returned by ``register_script`` 

6146 """ 

6147 

6148 def __init__(self, registered_client: "redis.client.Redis", script: ScriptTextT): 

6149 self.registered_client = registered_client 

6150 self.script = script 

6151 # Precalculate and store the SHA1 hex digest of the script. 

6152 

6153 if isinstance(script, str): 

6154 # We need the encoding from the client in order to generate an 

6155 # accurate byte representation of the script 

6156 encoder = self.get_encoder() 

6157 script = encoder.encode(script) 

6158 self.sha = hashlib.sha1(script).hexdigest() 

6159 

6160 def __call__( 

6161 self, 

6162 keys: Union[Sequence[KeyT], None] = None, 

6163 args: Union[Iterable[EncodableT], None] = None, 

6164 client: Union["redis.client.Redis", None] = None, 

6165 ): 

6166 """Execute the script, passing any required ``args``""" 

6167 keys = keys or [] 

6168 args = args or [] 

6169 if client is None: 

6170 client = self.registered_client 

6171 args = tuple(keys) + tuple(args) 

6172 # make sure the Redis server knows about the script 

6173 from redis.client import Pipeline 

6174 

6175 if isinstance(client, Pipeline): 

6176 # Make sure the pipeline can register the script before executing. 

6177 client.scripts.add(self) 

6178 try: 

6179 return client.evalsha(self.sha, len(keys), *args) 

6180 except NoScriptError: 

6181 # Maybe the client is pointed to a different server than the client 

6182 # that created this instance? 

6183 # Overwrite the sha just in case there was a discrepancy. 

6184 self.sha = client.script_load(self.script) 

6185 return client.evalsha(self.sha, len(keys), *args) 

6186 

6187 def get_encoder(self): 

6188 """Get the encoder to encode string scripts into bytes.""" 

6189 try: 

6190 return self.registered_client.get_encoder() 

6191 except AttributeError: 

6192 # DEPRECATED 

6193 # In version <=4.1.2, this was the code we used to get the encoder. 

6194 # However, after 4.1.2 we added support for scripting in clustered 

6195 # redis. ClusteredRedis doesn't have a `.connection_pool` attribute 

6196 # so we changed the Script class to use 

6197 # `self.registered_client.get_encoder` (see above). 

6198 # However, that is technically a breaking change, as consumers who 

6199 # use Scripts directly might inject a `registered_client` that 

6200 # doesn't have a `.get_encoder` field. This try/except prevents us 

6201 # from breaking backward-compatibility. Ideally, it would be 

6202 # removed in the next major release. 

6203 return self.registered_client.connection_pool.get_encoder() 

6204 

6205 

6206class AsyncScript: 

6207 """ 

6208 An executable Lua script object returned by ``register_script`` 

6209 """ 

6210 

6211 def __init__( 

6212 self, 

6213 registered_client: "redis.asyncio.client.Redis", 

6214 script: ScriptTextT, 

6215 ): 

6216 self.registered_client = registered_client 

6217 self.script = script 

6218 # Precalculate and store the SHA1 hex digest of the script. 

6219 

6220 if isinstance(script, str): 

6221 # We need the encoding from the client in order to generate an 

6222 # accurate byte representation of the script 

6223 try: 

6224 encoder = registered_client.connection_pool.get_encoder() 

6225 except AttributeError: 

6226 # Cluster 

6227 encoder = registered_client.get_encoder() 

6228 script = encoder.encode(script) 

6229 self.sha = hashlib.sha1(script).hexdigest() 

6230 

6231 async def __call__( 

6232 self, 

6233 keys: Union[Sequence[KeyT], None] = None, 

6234 args: Union[Iterable[EncodableT], None] = None, 

6235 client: Union["redis.asyncio.client.Redis", None] = None, 

6236 ): 

6237 """Execute the script, passing any required ``args``""" 

6238 keys = keys or [] 

6239 args = args or [] 

6240 if client is None: 

6241 client = self.registered_client 

6242 args = tuple(keys) + tuple(args) 

6243 # make sure the Redis server knows about the script 

6244 from redis.asyncio.client import Pipeline 

6245 

6246 if isinstance(client, Pipeline): 

6247 # Make sure the pipeline can register the script before executing. 

6248 client.scripts.add(self) 

6249 try: 

6250 return await client.evalsha(self.sha, len(keys), *args) 

6251 except NoScriptError: 

6252 # Maybe the client is pointed to a different server than the client 

6253 # that created this instance? 

6254 # Overwrite the sha just in case there was a discrepancy. 

6255 self.sha = await client.script_load(self.script) 

6256 return await client.evalsha(self.sha, len(keys), *args) 

6257 

6258 

6259class PubSubCommands(CommandsProtocol): 

6260 """ 

6261 Redis PubSub commands. 

6262 see https://redis.io/topics/pubsub 

6263 """ 

6264 

6265 def publish(self, channel: ChannelT, message: EncodableT, **kwargs) -> ResponseT: 

6266 """ 

6267 Publish ``message`` on ``channel``. 

6268 Returns the number of subscribers the message was delivered to. 

6269 

6270 For more information, see https://redis.io/commands/publish 

6271 """ 

6272 return self.execute_command("PUBLISH", channel, message, **kwargs) 

6273 

6274 def spublish(self, shard_channel: ChannelT, message: EncodableT) -> ResponseT: 

6275 """ 

6276 Posts a message to the given shard channel. 

6277 Returns the number of clients that received the message 

6278 

6279 For more information, see https://redis.io/commands/spublish 

6280 """ 

6281 return self.execute_command("SPUBLISH", shard_channel, message) 

6282 

6283 def pubsub_channels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

6284 """ 

6285 Return a list of channels that have at least one subscriber 

6286 

6287 For more information, see https://redis.io/commands/pubsub-channels 

6288 """ 

6289 return self.execute_command("PUBSUB CHANNELS", pattern, **kwargs) 

6290 

6291 def pubsub_shardchannels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

6292 """ 

6293 Return a list of shard_channels that have at least one subscriber 

6294 

6295 For more information, see https://redis.io/commands/pubsub-shardchannels 

6296 """ 

6297 return self.execute_command("PUBSUB SHARDCHANNELS", pattern, **kwargs) 

6298 

6299 def pubsub_numpat(self, **kwargs) -> ResponseT: 

6300 """ 

6301 Returns the number of subscriptions to patterns 

6302 

6303 For more information, see https://redis.io/commands/pubsub-numpat 

6304 """ 

6305 return self.execute_command("PUBSUB NUMPAT", **kwargs) 

6306 

6307 def pubsub_numsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

6308 """ 

6309 Return a list of (channel, number of subscribers) tuples 

6310 for each channel given in ``*args`` 

6311 

6312 For more information, see https://redis.io/commands/pubsub-numsub 

6313 """ 

6314 return self.execute_command("PUBSUB NUMSUB", *args, **kwargs) 

6315 

6316 def pubsub_shardnumsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

6317 """ 

6318 Return a list of (shard_channel, number of subscribers) tuples 

6319 for each channel given in ``*args`` 

6320 

6321 For more information, see https://redis.io/commands/pubsub-shardnumsub 

6322 """ 

6323 return self.execute_command("PUBSUB SHARDNUMSUB", *args, **kwargs) 

6324 

6325 

6326AsyncPubSubCommands = PubSubCommands 

6327 

6328 

6329class ScriptCommands(CommandsProtocol): 

6330 """ 

6331 Redis Lua script commands. see: 

6332 https://redis.io/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/ 

6333 """ 

6334 

6335 def _eval( 

6336 self, 

6337 command: str, 

6338 script: str, 

6339 numkeys: int, 

6340 *keys_and_args: Union[KeyT, EncodableT], 

6341 ) -> Union[Awaitable[str], str]: 

6342 return self.execute_command(command, script, numkeys, *keys_and_args) 

6343 

6344 def eval( 

6345 self, script: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT] 

6346 ) -> Union[Awaitable[str], str]: 

6347 """ 

6348 Execute the Lua ``script``, specifying the ``numkeys`` the script 

6349 will touch and the key names and argument values in ``keys_and_args``. 

6350 Returns the result of the script. 

6351 

6352 In practice, use the object returned by ``register_script``. This 

6353 function exists purely for Redis API completion. 

6354 

6355 For more information, see https://redis.io/commands/eval 

6356 """ 

6357 return self._eval("EVAL", script, numkeys, *keys_and_args) 

6358 

6359 def eval_ro( 

6360 self, script: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT] 

6361 ) -> Union[Awaitable[str], str]: 

6362 """ 

6363 The read-only variant of the EVAL command 

6364 

6365 Execute the read-only Lua ``script`` specifying the ``numkeys`` the script 

6366 will touch and the key names and argument values in ``keys_and_args``. 

6367 Returns the result of the script. 

6368 

6369 For more information, see https://redis.io/commands/eval_ro 

6370 """ 

6371 return self._eval("EVAL_RO", script, numkeys, *keys_and_args) 

6372 

6373 def _evalsha( 

6374 self, 

6375 command: str, 

6376 sha: str, 

6377 numkeys: int, 

6378 *keys_and_args: Union[KeyT, EncodableT], 

6379 ) -> Union[Awaitable[str], str]: 

6380 return self.execute_command(command, sha, numkeys, *keys_and_args) 

6381 

6382 def evalsha( 

6383 self, sha: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT] 

6384 ) -> Union[Awaitable[str], str]: 

6385 """ 

6386 Use the ``sha`` to execute a Lua script already registered via EVAL 

6387 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

6388 key names and argument values in ``keys_and_args``. Returns the result 

6389 of the script. 

6390 

6391 In practice, use the object returned by ``register_script``. This 

6392 function exists purely for Redis API completion. 

6393 

6394 For more information, see https://redis.io/commands/evalsha 

6395 """ 

6396 return self._evalsha("EVALSHA", sha, numkeys, *keys_and_args) 

6397 

6398 def evalsha_ro( 

6399 self, sha: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT] 

6400 ) -> Union[Awaitable[str], str]: 

6401 """ 

6402 The read-only variant of the EVALSHA command 

6403 

6404 Use the ``sha`` to execute a read-only Lua script already registered via EVAL 

6405 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

6406 key names and argument values in ``keys_and_args``. Returns the result 

6407 of the script. 

6408 

6409 For more information, see https://redis.io/commands/evalsha_ro 

6410 """ 

6411 return self._evalsha("EVALSHA_RO", sha, numkeys, *keys_and_args) 

6412 

6413 def script_exists(self, *args: str) -> ResponseT: 

6414 """ 

6415 Check if a script exists in the script cache by specifying the SHAs of 

6416 each script as ``args``. Returns a list of boolean values indicating if 

6417 if each already script exists in the cache_data. 

6418 

6419 For more information, see https://redis.io/commands/script-exists 

6420 """ 

6421 return self.execute_command("SCRIPT EXISTS", *args) 

6422 

6423 def script_debug(self, *args) -> None: 

6424 raise NotImplementedError( 

6425 "SCRIPT DEBUG is intentionally not implemented in the client." 

6426 ) 

6427 

6428 def script_flush( 

6429 self, sync_type: Union[Literal["SYNC"], Literal["ASYNC"]] = None 

6430 ) -> ResponseT: 

6431 """Flush all scripts from the script cache_data. 

6432 

6433 ``sync_type`` is by default SYNC (synchronous) but it can also be 

6434 ASYNC. 

6435 

6436 For more information, see https://redis.io/commands/script-flush 

6437 """ 

6438 

6439 # Redis pre 6 had no sync_type. 

6440 if sync_type not in ["SYNC", "ASYNC", None]: 

6441 raise DataError( 

6442 "SCRIPT FLUSH defaults to SYNC in redis > 6.2, or " 

6443 "accepts SYNC/ASYNC. For older versions, " 

6444 "of redis leave as None." 

6445 ) 

6446 if sync_type is None: 

6447 pieces = [] 

6448 else: 

6449 pieces = [sync_type] 

6450 return self.execute_command("SCRIPT FLUSH", *pieces) 

6451 

6452 def script_kill(self) -> ResponseT: 

6453 """ 

6454 Kill the currently executing Lua script 

6455 

6456 For more information, see https://redis.io/commands/script-kill 

6457 """ 

6458 return self.execute_command("SCRIPT KILL") 

6459 

6460 def script_load(self, script: ScriptTextT) -> ResponseT: 

6461 """ 

6462 Load a Lua ``script`` into the script cache_data. Returns the SHA. 

6463 

6464 For more information, see https://redis.io/commands/script-load 

6465 """ 

6466 return self.execute_command("SCRIPT LOAD", script) 

6467 

6468 def register_script(self: "redis.client.Redis", script: ScriptTextT) -> Script: 

6469 """ 

6470 Register a Lua ``script`` specifying the ``keys`` it will touch. 

6471 Returns a Script object that is callable and hides the complexity of 

6472 deal with scripts, keys, and shas. This is the preferred way to work 

6473 with Lua scripts. 

6474 """ 

6475 return Script(self, script) 

6476 

6477 

6478class AsyncScriptCommands(ScriptCommands): 

6479 async def script_debug(self, *args) -> None: 

6480 return super().script_debug() 

6481 

6482 def register_script( 

6483 self: "redis.asyncio.client.Redis", 

6484 script: ScriptTextT, 

6485 ) -> AsyncScript: 

6486 """ 

6487 Register a Lua ``script`` specifying the ``keys`` it will touch. 

6488 Returns a Script object that is callable and hides the complexity of 

6489 deal with scripts, keys, and shas. This is the preferred way to work 

6490 with Lua scripts. 

6491 """ 

6492 return AsyncScript(self, script) 

6493 

6494 

6495class GeoCommands(CommandsProtocol): 

6496 """ 

6497 Redis Geospatial commands. 

6498 see: https://redis.com/redis-best-practices/indexing-patterns/geospatial/ 

6499 """ 

6500 

6501 def geoadd( 

6502 self, 

6503 name: KeyT, 

6504 values: Sequence[EncodableT], 

6505 nx: bool = False, 

6506 xx: bool = False, 

6507 ch: bool = False, 

6508 ) -> ResponseT: 

6509 """ 

6510 Add the specified geospatial items to the specified key identified 

6511 by the ``name`` argument. The Geospatial items are given as ordered 

6512 members of the ``values`` argument, each item or place is formed by 

6513 the triad longitude, latitude and name. 

6514 

6515 Note: You can use ZREM to remove elements. 

6516 

6517 ``nx`` forces ZADD to only create new elements and not to update 

6518 scores for elements that already exist. 

6519 

6520 ``xx`` forces ZADD to only update scores of elements that already 

6521 exist. New elements will not be added. 

6522 

6523 ``ch`` modifies the return value to be the numbers of elements changed. 

6524 Changed elements include new elements that were added and elements 

6525 whose scores changed. 

6526 

6527 For more information, see https://redis.io/commands/geoadd 

6528 """ 

6529 if nx and xx: 

6530 raise DataError("GEOADD allows either 'nx' or 'xx', not both") 

6531 if len(values) % 3 != 0: 

6532 raise DataError("GEOADD requires places with lon, lat and name values") 

6533 pieces = [name] 

6534 if nx: 

6535 pieces.append("NX") 

6536 if xx: 

6537 pieces.append("XX") 

6538 if ch: 

6539 pieces.append("CH") 

6540 pieces.extend(values) 

6541 return self.execute_command("GEOADD", *pieces) 

6542 

6543 def geodist( 

6544 self, name: KeyT, place1: FieldT, place2: FieldT, unit: Optional[str] = None 

6545 ) -> ResponseT: 

6546 """ 

6547 Return the distance between ``place1`` and ``place2`` members of the 

6548 ``name`` key. 

6549 The units must be one of the following : m, km mi, ft. By default 

6550 meters are used. 

6551 

6552 For more information, see https://redis.io/commands/geodist 

6553 """ 

6554 pieces: list[EncodableT] = [name, place1, place2] 

6555 if unit and unit not in ("m", "km", "mi", "ft"): 

6556 raise DataError("GEODIST invalid unit") 

6557 elif unit: 

6558 pieces.append(unit) 

6559 return self.execute_command("GEODIST", *pieces, keys=[name]) 

6560 

6561 def geohash(self, name: KeyT, *values: FieldT) -> ResponseT: 

6562 """ 

6563 Return the geo hash string for each item of ``values`` members of 

6564 the specified key identified by the ``name`` argument. 

6565 

6566 For more information, see https://redis.io/commands/geohash 

6567 """ 

6568 return self.execute_command("GEOHASH", name, *values, keys=[name]) 

6569 

6570 def geopos(self, name: KeyT, *values: FieldT) -> ResponseT: 

6571 """ 

6572 Return the positions of each item of ``values`` as members of 

6573 the specified key identified by the ``name`` argument. Each position 

6574 is represented by the pairs lon and lat. 

6575 

6576 For more information, see https://redis.io/commands/geopos 

6577 """ 

6578 return self.execute_command("GEOPOS", name, *values, keys=[name]) 

6579 

6580 def georadius( 

6581 self, 

6582 name: KeyT, 

6583 longitude: float, 

6584 latitude: float, 

6585 radius: float, 

6586 unit: Optional[str] = None, 

6587 withdist: bool = False, 

6588 withcoord: bool = False, 

6589 withhash: bool = False, 

6590 count: Optional[int] = None, 

6591 sort: Optional[str] = None, 

6592 store: Optional[KeyT] = None, 

6593 store_dist: Optional[KeyT] = None, 

6594 any: bool = False, 

6595 ) -> ResponseT: 

6596 """ 

6597 Return the members of the specified key identified by the 

6598 ``name`` argument which are within the borders of the area specified 

6599 with the ``latitude`` and ``longitude`` location and the maximum 

6600 distance from the center specified by the ``radius`` value. 

6601 

6602 The units must be one of the following : m, km mi, ft. By default 

6603 

6604 ``withdist`` indicates to return the distances of each place. 

6605 

6606 ``withcoord`` indicates to return the latitude and longitude of 

6607 each place. 

6608 

6609 ``withhash`` indicates to return the geohash string of each place. 

6610 

6611 ``count`` indicates to return the number of elements up to N. 

6612 

6613 ``sort`` indicates to return the places in a sorted way, ASC for 

6614 nearest to fairest and DESC for fairest to nearest. 

6615 

6616 ``store`` indicates to save the places names in a sorted set named 

6617 with a specific key, each element of the destination sorted set is 

6618 populated with the score got from the original geo sorted set. 

6619 

6620 ``store_dist`` indicates to save the places names in a sorted set 

6621 named with a specific key, instead of ``store`` the sorted set 

6622 destination score is set with the distance. 

6623 

6624 For more information, see https://redis.io/commands/georadius 

6625 """ 

6626 return self._georadiusgeneric( 

6627 "GEORADIUS", 

6628 name, 

6629 longitude, 

6630 latitude, 

6631 radius, 

6632 unit=unit, 

6633 withdist=withdist, 

6634 withcoord=withcoord, 

6635 withhash=withhash, 

6636 count=count, 

6637 sort=sort, 

6638 store=store, 

6639 store_dist=store_dist, 

6640 any=any, 

6641 ) 

6642 

6643 def georadiusbymember( 

6644 self, 

6645 name: KeyT, 

6646 member: FieldT, 

6647 radius: float, 

6648 unit: Optional[str] = None, 

6649 withdist: bool = False, 

6650 withcoord: bool = False, 

6651 withhash: bool = False, 

6652 count: Optional[int] = None, 

6653 sort: Optional[str] = None, 

6654 store: Union[KeyT, None] = None, 

6655 store_dist: Union[KeyT, None] = None, 

6656 any: bool = False, 

6657 ) -> ResponseT: 

6658 """ 

6659 This command is exactly like ``georadius`` with the sole difference 

6660 that instead of taking, as the center of the area to query, a longitude 

6661 and latitude value, it takes the name of a member already existing 

6662 inside the geospatial index represented by the sorted set. 

6663 

6664 For more information, see https://redis.io/commands/georadiusbymember 

6665 """ 

6666 return self._georadiusgeneric( 

6667 "GEORADIUSBYMEMBER", 

6668 name, 

6669 member, 

6670 radius, 

6671 unit=unit, 

6672 withdist=withdist, 

6673 withcoord=withcoord, 

6674 withhash=withhash, 

6675 count=count, 

6676 sort=sort, 

6677 store=store, 

6678 store_dist=store_dist, 

6679 any=any, 

6680 ) 

6681 

6682 def _georadiusgeneric( 

6683 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

6684 ) -> ResponseT: 

6685 pieces = list(args) 

6686 if kwargs["unit"] and kwargs["unit"] not in ("m", "km", "mi", "ft"): 

6687 raise DataError("GEORADIUS invalid unit") 

6688 elif kwargs["unit"]: 

6689 pieces.append(kwargs["unit"]) 

6690 else: 

6691 pieces.append("m") 

6692 

6693 if kwargs["any"] and kwargs["count"] is None: 

6694 raise DataError("``any`` can't be provided without ``count``") 

6695 

6696 for arg_name, byte_repr in ( 

6697 ("withdist", "WITHDIST"), 

6698 ("withcoord", "WITHCOORD"), 

6699 ("withhash", "WITHHASH"), 

6700 ): 

6701 if kwargs[arg_name]: 

6702 pieces.append(byte_repr) 

6703 

6704 if kwargs["count"] is not None: 

6705 pieces.extend(["COUNT", kwargs["count"]]) 

6706 if kwargs["any"]: 

6707 pieces.append("ANY") 

6708 

6709 if kwargs["sort"]: 

6710 if kwargs["sort"] == "ASC": 

6711 pieces.append("ASC") 

6712 elif kwargs["sort"] == "DESC": 

6713 pieces.append("DESC") 

6714 else: 

6715 raise DataError("GEORADIUS invalid sort") 

6716 

6717 if kwargs["store"] and kwargs["store_dist"]: 

6718 raise DataError("GEORADIUS store and store_dist cant be set together") 

6719 

6720 if kwargs["store"]: 

6721 pieces.extend([b"STORE", kwargs["store"]]) 

6722 

6723 if kwargs["store_dist"]: 

6724 pieces.extend([b"STOREDIST", kwargs["store_dist"]]) 

6725 

6726 return self.execute_command(command, *pieces, **kwargs) 

6727 

6728 def geosearch( 

6729 self, 

6730 name: KeyT, 

6731 member: Union[FieldT, None] = None, 

6732 longitude: Union[float, None] = None, 

6733 latitude: Union[float, None] = None, 

6734 unit: str = "m", 

6735 radius: Union[float, None] = None, 

6736 width: Union[float, None] = None, 

6737 height: Union[float, None] = None, 

6738 sort: Optional[str] = None, 

6739 count: Optional[int] = None, 

6740 any: bool = False, 

6741 withcoord: bool = False, 

6742 withdist: bool = False, 

6743 withhash: bool = False, 

6744 ) -> ResponseT: 

6745 """ 

6746 Return the members of specified key identified by the 

6747 ``name`` argument, which are within the borders of the 

6748 area specified by a given shape. This command extends the 

6749 GEORADIUS command, so in addition to searching within circular 

6750 areas, it supports searching within rectangular areas. 

6751 

6752 This command should be used in place of the deprecated 

6753 GEORADIUS and GEORADIUSBYMEMBER commands. 

6754 

6755 ``member`` Use the position of the given existing 

6756 member in the sorted set. Can't be given with ``longitude`` 

6757 and ``latitude``. 

6758 

6759 ``longitude`` and ``latitude`` Use the position given by 

6760 this coordinates. Can't be given with ``member`` 

6761 ``radius`` Similar to GEORADIUS, search inside circular 

6762 area according the given radius. Can't be given with 

6763 ``height`` and ``width``. 

6764 ``height`` and ``width`` Search inside an axis-aligned 

6765 rectangle, determined by the given height and width. 

6766 Can't be given with ``radius`` 

6767 

6768 ``unit`` must be one of the following : m, km, mi, ft. 

6769 `m` for meters (the default value), `km` for kilometers, 

6770 `mi` for miles and `ft` for feet. 

6771 

6772 ``sort`` indicates to return the places in a sorted way, 

6773 ASC for nearest to furthest and DESC for furthest to nearest. 

6774 

6775 ``count`` limit the results to the first count matching items. 

6776 

6777 ``any`` is set to True, the command will return as soon as 

6778 enough matches are found. Can't be provided without ``count`` 

6779 

6780 ``withdist`` indicates to return the distances of each place. 

6781 ``withcoord`` indicates to return the latitude and longitude of 

6782 each place. 

6783 

6784 ``withhash`` indicates to return the geohash string of each place. 

6785 

6786 For more information, see https://redis.io/commands/geosearch 

6787 """ 

6788 

6789 return self._geosearchgeneric( 

6790 "GEOSEARCH", 

6791 name, 

6792 member=member, 

6793 longitude=longitude, 

6794 latitude=latitude, 

6795 unit=unit, 

6796 radius=radius, 

6797 width=width, 

6798 height=height, 

6799 sort=sort, 

6800 count=count, 

6801 any=any, 

6802 withcoord=withcoord, 

6803 withdist=withdist, 

6804 withhash=withhash, 

6805 store=None, 

6806 store_dist=None, 

6807 ) 

6808 

6809 def geosearchstore( 

6810 self, 

6811 dest: KeyT, 

6812 name: KeyT, 

6813 member: Optional[FieldT] = None, 

6814 longitude: Optional[float] = None, 

6815 latitude: Optional[float] = None, 

6816 unit: str = "m", 

6817 radius: Optional[float] = None, 

6818 width: Optional[float] = None, 

6819 height: Optional[float] = None, 

6820 sort: Optional[str] = None, 

6821 count: Optional[int] = None, 

6822 any: bool = False, 

6823 storedist: bool = False, 

6824 ) -> ResponseT: 

6825 """ 

6826 This command is like GEOSEARCH, but stores the result in 

6827 ``dest``. By default, it stores the results in the destination 

6828 sorted set with their geospatial information. 

6829 if ``store_dist`` set to True, the command will stores the 

6830 items in a sorted set populated with their distance from the 

6831 center of the circle or box, as a floating-point number. 

6832 

6833 For more information, see https://redis.io/commands/geosearchstore 

6834 """ 

6835 return self._geosearchgeneric( 

6836 "GEOSEARCHSTORE", 

6837 dest, 

6838 name, 

6839 member=member, 

6840 longitude=longitude, 

6841 latitude=latitude, 

6842 unit=unit, 

6843 radius=radius, 

6844 width=width, 

6845 height=height, 

6846 sort=sort, 

6847 count=count, 

6848 any=any, 

6849 withcoord=None, 

6850 withdist=None, 

6851 withhash=None, 

6852 store=None, 

6853 store_dist=storedist, 

6854 ) 

6855 

6856 def _geosearchgeneric( 

6857 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

6858 ) -> ResponseT: 

6859 pieces = list(args) 

6860 

6861 # FROMMEMBER or FROMLONLAT 

6862 if kwargs["member"] is None: 

6863 if kwargs["longitude"] is None or kwargs["latitude"] is None: 

6864 raise DataError("GEOSEARCH must have member or longitude and latitude") 

6865 if kwargs["member"]: 

6866 if kwargs["longitude"] or kwargs["latitude"]: 

6867 raise DataError( 

6868 "GEOSEARCH member and longitude or latitude cant be set together" 

6869 ) 

6870 pieces.extend([b"FROMMEMBER", kwargs["member"]]) 

6871 if kwargs["longitude"] is not None and kwargs["latitude"] is not None: 

6872 pieces.extend([b"FROMLONLAT", kwargs["longitude"], kwargs["latitude"]]) 

6873 

6874 # BYRADIUS or BYBOX 

6875 if kwargs["radius"] is None: 

6876 if kwargs["width"] is None or kwargs["height"] is None: 

6877 raise DataError("GEOSEARCH must have radius or width and height") 

6878 if kwargs["unit"] is None: 

6879 raise DataError("GEOSEARCH must have unit") 

6880 if kwargs["unit"].lower() not in ("m", "km", "mi", "ft"): 

6881 raise DataError("GEOSEARCH invalid unit") 

6882 if kwargs["radius"]: 

6883 if kwargs["width"] or kwargs["height"]: 

6884 raise DataError( 

6885 "GEOSEARCH radius and width or height cant be set together" 

6886 ) 

6887 pieces.extend([b"BYRADIUS", kwargs["radius"], kwargs["unit"]]) 

6888 if kwargs["width"] and kwargs["height"]: 

6889 pieces.extend([b"BYBOX", kwargs["width"], kwargs["height"], kwargs["unit"]]) 

6890 

6891 # sort 

6892 if kwargs["sort"]: 

6893 if kwargs["sort"].upper() == "ASC": 

6894 pieces.append(b"ASC") 

6895 elif kwargs["sort"].upper() == "DESC": 

6896 pieces.append(b"DESC") 

6897 else: 

6898 raise DataError("GEOSEARCH invalid sort") 

6899 

6900 # count any 

6901 if kwargs["count"]: 

6902 pieces.extend([b"COUNT", kwargs["count"]]) 

6903 if kwargs["any"]: 

6904 pieces.append(b"ANY") 

6905 elif kwargs["any"]: 

6906 raise DataError("GEOSEARCH ``any`` can't be provided without count") 

6907 

6908 # other properties 

6909 for arg_name, byte_repr in ( 

6910 ("withdist", b"WITHDIST"), 

6911 ("withcoord", b"WITHCOORD"), 

6912 ("withhash", b"WITHHASH"), 

6913 ("store_dist", b"STOREDIST"), 

6914 ): 

6915 if kwargs[arg_name]: 

6916 pieces.append(byte_repr) 

6917 

6918 kwargs["keys"] = [args[0] if command == "GEOSEARCH" else args[1]] 

6919 

6920 return self.execute_command(command, *pieces, **kwargs) 

6921 

6922 

6923AsyncGeoCommands = GeoCommands 

6924 

6925 

6926class ModuleCommands(CommandsProtocol): 

6927 """ 

6928 Redis Module commands. 

6929 see: https://redis.io/topics/modules-intro 

6930 """ 

6931 

6932 def module_load(self, path, *args) -> ResponseT: 

6933 """ 

6934 Loads the module from ``path``. 

6935 Passes all ``*args`` to the module, during loading. 

6936 Raises ``ModuleError`` if a module is not found at ``path``. 

6937 

6938 For more information, see https://redis.io/commands/module-load 

6939 """ 

6940 return self.execute_command("MODULE LOAD", path, *args) 

6941 

6942 def module_loadex( 

6943 self, 

6944 path: str, 

6945 options: Optional[List[str]] = None, 

6946 args: Optional[List[str]] = None, 

6947 ) -> ResponseT: 

6948 """ 

6949 Loads a module from a dynamic library at runtime with configuration directives. 

6950 

6951 For more information, see https://redis.io/commands/module-loadex 

6952 """ 

6953 pieces = [] 

6954 if options is not None: 

6955 pieces.append("CONFIG") 

6956 pieces.extend(options) 

6957 if args is not None: 

6958 pieces.append("ARGS") 

6959 pieces.extend(args) 

6960 

6961 return self.execute_command("MODULE LOADEX", path, *pieces) 

6962 

6963 def module_unload(self, name) -> ResponseT: 

6964 """ 

6965 Unloads the module ``name``. 

6966 Raises ``ModuleError`` if ``name`` is not in loaded modules. 

6967 

6968 For more information, see https://redis.io/commands/module-unload 

6969 """ 

6970 return self.execute_command("MODULE UNLOAD", name) 

6971 

6972 def module_list(self) -> ResponseT: 

6973 """ 

6974 Returns a list of dictionaries containing the name and version of 

6975 all loaded modules. 

6976 

6977 For more information, see https://redis.io/commands/module-list 

6978 """ 

6979 return self.execute_command("MODULE LIST") 

6980 

6981 def command_info(self) -> None: 

6982 raise NotImplementedError( 

6983 "COMMAND INFO is intentionally not implemented in the client." 

6984 ) 

6985 

6986 def command_count(self) -> ResponseT: 

6987 return self.execute_command("COMMAND COUNT") 

6988 

6989 def command_getkeys(self, *args) -> ResponseT: 

6990 return self.execute_command("COMMAND GETKEYS", *args) 

6991 

6992 def command(self) -> ResponseT: 

6993 return self.execute_command("COMMAND") 

6994 

6995 

6996class AsyncModuleCommands(ModuleCommands): 

6997 async def command_info(self) -> None: 

6998 return super().command_info() 

6999 

7000 

7001class ClusterCommands(CommandsProtocol): 

7002 """ 

7003 Class for Redis Cluster commands 

7004 """ 

7005 

7006 def cluster(self, cluster_arg, *args, **kwargs) -> ResponseT: 

7007 return self.execute_command(f"CLUSTER {cluster_arg.upper()}", *args, **kwargs) 

7008 

7009 def readwrite(self, **kwargs) -> ResponseT: 

7010 """ 

7011 Disables read queries for a connection to a Redis Cluster slave node. 

7012 

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

7014 """ 

7015 return self.execute_command("READWRITE", **kwargs) 

7016 

7017 def readonly(self, **kwargs) -> ResponseT: 

7018 """ 

7019 Enables read queries for a connection to a Redis Cluster replica node. 

7020 

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

7022 """ 

7023 return self.execute_command("READONLY", **kwargs) 

7024 

7025 

7026AsyncClusterCommands = ClusterCommands 

7027 

7028 

7029class FunctionCommands: 

7030 """ 

7031 Redis Function commands 

7032 """ 

7033 

7034 def function_load( 

7035 self, code: str, replace: Optional[bool] = False 

7036 ) -> Union[Awaitable[str], str]: 

7037 """ 

7038 Load a library to Redis. 

7039 :param code: the source code (must start with 

7040 Shebang statement that provides a metadata about the library) 

7041 :param replace: changes the behavior to overwrite the existing library 

7042 with the new contents. 

7043 Return the library name that was loaded. 

7044 

7045 For more information, see https://redis.io/commands/function-load 

7046 """ 

7047 pieces = ["REPLACE"] if replace else [] 

7048 pieces.append(code) 

7049 return self.execute_command("FUNCTION LOAD", *pieces) 

7050 

7051 def function_delete(self, library: str) -> Union[Awaitable[str], str]: 

7052 """ 

7053 Delete the library called ``library`` and all its functions. 

7054 

7055 For more information, see https://redis.io/commands/function-delete 

7056 """ 

7057 return self.execute_command("FUNCTION DELETE", library) 

7058 

7059 def function_flush(self, mode: str = "SYNC") -> Union[Awaitable[str], str]: 

7060 """ 

7061 Deletes all the libraries. 

7062 

7063 For more information, see https://redis.io/commands/function-flush 

7064 """ 

7065 return self.execute_command("FUNCTION FLUSH", mode) 

7066 

7067 def function_list( 

7068 self, library: Optional[str] = "*", withcode: Optional[bool] = False 

7069 ) -> Union[Awaitable[List], List]: 

7070 """ 

7071 Return information about the functions and libraries. 

7072 

7073 Args: 

7074 

7075 library: specify a pattern for matching library names 

7076 withcode: cause the server to include the libraries source implementation 

7077 in the reply 

7078 """ 

7079 args = ["LIBRARYNAME", library] 

7080 if withcode: 

7081 args.append("WITHCODE") 

7082 return self.execute_command("FUNCTION LIST", *args) 

7083 

7084 def _fcall( 

7085 self, command: str, function, numkeys: int, *keys_and_args: Any 

7086 ) -> Union[Awaitable[str], str]: 

7087 return self.execute_command(command, function, numkeys, *keys_and_args) 

7088 

7089 def fcall( 

7090 self, function, numkeys: int, *keys_and_args: Any 

7091 ) -> Union[Awaitable[str], str]: 

7092 """ 

7093 Invoke a function. 

7094 

7095 For more information, see https://redis.io/commands/fcall 

7096 """ 

7097 return self._fcall("FCALL", function, numkeys, *keys_and_args) 

7098 

7099 def fcall_ro( 

7100 self, function, numkeys: int, *keys_and_args: Any 

7101 ) -> Union[Awaitable[str], str]: 

7102 """ 

7103 This is a read-only variant of the FCALL command that cannot 

7104 execute commands that modify data. 

7105 

7106 For more information, see https://redis.io/commands/fcall_ro 

7107 """ 

7108 return self._fcall("FCALL_RO", function, numkeys, *keys_and_args) 

7109 

7110 def function_dump(self) -> Union[Awaitable[str], str]: 

7111 """ 

7112 Return the serialized payload of loaded libraries. 

7113 

7114 For more information, see https://redis.io/commands/function-dump 

7115 """ 

7116 from redis.client import NEVER_DECODE 

7117 

7118 options = {} 

7119 options[NEVER_DECODE] = [] 

7120 

7121 return self.execute_command("FUNCTION DUMP", **options) 

7122 

7123 def function_restore( 

7124 self, payload: str, policy: Optional[str] = "APPEND" 

7125 ) -> Union[Awaitable[str], str]: 

7126 """ 

7127 Restore libraries from the serialized ``payload``. 

7128 You can use the optional policy argument to provide a policy 

7129 for handling existing libraries. 

7130 

7131 For more information, see https://redis.io/commands/function-restore 

7132 """ 

7133 return self.execute_command("FUNCTION RESTORE", payload, policy) 

7134 

7135 def function_kill(self) -> Union[Awaitable[str], str]: 

7136 """ 

7137 Kill a function that is currently executing. 

7138 

7139 For more information, see https://redis.io/commands/function-kill 

7140 """ 

7141 return self.execute_command("FUNCTION KILL") 

7142 

7143 def function_stats(self) -> Union[Awaitable[List], List]: 

7144 """ 

7145 Return information about the function that's currently running 

7146 and information about the available execution engines. 

7147 

7148 For more information, see https://redis.io/commands/function-stats 

7149 """ 

7150 return self.execute_command("FUNCTION STATS") 

7151 

7152 

7153AsyncFunctionCommands = FunctionCommands 

7154 

7155 

7156class DataAccessCommands( 

7157 BasicKeyCommands, 

7158 HyperlogCommands, 

7159 HashCommands, 

7160 GeoCommands, 

7161 ListCommands, 

7162 ScanCommands, 

7163 SetCommands, 

7164 StreamCommands, 

7165 SortedSetCommands, 

7166): 

7167 """ 

7168 A class containing all of the implemented data access redis commands. 

7169 This class is to be used as a mixin for synchronous Redis clients. 

7170 """ 

7171 

7172 

7173class AsyncDataAccessCommands( 

7174 AsyncBasicKeyCommands, 

7175 AsyncHyperlogCommands, 

7176 AsyncHashCommands, 

7177 AsyncGeoCommands, 

7178 AsyncListCommands, 

7179 AsyncScanCommands, 

7180 AsyncSetCommands, 

7181 AsyncStreamCommands, 

7182 AsyncSortedSetCommands, 

7183): 

7184 """ 

7185 A class containing all of the implemented data access redis commands. 

7186 This class is to be used as a mixin for asynchronous Redis clients. 

7187 """ 

7188 

7189 

7190class CoreCommands( 

7191 ACLCommands, 

7192 ClusterCommands, 

7193 DataAccessCommands, 

7194 ManagementCommands, 

7195 ModuleCommands, 

7196 PubSubCommands, 

7197 ScriptCommands, 

7198 FunctionCommands, 

7199): 

7200 """ 

7201 A class containing all of the implemented redis commands. This class is 

7202 to be used as a mixin for synchronous Redis clients. 

7203 """ 

7204 

7205 

7206class AsyncCoreCommands( 

7207 AsyncACLCommands, 

7208 AsyncClusterCommands, 

7209 AsyncDataAccessCommands, 

7210 AsyncManagementCommands, 

7211 AsyncModuleCommands, 

7212 AsyncPubSubCommands, 

7213 AsyncScriptCommands, 

7214 AsyncFunctionCommands, 

7215): 

7216 """ 

7217 A class containing all of the implemented redis commands. This class is 

7218 to be used as a mixin for asynchronous Redis clients. 

7219 """