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

1890 statements  

1# from __future__ import annotations 

2 

3import datetime 

4import hashlib 

5import warnings 

6from enum import Enum 

7from typing import ( 

8 TYPE_CHECKING, 

9 Any, 

10 AsyncIterator, 

11 Awaitable, 

12 Callable, 

13 Dict, 

14 Iterable, 

15 Iterator, 

16 List, 

17 Literal, 

18 Mapping, 

19 Optional, 

20 Sequence, 

21 Set, 

22 Tuple, 

23 Union, 

24) 

25 

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

27from redis.typing import ( 

28 AbsExpiryT, 

29 AnyKeyT, 

30 BitfieldOffsetT, 

31 ChannelT, 

32 CommandsProtocol, 

33 ConsumerT, 

34 EncodableT, 

35 ExpiryT, 

36 FieldT, 

37 GroupT, 

38 KeysT, 

39 KeyT, 

40 Number, 

41 PatternT, 

42 ResponseT, 

43 ScriptTextT, 

44 StreamIdT, 

45 TimeoutSecT, 

46 ZScoreBoundT, 

47) 

48from redis.utils import ( 

49 deprecated_function, 

50 extract_expire_flags, 

51) 

52 

53from .helpers import list_or_args 

54 

55if TYPE_CHECKING: 

56 import redis.asyncio.client 

57 import redis.client 

58 

59 

60class ACLCommands(CommandsProtocol): 

61 """ 

62 Redis Access Control List (ACL) commands. 

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

64 """ 

65 

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

67 """ 

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

69 

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

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

72 that category. 

73 

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

75 """ 

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

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

78 

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

80 """ 

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

82 

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

84 """ 

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

86 

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

88 """ 

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

90 

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

92 """ 

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

94 

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

96 """Generate a random password value. 

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

98 the next multiple of 4. 

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

100 """ 

101 pieces = [] 

102 if bits is not None: 

103 try: 

104 b = int(bits) 

105 if b < 0 or b > 4096: 

106 raise ValueError 

107 pieces.append(b) 

108 except ValueError: 

109 raise DataError( 

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

111 ) 

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

113 

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

115 """ 

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

117 

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

119 

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

121 """ 

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

123 

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

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

126 the different subcommands. 

127 

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

129 """ 

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

131 

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

133 """ 

134 Return a list of all ACLs on the server 

135 

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

137 """ 

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

139 

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

141 """ 

142 Get ACL logs as a list. 

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

144 :rtype: List. 

145 

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

147 """ 

148 args = [] 

149 if count is not None: 

150 if not isinstance(count, int): 

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

152 args.append(count) 

153 

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

155 

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

157 """ 

158 Reset ACL logs. 

159 :rtype: Boolean. 

160 

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

162 """ 

163 args = [b"RESET"] 

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

165 

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

167 """ 

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

169 

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

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

172 

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

174 """ 

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

176 

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

178 """ 

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

180 

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

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

183 

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

185 """ 

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

187 

188 def acl_setuser( 

189 self, 

190 username: str, 

191 enabled: bool = False, 

192 nopass: bool = False, 

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

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

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

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

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

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

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

200 reset: bool = False, 

201 reset_keys: bool = False, 

202 reset_channels: bool = False, 

203 reset_passwords: bool = False, 

204 **kwargs, 

205 ) -> ResponseT: 

206 """ 

207 Create or update an ACL user. 

208 

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

210 the existing ACL is completely overwritten and replaced with the 

211 specified values. 

212 

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

214 

215 Args: 

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

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

218 Defaults to `False`. 

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

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

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

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

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

224 when adding or removing a single password. 

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

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

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

228 prefixed string can be used when adding or removing a 

229 single password. 

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

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

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

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

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

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

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

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

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

239 are prefixed with ``cache:``. 

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

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

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

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

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

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

246 applied on top. 

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

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

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

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

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

252 reset prior to applying any new channel permissions 

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

254 channel permissions will be kept and any new specified 

255 channel permissions will be applied on top. 

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

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

258 passwords specified in `passwords` or `hashed_passwords`. 

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

260 status will be kept and any new specified passwords or 

261 hashed passwords will be applied on top. 

262 """ 

263 encoder = self.get_encoder() 

264 pieces: List[EncodableT] = [username] 

265 

266 if reset: 

267 pieces.append(b"reset") 

268 

269 if reset_keys: 

270 pieces.append(b"resetkeys") 

271 

272 if reset_channels: 

273 pieces.append(b"resetchannels") 

274 

275 if reset_passwords: 

276 pieces.append(b"resetpass") 

277 

278 if enabled: 

279 pieces.append(b"on") 

280 else: 

281 pieces.append(b"off") 

282 

283 if (passwords or hashed_passwords) and nopass: 

284 raise DataError( 

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

286 ) 

287 

288 if passwords: 

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

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

291 passwords = list_or_args(passwords, []) 

292 for i, password in enumerate(passwords): 

293 password = encoder.encode(password) 

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

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

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

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

298 else: 

299 raise DataError( 

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

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

302 ) 

303 

304 if hashed_passwords: 

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

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

307 hashed_passwords = list_or_args(hashed_passwords, []) 

308 for i, hashed_password in enumerate(hashed_passwords): 

309 hashed_password = encoder.encode(hashed_password) 

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

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

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

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

314 else: 

315 raise DataError( 

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

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

318 ) 

319 

320 if nopass: 

321 pieces.append(b"nopass") 

322 

323 if categories: 

324 for category in categories: 

325 category = encoder.encode(category) 

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

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

328 pieces.append(category) 

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

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

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

332 pieces.append(category) 

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

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

335 else: 

336 raise DataError( 

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

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

339 ) 

340 if commands: 

341 for cmd in commands: 

342 cmd = encoder.encode(cmd) 

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

344 raise DataError( 

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

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

347 ) 

348 pieces.append(cmd) 

349 

350 if keys: 

351 for key in keys: 

352 key = encoder.encode(key) 

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

354 key = b"~%s" % key 

355 pieces.append(key) 

356 

357 if channels: 

358 for channel in channels: 

359 channel = encoder.encode(channel) 

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

361 

362 if selectors: 

363 for cmd, key in selectors: 

364 cmd = encoder.encode(cmd) 

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

366 raise DataError( 

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

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

369 ) 

370 

371 key = encoder.encode(key) 

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

373 key = b"~%s" % key 

374 

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

376 

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

378 

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

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

381 

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

383 """ 

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

385 

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

387 """Get the username for the current connection 

388 

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

390 """ 

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

392 

393 

394AsyncACLCommands = ACLCommands 

395 

396 

397class ManagementCommands(CommandsProtocol): 

398 """ 

399 Redis management commands 

400 """ 

401 

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

403 """ 

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

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

406 authenticate for the given user. 

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

408 """ 

409 pieces = [] 

410 if username is not None: 

411 pieces.append(username) 

412 pieces.append(password) 

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

414 

415 def bgrewriteaof(self, **kwargs): 

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

417 

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

419 """ 

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

421 

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

423 """ 

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

425 this method is asynchronous and returns immediately. 

426 

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

428 """ 

429 pieces = [] 

430 if schedule: 

431 pieces.append("SCHEDULE") 

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

433 

434 def role(self) -> ResponseT: 

435 """ 

436 Provide information on the role of a Redis instance in 

437 the context of replication, by returning if the instance 

438 is currently a master, slave, or sentinel. 

439 

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

441 """ 

442 return self.execute_command("ROLE") 

443 

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

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

446 

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

448 """ 

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

450 

451 def client_kill_filter( 

452 self, 

453 _id: Optional[str] = None, 

454 _type: Optional[str] = None, 

455 addr: Optional[str] = None, 

456 skipme: Optional[bool] = None, 

457 laddr: Optional[bool] = None, 

458 user: Optional[str] = None, 

459 maxage: Optional[int] = None, 

460 **kwargs, 

461 ) -> ResponseT: 

462 """ 

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

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

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

466 'master', 'slave' or 'pubsub' 

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

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

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

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

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

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

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

474 """ 

475 args = [] 

476 if _type is not None: 

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

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

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

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

481 if skipme is not None: 

482 if not isinstance(skipme, bool): 

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

484 if skipme: 

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

486 else: 

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

488 if _id is not None: 

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

490 if addr is not None: 

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

492 if laddr is not None: 

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

494 if user is not None: 

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

496 if maxage is not None: 

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

498 if not args: 

499 raise DataError( 

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

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

502 ) 

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

504 

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

506 """ 

507 Returns information and statistics about the current 

508 client connection. 

509 

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

511 """ 

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

513 

514 def client_list( 

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

516 ) -> ResponseT: 

517 """ 

518 Returns a list of currently connected clients. 

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

520 

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

522 replica, pubsub) 

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

524 

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

526 """ 

527 args = [] 

528 if _type is not None: 

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

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

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

532 args.append(b"TYPE") 

533 args.append(_type) 

534 if not isinstance(client_id, list): 

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

536 if client_id: 

537 args.append(b"ID") 

538 args += client_id 

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

540 

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

542 """ 

543 Returns the current connection name 

544 

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

546 """ 

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

548 

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

550 """ 

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

552 redirecting tracking notifications. 

553 

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

555 """ 

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

557 

558 def client_reply( 

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

560 ) -> ResponseT: 

561 """ 

562 Enable and disable redis server replies. 

563 

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

565 ON - The default most with server replies to commands 

566 OFF - Disable server responses to commands 

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

568 

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

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

571 TimeoutError. 

572 The test_client_reply unit test illustrates this, and 

573 conftest.py has a client with a timeout. 

574 

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

576 """ 

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

578 if reply not in replies: 

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

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

581 

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

583 """ 

584 Returns the current connection id 

585 

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

587 """ 

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

589 

590 def client_tracking_on( 

591 self, 

592 clientid: Optional[int] = None, 

593 prefix: Sequence[KeyT] = [], 

594 bcast: bool = False, 

595 optin: bool = False, 

596 optout: bool = False, 

597 noloop: bool = False, 

598 ) -> ResponseT: 

599 """ 

600 Turn on the tracking mode. 

601 For more information about the options look at client_tracking func. 

602 

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

604 """ 

605 return self.client_tracking( 

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

607 ) 

608 

609 def client_tracking_off( 

610 self, 

611 clientid: Optional[int] = None, 

612 prefix: Sequence[KeyT] = [], 

613 bcast: bool = False, 

614 optin: bool = False, 

615 optout: bool = False, 

616 noloop: bool = False, 

617 ) -> ResponseT: 

618 """ 

619 Turn off the tracking mode. 

620 For more information about the options look at client_tracking func. 

621 

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

623 """ 

624 return self.client_tracking( 

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

626 ) 

627 

628 def client_tracking( 

629 self, 

630 on: bool = True, 

631 clientid: Optional[int] = None, 

632 prefix: Sequence[KeyT] = [], 

633 bcast: bool = False, 

634 optin: bool = False, 

635 optout: bool = False, 

636 noloop: bool = False, 

637 **kwargs, 

638 ) -> ResponseT: 

639 """ 

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

641 for server assisted client side caching. 

642 

643 ``on`` indicate for tracking on or tracking off. The dafualt is on. 

644 

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

646 the specified ID. 

647 

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

649 invalidation messages are reported for all the prefixes 

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

651 

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

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

654 after a CLIENT CACHING yes command. 

655 

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

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

658 CLIENT CACHING no command. 

659 

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

661 connection itself. 

662 

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

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

665 

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

667 """ 

668 

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

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

671 

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

673 if clientid is not None: 

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

675 for p in prefix: 

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

677 if bcast: 

678 pieces.append("BCAST") 

679 if optin: 

680 pieces.append("OPTIN") 

681 if optout: 

682 pieces.append("OPTOUT") 

683 if noloop: 

684 pieces.append("NOLOOP") 

685 

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

687 

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

689 """ 

690 Returns the information about the current client connection's 

691 use of the server assisted client side cache. 

692 

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

694 """ 

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

696 

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

698 """ 

699 Sets the current connection name 

700 

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

702 

703 .. note:: 

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

705 

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

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

708 """ 

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

710 

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

712 """ 

713 Sets the current connection library name or version 

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

715 """ 

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

717 

718 def client_unblock( 

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

720 ) -> ResponseT: 

721 """ 

722 Unblocks a connection by its client id. 

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

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

725 regular timeout mechanism. 

726 

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

728 """ 

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

730 if error: 

731 args.append(b"ERROR") 

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

733 

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

735 """ 

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

737 

738 

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

740 

741 Args: 

742 timeout: milliseconds to pause clients 

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

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

745 a write command. 

746 

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

748 

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

750 * PUBLISH: Will block client. 

751 * PFCOUNT: Will block client. 

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

753 appear blocked. 

754 """ 

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

756 if not isinstance(timeout, int): 

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

758 if not all: 

759 args.append("WRITE") 

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

761 

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

763 """ 

764 Unpause all redis clients 

765 

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

767 """ 

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

769 

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

771 """ 

772 Sets the client eviction mode for the current connection. 

773 

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

775 """ 

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

777 

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

779 """ 

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

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

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

783 # unless it sends the TOUCH command. 

784 

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

786 """ 

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

788 

789 def command(self, **kwargs): 

790 """ 

791 Returns dict reply of details about all Redis commands. 

792 

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

794 """ 

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

796 

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

798 raise NotImplementedError( 

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

800 ) 

801 

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

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

804 

805 def command_list( 

806 self, 

807 module: Optional[str] = None, 

808 category: Optional[str] = None, 

809 pattern: Optional[str] = None, 

810 ) -> ResponseT: 

811 """ 

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

813 You can use one of the following filters: 

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

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

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

817 

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

819 """ 

820 pieces = [] 

821 if module is not None: 

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

823 if category is not None: 

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

825 if pattern is not None: 

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

827 

828 if pieces: 

829 pieces.insert(0, "FILTERBY") 

830 

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

832 

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

834 """ 

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

836 

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

838 """ 

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

840 

841 def command_docs(self, *args): 

842 """ 

843 This function throws a NotImplementedError since it is intentionally 

844 not supported. 

845 """ 

846 raise NotImplementedError( 

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

848 ) 

849 

850 def config_get( 

851 self, pattern: PatternT = "*", *args: List[PatternT], **kwargs 

852 ) -> ResponseT: 

853 """ 

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

855 

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

857 """ 

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

859 

860 def config_set( 

861 self, 

862 name: KeyT, 

863 value: EncodableT, 

864 *args: List[Union[KeyT, EncodableT]], 

865 **kwargs, 

866 ) -> ResponseT: 

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

868 

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

870 """ 

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

872 

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

874 """ 

875 Reset runtime statistics 

876 

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

878 """ 

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

880 

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

882 """ 

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

884 

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

886 """ 

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

888 

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

890 """ 

891 Returns the number of keys in the current database 

892 

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

894 """ 

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

896 

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

898 """ 

899 Returns version specific meta information about a given key 

900 

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

902 """ 

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

904 

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

906 raise NotImplementedError( 

907 """ 

908 DEBUG SEGFAULT is intentionally not implemented in the client. 

909 

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

911 """ 

912 ) 

913 

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

915 """ 

916 Echo the string back from the server 

917 

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

919 """ 

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

921 

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

923 """ 

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

925 

926 ``asynchronous`` indicates whether the operation is 

927 executed asynchronously by the server. 

928 

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

930 """ 

931 args = [] 

932 if asynchronous: 

933 args.append(b"ASYNC") 

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

935 

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

937 """ 

938 Delete all keys in the current database. 

939 

940 ``asynchronous`` indicates whether the operation is 

941 executed asynchronously by the server. 

942 

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

944 """ 

945 args = [] 

946 if asynchronous: 

947 args.append(b"ASYNC") 

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

949 

950 def sync(self) -> ResponseT: 

951 """ 

952 Initiates a replication stream from the master. 

953 

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

955 """ 

956 from redis.client import NEVER_DECODE 

957 

958 options = {} 

959 options[NEVER_DECODE] = [] 

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

961 

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

963 """ 

964 Initiates a replication stream from the master. 

965 Newer version for `sync`. 

966 

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

968 """ 

969 from redis.client import NEVER_DECODE 

970 

971 options = {} 

972 options[NEVER_DECODE] = [] 

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

974 

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

976 """ 

977 Swap two databases 

978 

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

980 """ 

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

982 

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

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

985 

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

987 """ 

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

989 

990 def info( 

991 self, section: Optional[str] = None, *args: List[str], **kwargs 

992 ) -> ResponseT: 

993 """ 

994 Returns a dictionary containing information about the Redis server 

995 

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

997 of information 

998 

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

1000 and will generate ResponseError 

1001 

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

1003 """ 

1004 if section is None: 

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

1006 else: 

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

1008 

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

1010 """ 

1011 Return a Python datetime object representing the last time the 

1012 Redis database was saved to disk 

1013 

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

1015 """ 

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

1017 

1018 def latency_doctor(self): 

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

1020 This funcion is best used within the redis-cli. 

1021 

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

1023 """ 

1024 raise NotImplementedError( 

1025 """ 

1026 LATENCY DOCTOR is intentionally not implemented in the client. 

1027 

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

1029 """ 

1030 ) 

1031 

1032 def latency_graph(self): 

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

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

1035 

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

1037 """ 

1038 raise NotImplementedError( 

1039 """ 

1040 LATENCY GRAPH is intentionally not implemented in the client. 

1041 

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

1043 """ 

1044 ) 

1045 

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

1047 """ 

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

1049 

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

1051 """ 

1052 if version_numbers: 

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

1054 else: 

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

1056 

1057 def reset(self) -> ResponseT: 

1058 """Perform a full reset on the connection's server side contenxt. 

1059 

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

1061 """ 

1062 return self.execute_command("RESET") 

1063 

1064 def migrate( 

1065 self, 

1066 host: str, 

1067 port: int, 

1068 keys: KeysT, 

1069 destination_db: int, 

1070 timeout: int, 

1071 copy: bool = False, 

1072 replace: bool = False, 

1073 auth: Optional[str] = None, 

1074 **kwargs, 

1075 ) -> ResponseT: 

1076 """ 

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

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

1079 

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

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

1082 command is interrupted. 

1083 

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

1085 the source server. 

1086 

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

1088 on the destination server if they exist. 

1089 

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

1091 the password provided. 

1092 

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

1094 """ 

1095 keys = list_or_args(keys, []) 

1096 if not keys: 

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

1098 pieces = [] 

1099 if copy: 

1100 pieces.append(b"COPY") 

1101 if replace: 

1102 pieces.append(b"REPLACE") 

1103 if auth: 

1104 pieces.append(b"AUTH") 

1105 pieces.append(auth) 

1106 pieces.append(b"KEYS") 

1107 pieces.extend(keys) 

1108 return self.execute_command( 

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

1110 ) 

1111 

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

1113 """ 

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

1115 """ 

1116 return self.execute_command( 

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

1118 ) 

1119 

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

1121 raise NotImplementedError( 

1122 """ 

1123 MEMORY DOCTOR is intentionally not implemented in the client. 

1124 

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

1126 """ 

1127 ) 

1128 

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

1130 raise NotImplementedError( 

1131 """ 

1132 MEMORY HELP is intentionally not implemented in the client. 

1133 

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

1135 """ 

1136 ) 

1137 

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

1139 """ 

1140 Return a dictionary of memory stats 

1141 

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

1143 """ 

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

1145 

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

1147 """ 

1148 Return an internal statistics report from the memory allocator. 

1149 

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

1151 """ 

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

1153 

1154 def memory_usage( 

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

1156 ) -> ResponseT: 

1157 """ 

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

1159 administrative overheads. 

1160 

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

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

1163 all elements. 

1164 

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

1166 """ 

1167 args = [] 

1168 if isinstance(samples, int): 

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

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

1171 

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

1173 """ 

1174 Attempts to purge dirty pages for reclamation by allocator 

1175 

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

1177 """ 

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

1179 

1180 def latency_histogram(self, *args): 

1181 """ 

1182 This function throws a NotImplementedError since it is intentionally 

1183 not supported. 

1184 """ 

1185 raise NotImplementedError( 

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

1187 ) 

1188 

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

1190 """ 

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

1192 

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

1194 """ 

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

1196 

1197 def latency_latest(self) -> ResponseT: 

1198 """ 

1199 Reports the latest latency events logged. 

1200 

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

1202 """ 

1203 return self.execute_command("LATENCY LATEST") 

1204 

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

1206 """ 

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

1208 

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

1210 """ 

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

1212 

1213 def ping(self, **kwargs) -> ResponseT: 

1214 """ 

1215 Ping the Redis server 

1216 

1217 For more information see https://redis.io/commands/ping 

1218 """ 

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

1220 

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

1222 """ 

1223 Ask the server to close the connection. 

1224 

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

1226 """ 

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

1228 

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

1230 """ 

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

1232 

1233 Examples of valid arguments include: 

1234 

1235 NO ONE (set no replication) 

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

1237 

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

1239 """ 

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

1241 

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

1243 """ 

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

1245 blocking until the save is complete 

1246 

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

1248 """ 

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

1250 

1251 def shutdown( 

1252 self, 

1253 save: bool = False, 

1254 nosave: bool = False, 

1255 now: bool = False, 

1256 force: bool = False, 

1257 abort: bool = False, 

1258 **kwargs, 

1259 ) -> None: 

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

1261 data will be flushed before shutdown. 

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

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

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

1265 are configured. 

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

1267 the shutdown sequence. 

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

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

1270 

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

1272 """ 

1273 if save and nosave: 

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

1275 args = ["SHUTDOWN"] 

1276 if save: 

1277 args.append("SAVE") 

1278 if nosave: 

1279 args.append("NOSAVE") 

1280 if now: 

1281 args.append("NOW") 

1282 if force: 

1283 args.append("FORCE") 

1284 if abort: 

1285 args.append("ABORT") 

1286 try: 

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

1288 except ConnectionError: 

1289 # a ConnectionError here is expected 

1290 return 

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

1292 

1293 def slaveof( 

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

1295 ) -> ResponseT: 

1296 """ 

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

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

1299 instance is promoted to a master instead. 

1300 

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

1302 """ 

1303 if host is None and port is None: 

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

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

1306 

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

1308 """ 

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

1310 most recent ``num`` items. 

1311 

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

1313 """ 

1314 from redis.client import NEVER_DECODE 

1315 

1316 args = ["SLOWLOG GET"] 

1317 if num is not None: 

1318 args.append(num) 

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

1320 if decode_responses is True: 

1321 kwargs[NEVER_DECODE] = [] 

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

1323 

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

1325 """ 

1326 Get the number of items in the slowlog 

1327 

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

1329 """ 

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

1331 

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

1333 """ 

1334 Remove all items in the slowlog 

1335 

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

1337 """ 

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

1339 

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

1341 """ 

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

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

1344 

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

1346 """ 

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

1348 

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

1350 """ 

1351 Redis synchronous replication 

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

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

1354 reached. 

1355 

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

1357 """ 

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

1359 

1360 def waitaof( 

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

1362 ) -> ResponseT: 

1363 """ 

1364 This command blocks the current client until all previous write 

1365 commands by that client are acknowledged as having been fsynced 

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

1367 of replicas. 

1368 

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

1370 """ 

1371 return self.execute_command( 

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

1373 ) 

1374 

1375 def hello(self): 

1376 """ 

1377 This function throws a NotImplementedError since it is intentionally 

1378 not supported. 

1379 """ 

1380 raise NotImplementedError( 

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

1382 ) 

1383 

1384 def failover(self): 

1385 """ 

1386 This function throws a NotImplementedError since it is intentionally 

1387 not supported. 

1388 """ 

1389 raise NotImplementedError( 

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

1391 ) 

1392 

1393 

1394class AsyncManagementCommands(ManagementCommands): 

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

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

1397 

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

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

1400 

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

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

1403 

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

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

1406 

1407 async def shutdown( 

1408 self, 

1409 save: bool = False, 

1410 nosave: bool = False, 

1411 now: bool = False, 

1412 force: bool = False, 

1413 abort: bool = False, 

1414 **kwargs, 

1415 ) -> None: 

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

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

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

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

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

1421 

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

1423 """ 

1424 if save and nosave: 

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

1426 args = ["SHUTDOWN"] 

1427 if save: 

1428 args.append("SAVE") 

1429 if nosave: 

1430 args.append("NOSAVE") 

1431 if now: 

1432 args.append("NOW") 

1433 if force: 

1434 args.append("FORCE") 

1435 if abort: 

1436 args.append("ABORT") 

1437 try: 

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

1439 except ConnectionError: 

1440 # a ConnectionError here is expected 

1441 return 

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

1443 

1444 

1445class BitFieldOperation: 

1446 """ 

1447 Command builder for BITFIELD commands. 

1448 """ 

1449 

1450 def __init__( 

1451 self, 

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

1453 key: str, 

1454 default_overflow: Optional[str] = None, 

1455 ): 

1456 self.client = client 

1457 self.key = key 

1458 self._default_overflow = default_overflow 

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

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

1461 self._last_overflow = "WRAP" 

1462 self.reset() 

1463 

1464 def reset(self): 

1465 """ 

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

1467 """ 

1468 self.operations = [] 

1469 self._last_overflow = "WRAP" 

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

1471 

1472 def overflow(self, overflow: str): 

1473 """ 

1474 Update the overflow algorithm of successive INCRBY operations 

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

1476 Redis docs for descriptions of these algorithmsself. 

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

1478 """ 

1479 overflow = overflow.upper() 

1480 if overflow != self._last_overflow: 

1481 self._last_overflow = overflow 

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

1483 return self 

1484 

1485 def incrby( 

1486 self, 

1487 fmt: str, 

1488 offset: BitfieldOffsetT, 

1489 increment: int, 

1490 overflow: Optional[str] = None, 

1491 ): 

1492 """ 

1493 Increment a bitfield by a given amount. 

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

1495 for an unsigned 8-bit integer. 

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

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

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

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

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

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

1502 descriptions of these algorithms. 

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

1504 """ 

1505 if overflow is not None: 

1506 self.overflow(overflow) 

1507 

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

1509 return self 

1510 

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

1512 """ 

1513 Get the value of a given bitfield. 

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

1515 an unsigned 8-bit integer. 

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

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

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

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

1520 """ 

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

1522 return self 

1523 

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

1525 """ 

1526 Set the value of a given bitfield. 

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

1528 an unsigned 8-bit integer. 

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

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

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

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

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

1534 """ 

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

1536 return self 

1537 

1538 @property 

1539 def command(self): 

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

1541 for ops in self.operations: 

1542 cmd.extend(ops) 

1543 return cmd 

1544 

1545 def execute(self) -> ResponseT: 

1546 """ 

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

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

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

1550 will be present within the pipeline's execute. 

1551 """ 

1552 command = self.command 

1553 self.reset() 

1554 return self.client.execute_command(*command) 

1555 

1556 

1557class BasicKeyCommands(CommandsProtocol): 

1558 """ 

1559 Redis basic key-based commands 

1560 """ 

1561 

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

1563 """ 

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

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

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

1567 

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

1569 """ 

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

1571 

1572 def bitcount( 

1573 self, 

1574 key: KeyT, 

1575 start: Optional[int] = None, 

1576 end: Optional[int] = None, 

1577 mode: Optional[str] = None, 

1578 ) -> ResponseT: 

1579 """ 

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

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

1582 

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

1584 """ 

1585 params = [key] 

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

1587 params.append(start) 

1588 params.append(end) 

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

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

1591 if mode is not None: 

1592 params.append(mode) 

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

1594 

1595 def bitfield( 

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

1597 key: KeyT, 

1598 default_overflow: Optional[str] = None, 

1599 ) -> BitFieldOperation: 

1600 """ 

1601 Return a BitFieldOperation instance to conveniently construct one or 

1602 more bitfield operations on ``key``. 

1603 

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

1605 """ 

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

1607 

1608 def bitfield_ro( 

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

1610 key: KeyT, 

1611 encoding: str, 

1612 offset: BitfieldOffsetT, 

1613 items: Optional[list] = None, 

1614 ) -> ResponseT: 

1615 """ 

1616 Return an array of the specified bitfield values 

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

1618 parameters and remaining values are result of corresponding 

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

1620 Read-only variant of the BITFIELD command. 

1621 

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

1623 """ 

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

1625 

1626 items = items or [] 

1627 for encoding, offset in items: 

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

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

1630 

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

1632 """ 

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

1634 store the result in ``dest``. 

1635 

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

1637 """ 

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

1639 

1640 def bitpos( 

1641 self, 

1642 key: KeyT, 

1643 bit: int, 

1644 start: Optional[int] = None, 

1645 end: Optional[int] = None, 

1646 mode: Optional[str] = None, 

1647 ) -> ResponseT: 

1648 """ 

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

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

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

1652 means to look at the first three bytes. 

1653 

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

1655 """ 

1656 if bit not in (0, 1): 

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

1658 params = [key, bit] 

1659 

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

1661 

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

1663 params.append(end) 

1664 elif start is None and end is not None: 

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

1666 

1667 if mode is not None: 

1668 params.append(mode) 

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

1670 

1671 def copy( 

1672 self, 

1673 source: str, 

1674 destination: str, 

1675 destination_db: Optional[str] = None, 

1676 replace: bool = False, 

1677 ) -> ResponseT: 

1678 """ 

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

1680 

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

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

1683 

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

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

1686 the ``destination`` key already exists. 

1687 

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

1689 """ 

1690 params = [source, destination] 

1691 if destination_db is not None: 

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

1693 if replace: 

1694 params.append("REPLACE") 

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

1696 

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

1698 """ 

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

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

1701 

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

1703 """ 

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

1705 

1706 decr = decrby 

1707 

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

1709 """ 

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

1711 """ 

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

1713 

1714 def __delitem__(self, name: KeyT): 

1715 self.delete(name) 

1716 

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

1718 """ 

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

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

1721 

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

1723 """ 

1724 from redis.client import NEVER_DECODE 

1725 

1726 options = {} 

1727 options[NEVER_DECODE] = [] 

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

1729 

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

1731 """ 

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

1733 

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

1735 """ 

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

1737 

1738 __contains__ = exists 

1739 

1740 def expire( 

1741 self, 

1742 name: KeyT, 

1743 time: ExpiryT, 

1744 nx: bool = False, 

1745 xx: bool = False, 

1746 gt: bool = False, 

1747 lt: bool = False, 

1748 ) -> ResponseT: 

1749 """ 

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

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

1752 object. 

1753 

1754 Valid options are: 

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

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

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

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

1759 

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

1761 """ 

1762 if isinstance(time, datetime.timedelta): 

1763 time = int(time.total_seconds()) 

1764 

1765 exp_option = list() 

1766 if nx: 

1767 exp_option.append("NX") 

1768 if xx: 

1769 exp_option.append("XX") 

1770 if gt: 

1771 exp_option.append("GT") 

1772 if lt: 

1773 exp_option.append("LT") 

1774 

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

1776 

1777 def expireat( 

1778 self, 

1779 name: KeyT, 

1780 when: AbsExpiryT, 

1781 nx: bool = False, 

1782 xx: bool = False, 

1783 gt: bool = False, 

1784 lt: bool = False, 

1785 ) -> ResponseT: 

1786 """ 

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

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

1789 datetime object. 

1790 

1791 Valid options are: 

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

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

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

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

1796 

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

1798 """ 

1799 if isinstance(when, datetime.datetime): 

1800 when = int(when.timestamp()) 

1801 

1802 exp_option = list() 

1803 if nx: 

1804 exp_option.append("NX") 

1805 if xx: 

1806 exp_option.append("XX") 

1807 if gt: 

1808 exp_option.append("GT") 

1809 if lt: 

1810 exp_option.append("LT") 

1811 

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

1813 

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

1815 """ 

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

1817 at which the given key will expire. 

1818 

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

1820 """ 

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

1822 

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

1824 """ 

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

1826 

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

1828 """ 

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

1830 

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

1832 """ 

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

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

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

1836 is a string). 

1837 

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

1839 """ 

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

1841 

1842 def getex( 

1843 self, 

1844 name: KeyT, 

1845 ex: Optional[ExpiryT] = None, 

1846 px: Optional[ExpiryT] = None, 

1847 exat: Optional[AbsExpiryT] = None, 

1848 pxat: Optional[AbsExpiryT] = None, 

1849 persist: bool = False, 

1850 ) -> ResponseT: 

1851 """ 

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

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

1854 additional options. All time parameters can be given as 

1855 datetime.timedelta or integers. 

1856 

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

1858 

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

1860 

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

1862 specified in unix time. 

1863 

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

1865 specified in unix time. 

1866 

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

1868 

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

1870 """ 

1871 opset = {ex, px, exat, pxat} 

1872 if len(opset) > 2 or len(opset) > 1 and persist: 

1873 raise DataError( 

1874 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

1876 ) 

1877 

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

1879 

1880 if persist: 

1881 exp_options.append("PERSIST") 

1882 

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

1884 

1885 def __getitem__(self, name: KeyT): 

1886 """ 

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

1888 doesn't exist. 

1889 """ 

1890 value = self.get(name) 

1891 if value is not None: 

1892 return value 

1893 raise KeyError(name) 

1894 

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

1896 """ 

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

1898 

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

1900 """ 

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

1902 

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

1904 """ 

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

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

1907 

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

1909 """ 

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

1911 

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

1913 """ 

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

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

1916 

1917 As per Redis 6.2, GETSET is considered deprecated. 

1918 Please use SET with GET parameter in new code. 

1919 

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

1921 """ 

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

1923 

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

1925 """ 

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

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

1928 

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

1930 """ 

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

1932 

1933 incr = incrby 

1934 

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

1936 """ 

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

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

1939 

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

1941 """ 

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

1943 

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

1945 """ 

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

1947 

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

1949 """ 

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

1951 

1952 def lmove( 

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

1954 ) -> ResponseT: 

1955 """ 

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

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

1958 Returns the element being popped and pushed. 

1959 

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

1961 """ 

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

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

1964 

1965 def blmove( 

1966 self, 

1967 first_list: str, 

1968 second_list: str, 

1969 timeout: int, 

1970 src: str = "LEFT", 

1971 dest: str = "RIGHT", 

1972 ) -> ResponseT: 

1973 """ 

1974 Blocking version of lmove. 

1975 

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

1977 """ 

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

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

1980 

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

1982 """ 

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

1984 

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

1986 """ 

1987 from redis.client import EMPTY_RESPONSE 

1988 

1989 args = list_or_args(keys, args) 

1990 options = {} 

1991 if not args: 

1992 options[EMPTY_RESPONSE] = [] 

1993 options["keys"] = args 

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

1995 

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

1997 """ 

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

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

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

2001 

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

2003 """ 

2004 items = [] 

2005 for pair in mapping.items(): 

2006 items.extend(pair) 

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

2008 

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

2010 """ 

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

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

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

2014 Returns a boolean indicating if the operation was successful. 

2015 

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

2017 """ 

2018 items = [] 

2019 for pair in mapping.items(): 

2020 items.extend(pair) 

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

2022 

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

2024 """ 

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

2026 

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

2028 """ 

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

2030 

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

2032 """ 

2033 Removes an expiration on ``name`` 

2034 

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

2036 """ 

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

2038 

2039 def pexpire( 

2040 self, 

2041 name: KeyT, 

2042 time: ExpiryT, 

2043 nx: bool = False, 

2044 xx: bool = False, 

2045 gt: bool = False, 

2046 lt: bool = False, 

2047 ) -> ResponseT: 

2048 """ 

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

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

2051 integer or a Python timedelta object. 

2052 

2053 Valid options are: 

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

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

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

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

2058 

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

2060 """ 

2061 if isinstance(time, datetime.timedelta): 

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

2063 

2064 exp_option = list() 

2065 if nx: 

2066 exp_option.append("NX") 

2067 if xx: 

2068 exp_option.append("XX") 

2069 if gt: 

2070 exp_option.append("GT") 

2071 if lt: 

2072 exp_option.append("LT") 

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

2074 

2075 def pexpireat( 

2076 self, 

2077 name: KeyT, 

2078 when: AbsExpiryT, 

2079 nx: bool = False, 

2080 xx: bool = False, 

2081 gt: bool = False, 

2082 lt: bool = False, 

2083 ) -> ResponseT: 

2084 """ 

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

2086 can be represented as an integer representing unix time in 

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

2088 

2089 Valid options are: 

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

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

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

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

2094 

2095 For more information see https://redis.io/commands/pexpireat 

2096 """ 

2097 if isinstance(when, datetime.datetime): 

2098 when = int(when.timestamp() * 1000) 

2099 exp_option = list() 

2100 if nx: 

2101 exp_option.append("NX") 

2102 if xx: 

2103 exp_option.append("XX") 

2104 if gt: 

2105 exp_option.append("GT") 

2106 if lt: 

2107 exp_option.append("LT") 

2108 return self.execute_command("PEXPIREAT", name, when, *exp_option) 

2109 

2110 def pexpiretime(self, key: str) -> int: 

2111 """ 

2112 Returns the absolute Unix timestamp (since January 1, 1970) in milliseconds 

2113 at which the given key will expire. 

2114 

2115 For more information see https://redis.io/commands/pexpiretime 

2116 """ 

2117 return self.execute_command("PEXPIRETIME", key) 

2118 

2119 def psetex(self, name: KeyT, time_ms: ExpiryT, value: EncodableT): 

2120 """ 

2121 Set the value of key ``name`` to ``value`` that expires in ``time_ms`` 

2122 milliseconds. ``time_ms`` can be represented by an integer or a Python 

2123 timedelta object 

2124 

2125 For more information see https://redis.io/commands/psetex 

2126 """ 

2127 if isinstance(time_ms, datetime.timedelta): 

2128 time_ms = int(time_ms.total_seconds() * 1000) 

2129 return self.execute_command("PSETEX", name, time_ms, value) 

2130 

2131 def pttl(self, name: KeyT) -> ResponseT: 

2132 """ 

2133 Returns the number of milliseconds until the key ``name`` will expire 

2134 

2135 For more information see https://redis.io/commands/pttl 

2136 """ 

2137 return self.execute_command("PTTL", name) 

2138 

2139 def hrandfield( 

2140 self, key: str, count: Optional[int] = None, withvalues: bool = False 

2141 ) -> ResponseT: 

2142 """ 

2143 Return a random field from the hash value stored at key. 

2144 

2145 count: if the argument is positive, return an array of distinct fields. 

2146 If called with a negative count, the behavior changes and the command 

2147 is allowed to return the same field multiple times. In this case, 

2148 the number of returned fields is the absolute value of the 

2149 specified count. 

2150 withvalues: The optional WITHVALUES modifier changes the reply so it 

2151 includes the respective values of the randomly selected hash fields. 

2152 

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

2154 """ 

2155 params = [] 

2156 if count is not None: 

2157 params.append(count) 

2158 if withvalues: 

2159 params.append("WITHVALUES") 

2160 

2161 return self.execute_command("HRANDFIELD", key, *params) 

2162 

2163 def randomkey(self, **kwargs) -> ResponseT: 

2164 """ 

2165 Returns the name of a random key 

2166 

2167 For more information see https://redis.io/commands/randomkey 

2168 """ 

2169 return self.execute_command("RANDOMKEY", **kwargs) 

2170 

2171 def rename(self, src: KeyT, dst: KeyT) -> ResponseT: 

2172 """ 

2173 Rename key ``src`` to ``dst`` 

2174 

2175 For more information see https://redis.io/commands/rename 

2176 """ 

2177 return self.execute_command("RENAME", src, dst) 

2178 

2179 def renamenx(self, src: KeyT, dst: KeyT): 

2180 """ 

2181 Rename key ``src`` to ``dst`` if ``dst`` doesn't already exist 

2182 

2183 For more information see https://redis.io/commands/renamenx 

2184 """ 

2185 return self.execute_command("RENAMENX", src, dst) 

2186 

2187 def restore( 

2188 self, 

2189 name: KeyT, 

2190 ttl: float, 

2191 value: EncodableT, 

2192 replace: bool = False, 

2193 absttl: bool = False, 

2194 idletime: Optional[int] = None, 

2195 frequency: Optional[int] = None, 

2196 ) -> ResponseT: 

2197 """ 

2198 Create a key using the provided serialized value, previously obtained 

2199 using DUMP. 

2200 

2201 ``replace`` allows an existing key on ``name`` to be overridden. If 

2202 it's not specified an error is raised on collision. 

2203 

2204 ``absttl`` if True, specified ``ttl`` should represent an absolute Unix 

2205 timestamp in milliseconds in which the key will expire. (Redis 5.0 or 

2206 greater). 

2207 

2208 ``idletime`` Used for eviction, this is the number of seconds the 

2209 key must be idle, prior to execution. 

2210 

2211 ``frequency`` Used for eviction, this is the frequency counter of 

2212 the object stored at the key, prior to execution. 

2213 

2214 For more information see https://redis.io/commands/restore 

2215 """ 

2216 params = [name, ttl, value] 

2217 if replace: 

2218 params.append("REPLACE") 

2219 if absttl: 

2220 params.append("ABSTTL") 

2221 if idletime is not None: 

2222 params.append("IDLETIME") 

2223 try: 

2224 params.append(int(idletime)) 

2225 except ValueError: 

2226 raise DataError("idletimemust be an integer") 

2227 

2228 if frequency is not None: 

2229 params.append("FREQ") 

2230 try: 

2231 params.append(int(frequency)) 

2232 except ValueError: 

2233 raise DataError("frequency must be an integer") 

2234 

2235 return self.execute_command("RESTORE", *params) 

2236 

2237 def set( 

2238 self, 

2239 name: KeyT, 

2240 value: EncodableT, 

2241 ex: Optional[ExpiryT] = None, 

2242 px: Optional[ExpiryT] = None, 

2243 nx: bool = False, 

2244 xx: bool = False, 

2245 keepttl: bool = False, 

2246 get: bool = False, 

2247 exat: Optional[AbsExpiryT] = None, 

2248 pxat: Optional[AbsExpiryT] = None, 

2249 ) -> ResponseT: 

2250 """ 

2251 Set the value at key ``name`` to ``value`` 

2252 

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

2254 

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

2256 

2257 ``nx`` if set to True, set the value at key ``name`` to ``value`` only 

2258 if it does not exist. 

2259 

2260 ``xx`` if set to True, set the value at key ``name`` to ``value`` only 

2261 if it already exists. 

2262 

2263 ``keepttl`` if True, retain the time to live associated with the key. 

2264 (Available since Redis 6.0) 

2265 

2266 ``get`` if True, set the value at key ``name`` to ``value`` and return 

2267 the old value stored at key, or None if the key did not exist. 

2268 (Available since Redis 6.2) 

2269 

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

2271 specified in unix time. 

2272 

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

2274 specified in unix time. 

2275 

2276 For more information see https://redis.io/commands/set 

2277 """ 

2278 opset = {ex, px, exat, pxat} 

2279 if len(opset) > 2 or len(opset) > 1 and keepttl: 

2280 raise DataError( 

2281 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

2283 ) 

2284 

2285 if nx and xx: 

2286 raise DataError("``nx`` and ``xx`` are mutually exclusive.") 

2287 

2288 pieces: list[EncodableT] = [name, value] 

2289 options = {} 

2290 

2291 pieces.extend(extract_expire_flags(ex, px, exat, pxat)) 

2292 

2293 if keepttl: 

2294 pieces.append("KEEPTTL") 

2295 

2296 if nx: 

2297 pieces.append("NX") 

2298 if xx: 

2299 pieces.append("XX") 

2300 

2301 if get: 

2302 pieces.append("GET") 

2303 options["get"] = True 

2304 

2305 return self.execute_command("SET", *pieces, **options) 

2306 

2307 def __setitem__(self, name: KeyT, value: EncodableT): 

2308 self.set(name, value) 

2309 

2310 def setbit(self, name: KeyT, offset: int, value: int) -> ResponseT: 

2311 """ 

2312 Flag the ``offset`` in ``name`` as ``value``. Returns an integer 

2313 indicating the previous value of ``offset``. 

2314 

2315 For more information see https://redis.io/commands/setbit 

2316 """ 

2317 value = value and 1 or 0 

2318 return self.execute_command("SETBIT", name, offset, value) 

2319 

2320 def setex(self, name: KeyT, time: ExpiryT, value: EncodableT) -> ResponseT: 

2321 """ 

2322 Set the value of key ``name`` to ``value`` that expires in ``time`` 

2323 seconds. ``time`` can be represented by an integer or a Python 

2324 timedelta object. 

2325 

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

2327 """ 

2328 if isinstance(time, datetime.timedelta): 

2329 time = int(time.total_seconds()) 

2330 return self.execute_command("SETEX", name, time, value) 

2331 

2332 def setnx(self, name: KeyT, value: EncodableT) -> ResponseT: 

2333 """ 

2334 Set the value of key ``name`` to ``value`` if key doesn't exist 

2335 

2336 For more information see https://redis.io/commands/setnx 

2337 """ 

2338 return self.execute_command("SETNX", name, value) 

2339 

2340 def setrange(self, name: KeyT, offset: int, value: EncodableT) -> ResponseT: 

2341 """ 

2342 Overwrite bytes in the value of ``name`` starting at ``offset`` with 

2343 ``value``. If ``offset`` plus the length of ``value`` exceeds the 

2344 length of the original value, the new value will be larger than before. 

2345 If ``offset`` exceeds the length of the original value, null bytes 

2346 will be used to pad between the end of the previous value and the start 

2347 of what's being injected. 

2348 

2349 Returns the length of the new string. 

2350 

2351 For more information see https://redis.io/commands/setrange 

2352 """ 

2353 return self.execute_command("SETRANGE", name, offset, value) 

2354 

2355 def stralgo( 

2356 self, 

2357 algo: Literal["LCS"], 

2358 value1: KeyT, 

2359 value2: KeyT, 

2360 specific_argument: Union[Literal["strings"], Literal["keys"]] = "strings", 

2361 len: bool = False, 

2362 idx: bool = False, 

2363 minmatchlen: Optional[int] = None, 

2364 withmatchlen: bool = False, 

2365 **kwargs, 

2366 ) -> ResponseT: 

2367 """ 

2368 Implements complex algorithms that operate on strings. 

2369 Right now the only algorithm implemented is the LCS algorithm 

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

2371 implemented in the future. 

2372 

2373 ``algo`` Right now must be LCS 

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

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

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

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

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

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

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

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

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

2383 

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

2385 """ 

2386 # check validity 

2387 supported_algo = ["LCS"] 

2388 if algo not in supported_algo: 

2389 supported_algos_str = ", ".join(supported_algo) 

2390 raise DataError(f"The supported algorithms are: {supported_algos_str}") 

2391 if specific_argument not in ["keys", "strings"]: 

2392 raise DataError("specific_argument can be only keys or strings") 

2393 if len and idx: 

2394 raise DataError("len and idx cannot be provided together.") 

2395 

2396 pieces: list[EncodableT] = [algo, specific_argument.upper(), value1, value2] 

2397 if len: 

2398 pieces.append(b"LEN") 

2399 if idx: 

2400 pieces.append(b"IDX") 

2401 try: 

2402 int(minmatchlen) 

2403 pieces.extend([b"MINMATCHLEN", minmatchlen]) 

2404 except TypeError: 

2405 pass 

2406 if withmatchlen: 

2407 pieces.append(b"WITHMATCHLEN") 

2408 

2409 return self.execute_command( 

2410 "STRALGO", 

2411 *pieces, 

2412 len=len, 

2413 idx=idx, 

2414 minmatchlen=minmatchlen, 

2415 withmatchlen=withmatchlen, 

2416 **kwargs, 

2417 ) 

2418 

2419 def strlen(self, name: KeyT) -> ResponseT: 

2420 """ 

2421 Return the number of bytes stored in the value of ``name`` 

2422 

2423 For more information see https://redis.io/commands/strlen 

2424 """ 

2425 return self.execute_command("STRLEN", name, keys=[name]) 

2426 

2427 def substr(self, name: KeyT, start: int, end: int = -1) -> ResponseT: 

2428 """ 

2429 Return a substring of the string at key ``name``. ``start`` and ``end`` 

2430 are 0-based integers specifying the portion of the string to return. 

2431 """ 

2432 return self.execute_command("SUBSTR", name, start, end, keys=[name]) 

2433 

2434 def touch(self, *args: KeyT) -> ResponseT: 

2435 """ 

2436 Alters the last access time of a key(s) ``*args``. A key is ignored 

2437 if it does not exist. 

2438 

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

2440 """ 

2441 return self.execute_command("TOUCH", *args) 

2442 

2443 def ttl(self, name: KeyT) -> ResponseT: 

2444 """ 

2445 Returns the number of seconds until the key ``name`` will expire 

2446 

2447 For more information see https://redis.io/commands/ttl 

2448 """ 

2449 return self.execute_command("TTL", name) 

2450 

2451 def type(self, name: KeyT) -> ResponseT: 

2452 """ 

2453 Returns the type of key ``name`` 

2454 

2455 For more information see https://redis.io/commands/type 

2456 """ 

2457 return self.execute_command("TYPE", name, keys=[name]) 

2458 

2459 def watch(self, *names: KeyT) -> None: 

2460 """ 

2461 Watches the values at keys ``names``, or None if the key doesn't exist 

2462 

2463 For more information see https://redis.io/commands/watch 

2464 """ 

2465 warnings.warn(DeprecationWarning("Call WATCH from a Pipeline object")) 

2466 

2467 def unwatch(self) -> None: 

2468 """ 

2469 Unwatches all previously watched keys for a transaction 

2470 

2471 For more information see https://redis.io/commands/unwatch 

2472 """ 

2473 warnings.warn(DeprecationWarning("Call UNWATCH from a Pipeline object")) 

2474 

2475 def unlink(self, *names: KeyT) -> ResponseT: 

2476 """ 

2477 Unlink one or more keys specified by ``names`` 

2478 

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

2480 """ 

2481 return self.execute_command("UNLINK", *names) 

2482 

2483 def lcs( 

2484 self, 

2485 key1: str, 

2486 key2: str, 

2487 len: Optional[bool] = False, 

2488 idx: Optional[bool] = False, 

2489 minmatchlen: Optional[int] = 0, 

2490 withmatchlen: Optional[bool] = False, 

2491 ) -> Union[str, int, list]: 

2492 """ 

2493 Find the longest common subsequence between ``key1`` and ``key2``. 

2494 If ``len`` is true the length of the match will will be returned. 

2495 If ``idx`` is true the match position in each strings will be returned. 

2496 ``minmatchlen`` restrict the list of matches to the ones of 

2497 the given ``minmatchlen``. 

2498 If ``withmatchlen`` the length of the match also will be returned. 

2499 For more information see https://redis.io/commands/lcs 

2500 """ 

2501 pieces = [key1, key2] 

2502 if len: 

2503 pieces.append("LEN") 

2504 if idx: 

2505 pieces.append("IDX") 

2506 if minmatchlen != 0: 

2507 pieces.extend(["MINMATCHLEN", minmatchlen]) 

2508 if withmatchlen: 

2509 pieces.append("WITHMATCHLEN") 

2510 return self.execute_command("LCS", *pieces, keys=[key1, key2]) 

2511 

2512 

2513class AsyncBasicKeyCommands(BasicKeyCommands): 

2514 def __delitem__(self, name: KeyT): 

2515 raise TypeError("Async Redis client does not support class deletion") 

2516 

2517 def __contains__(self, name: KeyT): 

2518 raise TypeError("Async Redis client does not support class inclusion") 

2519 

2520 def __getitem__(self, name: KeyT): 

2521 raise TypeError("Async Redis client does not support class retrieval") 

2522 

2523 def __setitem__(self, name: KeyT, value: EncodableT): 

2524 raise TypeError("Async Redis client does not support class assignment") 

2525 

2526 async def watch(self, *names: KeyT) -> None: 

2527 return super().watch(*names) 

2528 

2529 async def unwatch(self) -> None: 

2530 return super().unwatch() 

2531 

2532 

2533class ListCommands(CommandsProtocol): 

2534 """ 

2535 Redis commands for List data type. 

2536 see: https://redis.io/topics/data-types#lists 

2537 """ 

2538 

2539 def blpop( 

2540 self, keys: List, timeout: Optional[Number] = 0 

2541 ) -> Union[Awaitable[list], list]: 

2542 """ 

2543 LPOP a value off of the first non-empty list 

2544 named in the ``keys`` list. 

2545 

2546 If none of the lists in ``keys`` has a value to LPOP, then block 

2547 for ``timeout`` seconds, or until a value gets pushed on to one 

2548 of the lists. 

2549 

2550 If timeout is 0, then block indefinitely. 

2551 

2552 For more information see https://redis.io/commands/blpop 

2553 """ 

2554 if timeout is None: 

2555 timeout = 0 

2556 keys = list_or_args(keys, None) 

2557 keys.append(timeout) 

2558 return self.execute_command("BLPOP", *keys) 

2559 

2560 def brpop( 

2561 self, keys: List, timeout: Optional[Number] = 0 

2562 ) -> Union[Awaitable[list], list]: 

2563 """ 

2564 RPOP a value off of the first non-empty list 

2565 named in the ``keys`` list. 

2566 

2567 If none of the lists in ``keys`` has a value to RPOP, then block 

2568 for ``timeout`` seconds, or until a value gets pushed on to one 

2569 of the lists. 

2570 

2571 If timeout is 0, then block indefinitely. 

2572 

2573 For more information see https://redis.io/commands/brpop 

2574 """ 

2575 if timeout is None: 

2576 timeout = 0 

2577 keys = list_or_args(keys, None) 

2578 keys.append(timeout) 

2579 return self.execute_command("BRPOP", *keys) 

2580 

2581 def brpoplpush( 

2582 self, src: str, dst: str, timeout: Optional[Number] = 0 

2583 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

2584 """ 

2585 Pop a value off the tail of ``src``, push it on the head of ``dst`` 

2586 and then return it. 

2587 

2588 This command blocks until a value is in ``src`` or until ``timeout`` 

2589 seconds elapse, whichever is first. A ``timeout`` value of 0 blocks 

2590 forever. 

2591 

2592 For more information see https://redis.io/commands/brpoplpush 

2593 """ 

2594 if timeout is None: 

2595 timeout = 0 

2596 return self.execute_command("BRPOPLPUSH", src, dst, timeout) 

2597 

2598 def blmpop( 

2599 self, 

2600 timeout: float, 

2601 numkeys: int, 

2602 *args: List[str], 

2603 direction: str, 

2604 count: Optional[int] = 1, 

2605 ) -> Optional[list]: 

2606 """ 

2607 Pop ``count`` values (default 1) from first non-empty in the list 

2608 of provided key names. 

2609 

2610 When all lists are empty this command blocks the connection until another 

2611 client pushes to it or until the timeout, timeout of 0 blocks indefinitely 

2612 

2613 For more information see https://redis.io/commands/blmpop 

2614 """ 

2615 args = [timeout, numkeys, *args, direction, "COUNT", count] 

2616 

2617 return self.execute_command("BLMPOP", *args) 

2618 

2619 def lmpop( 

2620 self, 

2621 num_keys: int, 

2622 *args: List[str], 

2623 direction: str, 

2624 count: Optional[int] = 1, 

2625 ) -> Union[Awaitable[list], list]: 

2626 """ 

2627 Pop ``count`` values (default 1) first non-empty list key from the list 

2628 of args provided key names. 

2629 

2630 For more information see https://redis.io/commands/lmpop 

2631 """ 

2632 args = [num_keys] + list(args) + [direction] 

2633 if count != 1: 

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

2635 

2636 return self.execute_command("LMPOP", *args) 

2637 

2638 def lindex( 

2639 self, name: str, index: int 

2640 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

2641 """ 

2642 Return the item from list ``name`` at position ``index`` 

2643 

2644 Negative indexes are supported and will return an item at the 

2645 end of the list 

2646 

2647 For more information see https://redis.io/commands/lindex 

2648 """ 

2649 return self.execute_command("LINDEX", name, index, keys=[name]) 

2650 

2651 def linsert( 

2652 self, name: str, where: str, refvalue: str, value: str 

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

2654 """ 

2655 Insert ``value`` in list ``name`` either immediately before or after 

2656 [``where``] ``refvalue`` 

2657 

2658 Returns the new length of the list on success or -1 if ``refvalue`` 

2659 is not in the list. 

2660 

2661 For more information see https://redis.io/commands/linsert 

2662 """ 

2663 return self.execute_command("LINSERT", name, where, refvalue, value) 

2664 

2665 def llen(self, name: str) -> Union[Awaitable[int], int]: 

2666 """ 

2667 Return the length of the list ``name`` 

2668 

2669 For more information see https://redis.io/commands/llen 

2670 """ 

2671 return self.execute_command("LLEN", name, keys=[name]) 

2672 

2673 def lpop( 

2674 self, 

2675 name: str, 

2676 count: Optional[int] = None, 

2677 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]: 

2678 """ 

2679 Removes and returns the first elements of the list ``name``. 

2680 

2681 By default, the command pops a single element from the beginning of 

2682 the list. When provided with the optional ``count`` argument, the reply 

2683 will consist of up to count elements, depending on the list's length. 

2684 

2685 For more information see https://redis.io/commands/lpop 

2686 """ 

2687 if count is not None: 

2688 return self.execute_command("LPOP", name, count) 

2689 else: 

2690 return self.execute_command("LPOP", name) 

2691 

2692 def lpush(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2693 """ 

2694 Push ``values`` onto the head of the list ``name`` 

2695 

2696 For more information see https://redis.io/commands/lpush 

2697 """ 

2698 return self.execute_command("LPUSH", name, *values) 

2699 

2700 def lpushx(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2701 """ 

2702 Push ``value`` onto the head of the list ``name`` if ``name`` exists 

2703 

2704 For more information see https://redis.io/commands/lpushx 

2705 """ 

2706 return self.execute_command("LPUSHX", name, *values) 

2707 

2708 def lrange(self, name: str, start: int, end: int) -> Union[Awaitable[list], list]: 

2709 """ 

2710 Return a slice of the list ``name`` between 

2711 position ``start`` and ``end`` 

2712 

2713 ``start`` and ``end`` can be negative numbers just like 

2714 Python slicing notation 

2715 

2716 For more information see https://redis.io/commands/lrange 

2717 """ 

2718 return self.execute_command("LRANGE", name, start, end, keys=[name]) 

2719 

2720 def lrem(self, name: str, count: int, value: str) -> Union[Awaitable[int], int]: 

2721 """ 

2722 Remove the first ``count`` occurrences of elements equal to ``value`` 

2723 from the list stored at ``name``. 

2724 

2725 The count argument influences the operation in the following ways: 

2726 count > 0: Remove elements equal to value moving from head to tail. 

2727 count < 0: Remove elements equal to value moving from tail to head. 

2728 count = 0: Remove all elements equal to value. 

2729 

2730 For more information see https://redis.io/commands/lrem 

2731 """ 

2732 return self.execute_command("LREM", name, count, value) 

2733 

2734 def lset(self, name: str, index: int, value: str) -> Union[Awaitable[str], str]: 

2735 """ 

2736 Set element at ``index`` of list ``name`` to ``value`` 

2737 

2738 For more information see https://redis.io/commands/lset 

2739 """ 

2740 return self.execute_command("LSET", name, index, value) 

2741 

2742 def ltrim(self, name: str, start: int, end: int) -> Union[Awaitable[str], str]: 

2743 """ 

2744 Trim the list ``name``, removing all values not within the slice 

2745 between ``start`` and ``end`` 

2746 

2747 ``start`` and ``end`` can be negative numbers just like 

2748 Python slicing notation 

2749 

2750 For more information see https://redis.io/commands/ltrim 

2751 """ 

2752 return self.execute_command("LTRIM", name, start, end) 

2753 

2754 def rpop( 

2755 self, 

2756 name: str, 

2757 count: Optional[int] = None, 

2758 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]: 

2759 """ 

2760 Removes and returns the last elements of the list ``name``. 

2761 

2762 By default, the command pops a single element from the end of the list. 

2763 When provided with the optional ``count`` argument, the reply will 

2764 consist of up to count elements, depending on the list's length. 

2765 

2766 For more information see https://redis.io/commands/rpop 

2767 """ 

2768 if count is not None: 

2769 return self.execute_command("RPOP", name, count) 

2770 else: 

2771 return self.execute_command("RPOP", name) 

2772 

2773 def rpoplpush(self, src: str, dst: str) -> Union[Awaitable[str], str]: 

2774 """ 

2775 RPOP a value off of the ``src`` list and atomically LPUSH it 

2776 on to the ``dst`` list. Returns the value. 

2777 

2778 For more information see https://redis.io/commands/rpoplpush 

2779 """ 

2780 return self.execute_command("RPOPLPUSH", src, dst) 

2781 

2782 def rpush(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2783 """ 

2784 Push ``values`` onto the tail of the list ``name`` 

2785 

2786 For more information see https://redis.io/commands/rpush 

2787 """ 

2788 return self.execute_command("RPUSH", name, *values) 

2789 

2790 def rpushx(self, name: str, *values: str) -> Union[Awaitable[int], int]: 

2791 """ 

2792 Push ``value`` onto the tail of the list ``name`` if ``name`` exists 

2793 

2794 For more information see https://redis.io/commands/rpushx 

2795 """ 

2796 return self.execute_command("RPUSHX", name, *values) 

2797 

2798 def lpos( 

2799 self, 

2800 name: str, 

2801 value: str, 

2802 rank: Optional[int] = None, 

2803 count: Optional[int] = None, 

2804 maxlen: Optional[int] = None, 

2805 ) -> Union[str, List, None]: 

2806 """ 

2807 Get position of ``value`` within the list ``name`` 

2808 

2809 If specified, ``rank`` indicates the "rank" of the first element to 

2810 return in case there are multiple copies of ``value`` in the list. 

2811 By default, LPOS returns the position of the first occurrence of 

2812 ``value`` in the list. When ``rank`` 2, LPOS returns the position of 

2813 the second ``value`` in the list. If ``rank`` is negative, LPOS 

2814 searches the list in reverse. For example, -1 would return the 

2815 position of the last occurrence of ``value`` and -2 would return the 

2816 position of the next to last occurrence of ``value``. 

2817 

2818 If specified, ``count`` indicates that LPOS should return a list of 

2819 up to ``count`` positions. A ``count`` of 2 would return a list of 

2820 up to 2 positions. A ``count`` of 0 returns a list of all positions 

2821 matching ``value``. When ``count`` is specified and but ``value`` 

2822 does not exist in the list, an empty list is returned. 

2823 

2824 If specified, ``maxlen`` indicates the maximum number of list 

2825 elements to scan. A ``maxlen`` of 1000 will only return the 

2826 position(s) of items within the first 1000 entries in the list. 

2827 A ``maxlen`` of 0 (the default) will scan the entire list. 

2828 

2829 For more information see https://redis.io/commands/lpos 

2830 """ 

2831 pieces: list[EncodableT] = [name, value] 

2832 if rank is not None: 

2833 pieces.extend(["RANK", rank]) 

2834 

2835 if count is not None: 

2836 pieces.extend(["COUNT", count]) 

2837 

2838 if maxlen is not None: 

2839 pieces.extend(["MAXLEN", maxlen]) 

2840 

2841 return self.execute_command("LPOS", *pieces, keys=[name]) 

2842 

2843 def sort( 

2844 self, 

2845 name: str, 

2846 start: Optional[int] = None, 

2847 num: Optional[int] = None, 

2848 by: Optional[str] = None, 

2849 get: Optional[List[str]] = None, 

2850 desc: bool = False, 

2851 alpha: bool = False, 

2852 store: Optional[str] = None, 

2853 groups: Optional[bool] = False, 

2854 ) -> Union[List, int]: 

2855 """ 

2856 Sort and return the list, set or sorted set at ``name``. 

2857 

2858 ``start`` and ``num`` allow for paging through the sorted data 

2859 

2860 ``by`` allows using an external key to weight and sort the items. 

2861 Use an "*" to indicate where in the key the item value is located 

2862 

2863 ``get`` allows for returning items from external keys rather than the 

2864 sorted data itself. Use an "*" to indicate where in the key 

2865 the item value is located 

2866 

2867 ``desc`` allows for reversing the sort 

2868 

2869 ``alpha`` allows for sorting lexicographically rather than numerically 

2870 

2871 ``store`` allows for storing the result of the sort into 

2872 the key ``store`` 

2873 

2874 ``groups`` if set to True and if ``get`` contains at least two 

2875 elements, sort will return a list of tuples, each containing the 

2876 values fetched from the arguments to ``get``. 

2877 

2878 For more information see https://redis.io/commands/sort 

2879 """ 

2880 if (start is not None and num is None) or (num is not None and start is None): 

2881 raise DataError("``start`` and ``num`` must both be specified") 

2882 

2883 pieces: list[EncodableT] = [name] 

2884 if by is not None: 

2885 pieces.extend([b"BY", by]) 

2886 if start is not None and num is not None: 

2887 pieces.extend([b"LIMIT", start, num]) 

2888 if get is not None: 

2889 # If get is a string assume we want to get a single value. 

2890 # Otherwise assume it's an interable and we want to get multiple 

2891 # values. We can't just iterate blindly because strings are 

2892 # iterable. 

2893 if isinstance(get, (bytes, str)): 

2894 pieces.extend([b"GET", get]) 

2895 else: 

2896 for g in get: 

2897 pieces.extend([b"GET", g]) 

2898 if desc: 

2899 pieces.append(b"DESC") 

2900 if alpha: 

2901 pieces.append(b"ALPHA") 

2902 if store is not None: 

2903 pieces.extend([b"STORE", store]) 

2904 if groups: 

2905 if not get or isinstance(get, (bytes, str)) or len(get) < 2: 

2906 raise DataError( 

2907 'when using "groups" the "get" argument ' 

2908 "must be specified and contain at least " 

2909 "two keys" 

2910 ) 

2911 

2912 options = {"groups": len(get) if groups else None} 

2913 options["keys"] = [name] 

2914 return self.execute_command("SORT", *pieces, **options) 

2915 

2916 def sort_ro( 

2917 self, 

2918 key: str, 

2919 start: Optional[int] = None, 

2920 num: Optional[int] = None, 

2921 by: Optional[str] = None, 

2922 get: Optional[List[str]] = None, 

2923 desc: bool = False, 

2924 alpha: bool = False, 

2925 ) -> list: 

2926 """ 

2927 Returns the elements contained in the list, set or sorted set at key. 

2928 (read-only variant of the SORT command) 

2929 

2930 ``start`` and ``num`` allow for paging through the sorted data 

2931 

2932 ``by`` allows using an external key to weight and sort the items. 

2933 Use an "*" to indicate where in the key the item value is located 

2934 

2935 ``get`` allows for returning items from external keys rather than the 

2936 sorted data itself. Use an "*" to indicate where in the key 

2937 the item value is located 

2938 

2939 ``desc`` allows for reversing the sort 

2940 

2941 ``alpha`` allows for sorting lexicographically rather than numerically 

2942 

2943 For more information see https://redis.io/commands/sort_ro 

2944 """ 

2945 return self.sort( 

2946 key, start=start, num=num, by=by, get=get, desc=desc, alpha=alpha 

2947 ) 

2948 

2949 

2950AsyncListCommands = ListCommands 

2951 

2952 

2953class ScanCommands(CommandsProtocol): 

2954 """ 

2955 Redis SCAN commands. 

2956 see: https://redis.io/commands/scan 

2957 """ 

2958 

2959 def scan( 

2960 self, 

2961 cursor: int = 0, 

2962 match: Union[PatternT, None] = None, 

2963 count: Optional[int] = None, 

2964 _type: Optional[str] = None, 

2965 **kwargs, 

2966 ) -> ResponseT: 

2967 """ 

2968 Incrementally return lists of key names. Also return a cursor 

2969 indicating the scan position. 

2970 

2971 ``match`` allows for filtering the keys by pattern 

2972 

2973 ``count`` provides a hint to Redis about the number of keys to 

2974 return per batch. 

2975 

2976 ``_type`` filters the returned values by a particular Redis type. 

2977 Stock Redis instances allow for the following types: 

2978 HASH, LIST, SET, STREAM, STRING, ZSET 

2979 Additionally, Redis modules can expose other types as well. 

2980 

2981 For more information see https://redis.io/commands/scan 

2982 """ 

2983 pieces: list[EncodableT] = [cursor] 

2984 if match is not None: 

2985 pieces.extend([b"MATCH", match]) 

2986 if count is not None: 

2987 pieces.extend([b"COUNT", count]) 

2988 if _type is not None: 

2989 pieces.extend([b"TYPE", _type]) 

2990 return self.execute_command("SCAN", *pieces, **kwargs) 

2991 

2992 def scan_iter( 

2993 self, 

2994 match: Union[PatternT, None] = None, 

2995 count: Optional[int] = None, 

2996 _type: Optional[str] = None, 

2997 **kwargs, 

2998 ) -> Iterator: 

2999 """ 

3000 Make an iterator using the SCAN command so that the client doesn't 

3001 need to remember the cursor position. 

3002 

3003 ``match`` allows for filtering the keys by pattern 

3004 

3005 ``count`` provides a hint to Redis about the number of keys to 

3006 return per batch. 

3007 

3008 ``_type`` filters the returned values by a particular Redis type. 

3009 Stock Redis instances allow for the following types: 

3010 HASH, LIST, SET, STREAM, STRING, ZSET 

3011 Additionally, Redis modules can expose other types as well. 

3012 """ 

3013 cursor = "0" 

3014 while cursor != 0: 

3015 cursor, data = self.scan( 

3016 cursor=cursor, match=match, count=count, _type=_type, **kwargs 

3017 ) 

3018 yield from data 

3019 

3020 def sscan( 

3021 self, 

3022 name: KeyT, 

3023 cursor: int = 0, 

3024 match: Union[PatternT, None] = None, 

3025 count: Optional[int] = None, 

3026 ) -> ResponseT: 

3027 """ 

3028 Incrementally return lists of elements in a set. Also return a cursor 

3029 indicating the scan position. 

3030 

3031 ``match`` allows for filtering the keys by pattern 

3032 

3033 ``count`` allows for hint the minimum number of returns 

3034 

3035 For more information see https://redis.io/commands/sscan 

3036 """ 

3037 pieces: list[EncodableT] = [name, cursor] 

3038 if match is not None: 

3039 pieces.extend([b"MATCH", match]) 

3040 if count is not None: 

3041 pieces.extend([b"COUNT", count]) 

3042 return self.execute_command("SSCAN", *pieces) 

3043 

3044 def sscan_iter( 

3045 self, 

3046 name: KeyT, 

3047 match: Union[PatternT, None] = None, 

3048 count: Optional[int] = None, 

3049 ) -> Iterator: 

3050 """ 

3051 Make an iterator using the SSCAN command so that the client doesn't 

3052 need to remember the cursor position. 

3053 

3054 ``match`` allows for filtering the keys by pattern 

3055 

3056 ``count`` allows for hint the minimum number of returns 

3057 """ 

3058 cursor = "0" 

3059 while cursor != 0: 

3060 cursor, data = self.sscan(name, cursor=cursor, match=match, count=count) 

3061 yield from data 

3062 

3063 def hscan( 

3064 self, 

3065 name: KeyT, 

3066 cursor: int = 0, 

3067 match: Union[PatternT, None] = None, 

3068 count: Optional[int] = None, 

3069 no_values: Union[bool, None] = None, 

3070 ) -> ResponseT: 

3071 """ 

3072 Incrementally return key/value slices in a hash. Also return a cursor 

3073 indicating the scan position. 

3074 

3075 ``match`` allows for filtering the keys by pattern 

3076 

3077 ``count`` allows for hint the minimum number of returns 

3078 

3079 ``no_values`` indicates to return only the keys, without values. 

3080 

3081 For more information see https://redis.io/commands/hscan 

3082 """ 

3083 pieces: list[EncodableT] = [name, cursor] 

3084 if match is not None: 

3085 pieces.extend([b"MATCH", match]) 

3086 if count is not None: 

3087 pieces.extend([b"COUNT", count]) 

3088 if no_values is not None: 

3089 pieces.extend([b"NOVALUES"]) 

3090 return self.execute_command("HSCAN", *pieces, no_values=no_values) 

3091 

3092 def hscan_iter( 

3093 self, 

3094 name: str, 

3095 match: Union[PatternT, None] = None, 

3096 count: Optional[int] = None, 

3097 no_values: Union[bool, None] = None, 

3098 ) -> Iterator: 

3099 """ 

3100 Make an iterator using the HSCAN command so that the client doesn't 

3101 need to remember the cursor position. 

3102 

3103 ``match`` allows for filtering the keys by pattern 

3104 

3105 ``count`` allows for hint the minimum number of returns 

3106 

3107 ``no_values`` indicates to return only the keys, without values 

3108 """ 

3109 cursor = "0" 

3110 while cursor != 0: 

3111 cursor, data = self.hscan( 

3112 name, cursor=cursor, match=match, count=count, no_values=no_values 

3113 ) 

3114 if no_values: 

3115 yield from data 

3116 else: 

3117 yield from data.items() 

3118 

3119 def zscan( 

3120 self, 

3121 name: KeyT, 

3122 cursor: int = 0, 

3123 match: Union[PatternT, None] = None, 

3124 count: Optional[int] = None, 

3125 score_cast_func: Union[type, Callable] = float, 

3126 ) -> ResponseT: 

3127 """ 

3128 Incrementally return lists of elements in a sorted set. Also return a 

3129 cursor indicating the scan position. 

3130 

3131 ``match`` allows for filtering the keys by pattern 

3132 

3133 ``count`` allows for hint the minimum number of returns 

3134 

3135 ``score_cast_func`` a callable used to cast the score return value 

3136 

3137 For more information see https://redis.io/commands/zscan 

3138 """ 

3139 pieces = [name, cursor] 

3140 if match is not None: 

3141 pieces.extend([b"MATCH", match]) 

3142 if count is not None: 

3143 pieces.extend([b"COUNT", count]) 

3144 options = {"score_cast_func": score_cast_func} 

3145 return self.execute_command("ZSCAN", *pieces, **options) 

3146 

3147 def zscan_iter( 

3148 self, 

3149 name: KeyT, 

3150 match: Union[PatternT, None] = None, 

3151 count: Optional[int] = None, 

3152 score_cast_func: Union[type, Callable] = float, 

3153 ) -> Iterator: 

3154 """ 

3155 Make an iterator using the ZSCAN command so that the client doesn't 

3156 need to remember the cursor position. 

3157 

3158 ``match`` allows for filtering the keys by pattern 

3159 

3160 ``count`` allows for hint the minimum number of returns 

3161 

3162 ``score_cast_func`` a callable used to cast the score return value 

3163 """ 

3164 cursor = "0" 

3165 while cursor != 0: 

3166 cursor, data = self.zscan( 

3167 name, 

3168 cursor=cursor, 

3169 match=match, 

3170 count=count, 

3171 score_cast_func=score_cast_func, 

3172 ) 

3173 yield from data 

3174 

3175 

3176class AsyncScanCommands(ScanCommands): 

3177 async def scan_iter( 

3178 self, 

3179 match: Union[PatternT, None] = None, 

3180 count: Optional[int] = None, 

3181 _type: Optional[str] = None, 

3182 **kwargs, 

3183 ) -> AsyncIterator: 

3184 """ 

3185 Make an iterator using the SCAN command so that the client doesn't 

3186 need to remember the cursor position. 

3187 

3188 ``match`` allows for filtering the keys by pattern 

3189 

3190 ``count`` provides a hint to Redis about the number of keys to 

3191 return per batch. 

3192 

3193 ``_type`` filters the returned values by a particular Redis type. 

3194 Stock Redis instances allow for the following types: 

3195 HASH, LIST, SET, STREAM, STRING, ZSET 

3196 Additionally, Redis modules can expose other types as well. 

3197 """ 

3198 cursor = "0" 

3199 while cursor != 0: 

3200 cursor, data = await self.scan( 

3201 cursor=cursor, match=match, count=count, _type=_type, **kwargs 

3202 ) 

3203 for d in data: 

3204 yield d 

3205 

3206 async def sscan_iter( 

3207 self, 

3208 name: KeyT, 

3209 match: Union[PatternT, None] = None, 

3210 count: Optional[int] = None, 

3211 ) -> AsyncIterator: 

3212 """ 

3213 Make an iterator using the SSCAN command so that the client doesn't 

3214 need to remember the cursor position. 

3215 

3216 ``match`` allows for filtering the keys by pattern 

3217 

3218 ``count`` allows for hint the minimum number of returns 

3219 """ 

3220 cursor = "0" 

3221 while cursor != 0: 

3222 cursor, data = await self.sscan( 

3223 name, cursor=cursor, match=match, count=count 

3224 ) 

3225 for d in data: 

3226 yield d 

3227 

3228 async def hscan_iter( 

3229 self, 

3230 name: str, 

3231 match: Union[PatternT, None] = None, 

3232 count: Optional[int] = None, 

3233 no_values: Union[bool, None] = None, 

3234 ) -> AsyncIterator: 

3235 """ 

3236 Make an iterator using the HSCAN command so that the client doesn't 

3237 need to remember the cursor position. 

3238 

3239 ``match`` allows for filtering the keys by pattern 

3240 

3241 ``count`` allows for hint the minimum number of returns 

3242 

3243 ``no_values`` indicates to return only the keys, without values 

3244 """ 

3245 cursor = "0" 

3246 while cursor != 0: 

3247 cursor, data = await self.hscan( 

3248 name, cursor=cursor, match=match, count=count, no_values=no_values 

3249 ) 

3250 if no_values: 

3251 for it in data: 

3252 yield it 

3253 else: 

3254 for it in data.items(): 

3255 yield it 

3256 

3257 async def zscan_iter( 

3258 self, 

3259 name: KeyT, 

3260 match: Union[PatternT, None] = None, 

3261 count: Optional[int] = None, 

3262 score_cast_func: Union[type, Callable] = float, 

3263 ) -> AsyncIterator: 

3264 """ 

3265 Make an iterator using the ZSCAN command so that the client doesn't 

3266 need to remember the cursor position. 

3267 

3268 ``match`` allows for filtering the keys by pattern 

3269 

3270 ``count`` allows for hint the minimum number of returns 

3271 

3272 ``score_cast_func`` a callable used to cast the score return value 

3273 """ 

3274 cursor = "0" 

3275 while cursor != 0: 

3276 cursor, data = await self.zscan( 

3277 name, 

3278 cursor=cursor, 

3279 match=match, 

3280 count=count, 

3281 score_cast_func=score_cast_func, 

3282 ) 

3283 for d in data: 

3284 yield d 

3285 

3286 

3287class SetCommands(CommandsProtocol): 

3288 """ 

3289 Redis commands for Set data type. 

3290 see: https://redis.io/topics/data-types#sets 

3291 """ 

3292 

3293 def sadd(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]: 

3294 """ 

3295 Add ``value(s)`` to set ``name`` 

3296 

3297 For more information see https://redis.io/commands/sadd 

3298 """ 

3299 return self.execute_command("SADD", name, *values) 

3300 

3301 def scard(self, name: KeyT) -> Union[Awaitable[int], int]: 

3302 """ 

3303 Return the number of elements in set ``name`` 

3304 

3305 For more information see https://redis.io/commands/scard 

3306 """ 

3307 return self.execute_command("SCARD", name, keys=[name]) 

3308 

3309 def sdiff(self, keys: List, *args: List) -> Union[Awaitable[list], list]: 

3310 """ 

3311 Return the difference of sets specified by ``keys`` 

3312 

3313 For more information see https://redis.io/commands/sdiff 

3314 """ 

3315 args = list_or_args(keys, args) 

3316 return self.execute_command("SDIFF", *args, keys=args) 

3317 

3318 def sdiffstore( 

3319 self, dest: str, keys: List, *args: List 

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

3321 """ 

3322 Store the difference of sets specified by ``keys`` into a new 

3323 set named ``dest``. Returns the number of keys in the new set. 

3324 

3325 For more information see https://redis.io/commands/sdiffstore 

3326 """ 

3327 args = list_or_args(keys, args) 

3328 return self.execute_command("SDIFFSTORE", dest, *args) 

3329 

3330 def sinter(self, keys: List, *args: List) -> Union[Awaitable[list], list]: 

3331 """ 

3332 Return the intersection of sets specified by ``keys`` 

3333 

3334 For more information see https://redis.io/commands/sinter 

3335 """ 

3336 args = list_or_args(keys, args) 

3337 return self.execute_command("SINTER", *args, keys=args) 

3338 

3339 def sintercard( 

3340 self, numkeys: int, keys: List[KeyT], limit: int = 0 

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

3342 """ 

3343 Return the cardinality of the intersect of multiple sets specified by ``keys``. 

3344 

3345 When LIMIT provided (defaults to 0 and means unlimited), if the intersection 

3346 cardinality reaches limit partway through the computation, the algorithm will 

3347 exit and yield limit as the cardinality 

3348 

3349 For more information see https://redis.io/commands/sintercard 

3350 """ 

3351 args = [numkeys, *keys, "LIMIT", limit] 

3352 return self.execute_command("SINTERCARD", *args, keys=keys) 

3353 

3354 def sinterstore( 

3355 self, dest: KeyT, keys: List, *args: List 

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

3357 """ 

3358 Store the intersection of sets specified by ``keys`` into a new 

3359 set named ``dest``. Returns the number of keys in the new set. 

3360 

3361 For more information see https://redis.io/commands/sinterstore 

3362 """ 

3363 args = list_or_args(keys, args) 

3364 return self.execute_command("SINTERSTORE", dest, *args) 

3365 

3366 def sismember( 

3367 self, name: KeyT, value: str 

3368 ) -> Union[Awaitable[Union[Literal[0], Literal[1]]], Union[Literal[0], Literal[1]]]: 

3369 """ 

3370 Return whether ``value`` is a member of set ``name``: 

3371 - 1 if the value is a member of the set. 

3372 - 0 if the value is not a member of the set or if key does not exist. 

3373 

3374 For more information see https://redis.io/commands/sismember 

3375 """ 

3376 return self.execute_command("SISMEMBER", name, value, keys=[name]) 

3377 

3378 def smembers(self, name: KeyT) -> Union[Awaitable[Set], Set]: 

3379 """ 

3380 Return all members of the set ``name`` 

3381 

3382 For more information see https://redis.io/commands/smembers 

3383 """ 

3384 return self.execute_command("SMEMBERS", name, keys=[name]) 

3385 

3386 def smismember( 

3387 self, name: KeyT, values: List, *args: List 

3388 ) -> Union[ 

3389 Awaitable[List[Union[Literal[0], Literal[1]]]], 

3390 List[Union[Literal[0], Literal[1]]], 

3391 ]: 

3392 """ 

3393 Return whether each value in ``values`` is a member of the set ``name`` 

3394 as a list of ``int`` in the order of ``values``: 

3395 - 1 if the value is a member of the set. 

3396 - 0 if the value is not a member of the set or if key does not exist. 

3397 

3398 For more information see https://redis.io/commands/smismember 

3399 """ 

3400 args = list_or_args(values, args) 

3401 return self.execute_command("SMISMEMBER", name, *args, keys=[name]) 

3402 

3403 def smove(self, src: KeyT, dst: KeyT, value: str) -> Union[Awaitable[bool], bool]: 

3404 """ 

3405 Move ``value`` from set ``src`` to set ``dst`` atomically 

3406 

3407 For more information see https://redis.io/commands/smove 

3408 """ 

3409 return self.execute_command("SMOVE", src, dst, value) 

3410 

3411 def spop(self, name: KeyT, count: Optional[int] = None) -> Union[str, List, None]: 

3412 """ 

3413 Remove and return a random member of set ``name`` 

3414 

3415 For more information see https://redis.io/commands/spop 

3416 """ 

3417 args = (count is not None) and [count] or [] 

3418 return self.execute_command("SPOP", name, *args) 

3419 

3420 def srandmember( 

3421 self, name: KeyT, number: Optional[int] = None 

3422 ) -> Union[str, List, None]: 

3423 """ 

3424 If ``number`` is None, returns a random member of set ``name``. 

3425 

3426 If ``number`` is supplied, returns a list of ``number`` random 

3427 members of set ``name``. Note this is only available when running 

3428 Redis 2.6+. 

3429 

3430 For more information see https://redis.io/commands/srandmember 

3431 """ 

3432 args = (number is not None) and [number] or [] 

3433 return self.execute_command("SRANDMEMBER", name, *args) 

3434 

3435 def srem(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]: 

3436 """ 

3437 Remove ``values`` from set ``name`` 

3438 

3439 For more information see https://redis.io/commands/srem 

3440 """ 

3441 return self.execute_command("SREM", name, *values) 

3442 

3443 def sunion(self, keys: List, *args: List) -> Union[Awaitable[List], List]: 

3444 """ 

3445 Return the union of sets specified by ``keys`` 

3446 

3447 For more information see https://redis.io/commands/sunion 

3448 """ 

3449 args = list_or_args(keys, args) 

3450 return self.execute_command("SUNION", *args, keys=args) 

3451 

3452 def sunionstore( 

3453 self, dest: KeyT, keys: List, *args: List 

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

3455 """ 

3456 Store the union of sets specified by ``keys`` into a new 

3457 set named ``dest``. Returns the number of keys in the new set. 

3458 

3459 For more information see https://redis.io/commands/sunionstore 

3460 """ 

3461 args = list_or_args(keys, args) 

3462 return self.execute_command("SUNIONSTORE", dest, *args) 

3463 

3464 

3465AsyncSetCommands = SetCommands 

3466 

3467 

3468class StreamCommands(CommandsProtocol): 

3469 """ 

3470 Redis commands for Stream data type. 

3471 see: https://redis.io/topics/streams-intro 

3472 """ 

3473 

3474 def xack(self, name: KeyT, groupname: GroupT, *ids: StreamIdT) -> ResponseT: 

3475 """ 

3476 Acknowledges the successful processing of one or more messages. 

3477 

3478 Args: 

3479 name: name of the stream. 

3480 groupname: name of the consumer group. 

3481 *ids: message ids to acknowledge. 

3482 

3483 For more information see https://redis.io/commands/xack 

3484 """ 

3485 return self.execute_command("XACK", name, groupname, *ids) 

3486 

3487 def xadd( 

3488 self, 

3489 name: KeyT, 

3490 fields: Dict[FieldT, EncodableT], 

3491 id: StreamIdT = "*", 

3492 maxlen: Optional[int] = None, 

3493 approximate: bool = True, 

3494 nomkstream: bool = False, 

3495 minid: Union[StreamIdT, None] = None, 

3496 limit: Optional[int] = None, 

3497 ) -> ResponseT: 

3498 """ 

3499 Add to a stream. 

3500 name: name of the stream 

3501 fields: dict of field/value pairs to insert into the stream 

3502 id: Location to insert this record. By default it is appended. 

3503 maxlen: truncate old stream members beyond this size. 

3504 Can't be specified with minid. 

3505 approximate: actual stream length may be slightly more than maxlen 

3506 nomkstream: When set to true, do not make a stream 

3507 minid: the minimum id in the stream to query. 

3508 Can't be specified with maxlen. 

3509 limit: specifies the maximum number of entries to retrieve 

3510 

3511 For more information see https://redis.io/commands/xadd 

3512 """ 

3513 pieces: list[EncodableT] = [] 

3514 if maxlen is not None and minid is not None: 

3515 raise DataError("Only one of ```maxlen``` or ```minid``` may be specified") 

3516 

3517 if maxlen is not None: 

3518 if not isinstance(maxlen, int) or maxlen < 0: 

3519 raise DataError("XADD maxlen must be non-negative integer") 

3520 pieces.append(b"MAXLEN") 

3521 if approximate: 

3522 pieces.append(b"~") 

3523 pieces.append(str(maxlen)) 

3524 if minid is not None: 

3525 pieces.append(b"MINID") 

3526 if approximate: 

3527 pieces.append(b"~") 

3528 pieces.append(minid) 

3529 if limit is not None: 

3530 pieces.extend([b"LIMIT", limit]) 

3531 if nomkstream: 

3532 pieces.append(b"NOMKSTREAM") 

3533 pieces.append(id) 

3534 if not isinstance(fields, dict) or len(fields) == 0: 

3535 raise DataError("XADD fields must be a non-empty dict") 

3536 for pair in fields.items(): 

3537 pieces.extend(pair) 

3538 return self.execute_command("XADD", name, *pieces) 

3539 

3540 def xautoclaim( 

3541 self, 

3542 name: KeyT, 

3543 groupname: GroupT, 

3544 consumername: ConsumerT, 

3545 min_idle_time: int, 

3546 start_id: StreamIdT = "0-0", 

3547 count: Optional[int] = None, 

3548 justid: bool = False, 

3549 ) -> ResponseT: 

3550 """ 

3551 Transfers ownership of pending stream entries that match the specified 

3552 criteria. Conceptually, equivalent to calling XPENDING and then XCLAIM, 

3553 but provides a more straightforward way to deal with message delivery 

3554 failures via SCAN-like semantics. 

3555 name: name of the stream. 

3556 groupname: name of the consumer group. 

3557 consumername: name of a consumer that claims the message. 

3558 min_idle_time: filter messages that were idle less than this amount of 

3559 milliseconds. 

3560 start_id: filter messages with equal or greater ID. 

3561 count: optional integer, upper limit of the number of entries that the 

3562 command attempts to claim. Set to 100 by default. 

3563 justid: optional boolean, false by default. Return just an array of IDs 

3564 of messages successfully claimed, without returning the actual message 

3565 

3566 For more information see https://redis.io/commands/xautoclaim 

3567 """ 

3568 try: 

3569 if int(min_idle_time) < 0: 

3570 raise DataError( 

3571 "XAUTOCLAIM min_idle_time must be a nonnegative integer" 

3572 ) 

3573 except TypeError: 

3574 pass 

3575 

3576 kwargs = {} 

3577 pieces = [name, groupname, consumername, min_idle_time, start_id] 

3578 

3579 try: 

3580 if int(count) < 0: 

3581 raise DataError("XPENDING count must be a integer >= 0") 

3582 pieces.extend([b"COUNT", count]) 

3583 except TypeError: 

3584 pass 

3585 if justid: 

3586 pieces.append(b"JUSTID") 

3587 kwargs["parse_justid"] = True 

3588 

3589 return self.execute_command("XAUTOCLAIM", *pieces, **kwargs) 

3590 

3591 def xclaim( 

3592 self, 

3593 name: KeyT, 

3594 groupname: GroupT, 

3595 consumername: ConsumerT, 

3596 min_idle_time: int, 

3597 message_ids: Union[List[StreamIdT], Tuple[StreamIdT]], 

3598 idle: Optional[int] = None, 

3599 time: Optional[int] = None, 

3600 retrycount: Optional[int] = None, 

3601 force: bool = False, 

3602 justid: bool = False, 

3603 ) -> ResponseT: 

3604 """ 

3605 Changes the ownership of a pending message. 

3606 

3607 name: name of the stream. 

3608 

3609 groupname: name of the consumer group. 

3610 

3611 consumername: name of a consumer that claims the message. 

3612 

3613 min_idle_time: filter messages that were idle less than this amount of 

3614 milliseconds 

3615 

3616 message_ids: non-empty list or tuple of message IDs to claim 

3617 

3618 idle: optional. Set the idle time (last time it was delivered) of the 

3619 message in ms 

3620 

3621 time: optional integer. This is the same as idle but instead of a 

3622 relative amount of milliseconds, it sets the idle time to a specific 

3623 Unix time (in milliseconds). 

3624 

3625 retrycount: optional integer. set the retry counter to the specified 

3626 value. This counter is incremented every time a message is delivered 

3627 again. 

3628 

3629 force: optional boolean, false by default. Creates the pending message 

3630 entry in the PEL even if certain specified IDs are not already in the 

3631 PEL assigned to a different client. 

3632 

3633 justid: optional boolean, false by default. Return just an array of IDs 

3634 of messages successfully claimed, without returning the actual message 

3635 

3636 For more information see https://redis.io/commands/xclaim 

3637 """ 

3638 if not isinstance(min_idle_time, int) or min_idle_time < 0: 

3639 raise DataError("XCLAIM min_idle_time must be a non negative integer") 

3640 if not isinstance(message_ids, (list, tuple)) or not message_ids: 

3641 raise DataError( 

3642 "XCLAIM message_ids must be a non empty list or " 

3643 "tuple of message IDs to claim" 

3644 ) 

3645 

3646 kwargs = {} 

3647 pieces: list[EncodableT] = [name, groupname, consumername, str(min_idle_time)] 

3648 pieces.extend(list(message_ids)) 

3649 

3650 if idle is not None: 

3651 if not isinstance(idle, int): 

3652 raise DataError("XCLAIM idle must be an integer") 

3653 pieces.extend((b"IDLE", str(idle))) 

3654 if time is not None: 

3655 if not isinstance(time, int): 

3656 raise DataError("XCLAIM time must be an integer") 

3657 pieces.extend((b"TIME", str(time))) 

3658 if retrycount is not None: 

3659 if not isinstance(retrycount, int): 

3660 raise DataError("XCLAIM retrycount must be an integer") 

3661 pieces.extend((b"RETRYCOUNT", str(retrycount))) 

3662 

3663 if force: 

3664 if not isinstance(force, bool): 

3665 raise DataError("XCLAIM force must be a boolean") 

3666 pieces.append(b"FORCE") 

3667 if justid: 

3668 if not isinstance(justid, bool): 

3669 raise DataError("XCLAIM justid must be a boolean") 

3670 pieces.append(b"JUSTID") 

3671 kwargs["parse_justid"] = True 

3672 return self.execute_command("XCLAIM", *pieces, **kwargs) 

3673 

3674 def xdel(self, name: KeyT, *ids: StreamIdT) -> ResponseT: 

3675 """ 

3676 Deletes one or more messages from a stream. 

3677 

3678 Args: 

3679 name: name of the stream. 

3680 *ids: message ids to delete. 

3681 

3682 For more information see https://redis.io/commands/xdel 

3683 """ 

3684 return self.execute_command("XDEL", name, *ids) 

3685 

3686 def xgroup_create( 

3687 self, 

3688 name: KeyT, 

3689 groupname: GroupT, 

3690 id: StreamIdT = "$", 

3691 mkstream: bool = False, 

3692 entries_read: Optional[int] = None, 

3693 ) -> ResponseT: 

3694 """ 

3695 Create a new consumer group associated with a stream. 

3696 name: name of the stream. 

3697 groupname: name of the consumer group. 

3698 id: ID of the last item in the stream to consider already delivered. 

3699 

3700 For more information see https://redis.io/commands/xgroup-create 

3701 """ 

3702 pieces: list[EncodableT] = ["XGROUP CREATE", name, groupname, id] 

3703 if mkstream: 

3704 pieces.append(b"MKSTREAM") 

3705 if entries_read is not None: 

3706 pieces.extend(["ENTRIESREAD", entries_read]) 

3707 

3708 return self.execute_command(*pieces) 

3709 

3710 def xgroup_delconsumer( 

3711 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

3712 ) -> ResponseT: 

3713 """ 

3714 Remove a specific consumer from a consumer group. 

3715 Returns the number of pending messages that the consumer had before it 

3716 was deleted. 

3717 name: name of the stream. 

3718 groupname: name of the consumer group. 

3719 consumername: name of consumer to delete 

3720 

3721 For more information see https://redis.io/commands/xgroup-delconsumer 

3722 """ 

3723 return self.execute_command("XGROUP DELCONSUMER", name, groupname, consumername) 

3724 

3725 def xgroup_destroy(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3726 """ 

3727 Destroy a consumer group. 

3728 name: name of the stream. 

3729 groupname: name of the consumer group. 

3730 

3731 For more information see https://redis.io/commands/xgroup-destroy 

3732 """ 

3733 return self.execute_command("XGROUP DESTROY", name, groupname) 

3734 

3735 def xgroup_createconsumer( 

3736 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

3737 ) -> ResponseT: 

3738 """ 

3739 Consumers in a consumer group are auto-created every time a new 

3740 consumer name is mentioned by some command. 

3741 They can be explicitly created by using this command. 

3742 name: name of the stream. 

3743 groupname: name of the consumer group. 

3744 consumername: name of consumer to create. 

3745 

3746 See: https://redis.io/commands/xgroup-createconsumer 

3747 """ 

3748 return self.execute_command( 

3749 "XGROUP CREATECONSUMER", name, groupname, consumername 

3750 ) 

3751 

3752 def xgroup_setid( 

3753 self, 

3754 name: KeyT, 

3755 groupname: GroupT, 

3756 id: StreamIdT, 

3757 entries_read: Optional[int] = None, 

3758 ) -> ResponseT: 

3759 """ 

3760 Set the consumer group last delivered ID to something else. 

3761 name: name of the stream. 

3762 groupname: name of the consumer group. 

3763 id: ID of the last item in the stream to consider already delivered. 

3764 

3765 For more information see https://redis.io/commands/xgroup-setid 

3766 """ 

3767 pieces = [name, groupname, id] 

3768 if entries_read is not None: 

3769 pieces.extend(["ENTRIESREAD", entries_read]) 

3770 return self.execute_command("XGROUP SETID", *pieces) 

3771 

3772 def xinfo_consumers(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3773 """ 

3774 Returns general information about the consumers in the group. 

3775 name: name of the stream. 

3776 groupname: name of the consumer group. 

3777 

3778 For more information see https://redis.io/commands/xinfo-consumers 

3779 """ 

3780 return self.execute_command("XINFO CONSUMERS", name, groupname) 

3781 

3782 def xinfo_groups(self, name: KeyT) -> ResponseT: 

3783 """ 

3784 Returns general information about the consumer groups of the stream. 

3785 name: name of the stream. 

3786 

3787 For more information see https://redis.io/commands/xinfo-groups 

3788 """ 

3789 return self.execute_command("XINFO GROUPS", name) 

3790 

3791 def xinfo_stream(self, name: KeyT, full: bool = False) -> ResponseT: 

3792 """ 

3793 Returns general information about the stream. 

3794 name: name of the stream. 

3795 full: optional boolean, false by default. Return full summary 

3796 

3797 For more information see https://redis.io/commands/xinfo-stream 

3798 """ 

3799 pieces = [name] 

3800 options = {} 

3801 if full: 

3802 pieces.append(b"FULL") 

3803 options = {"full": full} 

3804 return self.execute_command("XINFO STREAM", *pieces, **options) 

3805 

3806 def xlen(self, name: KeyT) -> ResponseT: 

3807 """ 

3808 Returns the number of elements in a given stream. 

3809 

3810 For more information see https://redis.io/commands/xlen 

3811 """ 

3812 return self.execute_command("XLEN", name, keys=[name]) 

3813 

3814 def xpending(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3815 """ 

3816 Returns information about pending messages of a group. 

3817 name: name of the stream. 

3818 groupname: name of the consumer group. 

3819 

3820 For more information see https://redis.io/commands/xpending 

3821 """ 

3822 return self.execute_command("XPENDING", name, groupname, keys=[name]) 

3823 

3824 def xpending_range( 

3825 self, 

3826 name: KeyT, 

3827 groupname: GroupT, 

3828 min: StreamIdT, 

3829 max: StreamIdT, 

3830 count: int, 

3831 consumername: Union[ConsumerT, None] = None, 

3832 idle: Optional[int] = None, 

3833 ) -> ResponseT: 

3834 """ 

3835 Returns information about pending messages, in a range. 

3836 

3837 name: name of the stream. 

3838 groupname: name of the consumer group. 

3839 idle: available from version 6.2. filter entries by their 

3840 idle-time, given in milliseconds (optional). 

3841 min: minimum stream ID. 

3842 max: maximum stream ID. 

3843 count: number of messages to return 

3844 consumername: name of a consumer to filter by (optional). 

3845 """ 

3846 if {min, max, count} == {None}: 

3847 if idle is not None or consumername is not None: 

3848 raise DataError( 

3849 "if XPENDING is provided with idle time" 

3850 " or consumername, it must be provided" 

3851 " with min, max and count parameters" 

3852 ) 

3853 return self.xpending(name, groupname) 

3854 

3855 pieces = [name, groupname] 

3856 if min is None or max is None or count is None: 

3857 raise DataError( 

3858 "XPENDING must be provided with min, max " 

3859 "and count parameters, or none of them." 

3860 ) 

3861 # idle 

3862 try: 

3863 if int(idle) < 0: 

3864 raise DataError("XPENDING idle must be a integer >= 0") 

3865 pieces.extend(["IDLE", idle]) 

3866 except TypeError: 

3867 pass 

3868 # count 

3869 try: 

3870 if int(count) < 0: 

3871 raise DataError("XPENDING count must be a integer >= 0") 

3872 pieces.extend([min, max, count]) 

3873 except TypeError: 

3874 pass 

3875 # consumername 

3876 if consumername: 

3877 pieces.append(consumername) 

3878 

3879 return self.execute_command("XPENDING", *pieces, parse_detail=True) 

3880 

3881 def xrange( 

3882 self, 

3883 name: KeyT, 

3884 min: StreamIdT = "-", 

3885 max: StreamIdT = "+", 

3886 count: Optional[int] = None, 

3887 ) -> ResponseT: 

3888 """ 

3889 Read stream values within an interval. 

3890 

3891 name: name of the stream. 

3892 

3893 start: first stream ID. defaults to '-', 

3894 meaning the earliest available. 

3895 

3896 finish: last stream ID. defaults to '+', 

3897 meaning the latest available. 

3898 

3899 count: if set, only return this many items, beginning with the 

3900 earliest available. 

3901 

3902 For more information see https://redis.io/commands/xrange 

3903 """ 

3904 pieces = [min, max] 

3905 if count is not None: 

3906 if not isinstance(count, int) or count < 1: 

3907 raise DataError("XRANGE count must be a positive integer") 

3908 pieces.append(b"COUNT") 

3909 pieces.append(str(count)) 

3910 

3911 return self.execute_command("XRANGE", name, *pieces, keys=[name]) 

3912 

3913 def xread( 

3914 self, 

3915 streams: Dict[KeyT, StreamIdT], 

3916 count: Optional[int] = None, 

3917 block: Optional[int] = None, 

3918 ) -> ResponseT: 

3919 """ 

3920 Block and monitor multiple streams for new data. 

3921 

3922 streams: a dict of stream names to stream IDs, where 

3923 IDs indicate the last ID already seen. 

3924 

3925 count: if set, only return this many items, beginning with the 

3926 earliest available. 

3927 

3928 block: number of milliseconds to wait, if nothing already present. 

3929 

3930 For more information see https://redis.io/commands/xread 

3931 """ 

3932 pieces = [] 

3933 if block is not None: 

3934 if not isinstance(block, int) or block < 0: 

3935 raise DataError("XREAD block must be a non-negative integer") 

3936 pieces.append(b"BLOCK") 

3937 pieces.append(str(block)) 

3938 if count is not None: 

3939 if not isinstance(count, int) or count < 1: 

3940 raise DataError("XREAD count must be a positive integer") 

3941 pieces.append(b"COUNT") 

3942 pieces.append(str(count)) 

3943 if not isinstance(streams, dict) or len(streams) == 0: 

3944 raise DataError("XREAD streams must be a non empty dict") 

3945 pieces.append(b"STREAMS") 

3946 keys, values = zip(*streams.items()) 

3947 pieces.extend(keys) 

3948 pieces.extend(values) 

3949 return self.execute_command("XREAD", *pieces, keys=keys) 

3950 

3951 def xreadgroup( 

3952 self, 

3953 groupname: str, 

3954 consumername: str, 

3955 streams: Dict[KeyT, StreamIdT], 

3956 count: Optional[int] = None, 

3957 block: Optional[int] = None, 

3958 noack: bool = False, 

3959 ) -> ResponseT: 

3960 """ 

3961 Read from a stream via a consumer group. 

3962 

3963 groupname: name of the consumer group. 

3964 

3965 consumername: name of the requesting consumer. 

3966 

3967 streams: a dict of stream names to stream IDs, where 

3968 IDs indicate the last ID already seen. 

3969 

3970 count: if set, only return this many items, beginning with the 

3971 earliest available. 

3972 

3973 block: number of milliseconds to wait, if nothing already present. 

3974 noack: do not add messages to the PEL 

3975 

3976 For more information see https://redis.io/commands/xreadgroup 

3977 """ 

3978 pieces: list[EncodableT] = [b"GROUP", groupname, consumername] 

3979 if count is not None: 

3980 if not isinstance(count, int) or count < 1: 

3981 raise DataError("XREADGROUP count must be a positive integer") 

3982 pieces.append(b"COUNT") 

3983 pieces.append(str(count)) 

3984 if block is not None: 

3985 if not isinstance(block, int) or block < 0: 

3986 raise DataError("XREADGROUP block must be a non-negative integer") 

3987 pieces.append(b"BLOCK") 

3988 pieces.append(str(block)) 

3989 if noack: 

3990 pieces.append(b"NOACK") 

3991 if not isinstance(streams, dict) or len(streams) == 0: 

3992 raise DataError("XREADGROUP streams must be a non empty dict") 

3993 pieces.append(b"STREAMS") 

3994 pieces.extend(streams.keys()) 

3995 pieces.extend(streams.values()) 

3996 return self.execute_command("XREADGROUP", *pieces) 

3997 

3998 def xrevrange( 

3999 self, 

4000 name: KeyT, 

4001 max: StreamIdT = "+", 

4002 min: StreamIdT = "-", 

4003 count: Optional[int] = None, 

4004 ) -> ResponseT: 

4005 """ 

4006 Read stream values within an interval, in reverse order. 

4007 

4008 name: name of the stream 

4009 

4010 start: first stream ID. defaults to '+', 

4011 meaning the latest available. 

4012 

4013 finish: last stream ID. defaults to '-', 

4014 meaning the earliest available. 

4015 

4016 count: if set, only return this many items, beginning with the 

4017 latest available. 

4018 

4019 For more information see https://redis.io/commands/xrevrange 

4020 """ 

4021 pieces: list[EncodableT] = [max, min] 

4022 if count is not None: 

4023 if not isinstance(count, int) or count < 1: 

4024 raise DataError("XREVRANGE count must be a positive integer") 

4025 pieces.append(b"COUNT") 

4026 pieces.append(str(count)) 

4027 

4028 return self.execute_command("XREVRANGE", name, *pieces, keys=[name]) 

4029 

4030 def xtrim( 

4031 self, 

4032 name: KeyT, 

4033 maxlen: Optional[int] = None, 

4034 approximate: bool = True, 

4035 minid: Union[StreamIdT, None] = None, 

4036 limit: Optional[int] = None, 

4037 ) -> ResponseT: 

4038 """ 

4039 Trims old messages from a stream. 

4040 name: name of the stream. 

4041 maxlen: truncate old stream messages beyond this size 

4042 Can't be specified with minid. 

4043 approximate: actual stream length may be slightly more than maxlen 

4044 minid: the minimum id in the stream to query 

4045 Can't be specified with maxlen. 

4046 limit: specifies the maximum number of entries to retrieve 

4047 

4048 For more information see https://redis.io/commands/xtrim 

4049 """ 

4050 pieces: list[EncodableT] = [] 

4051 if maxlen is not None and minid is not None: 

4052 raise DataError("Only one of ``maxlen`` or ``minid`` may be specified") 

4053 

4054 if maxlen is None and minid is None: 

4055 raise DataError("One of ``maxlen`` or ``minid`` must be specified") 

4056 

4057 if maxlen is not None: 

4058 pieces.append(b"MAXLEN") 

4059 if minid is not None: 

4060 pieces.append(b"MINID") 

4061 if approximate: 

4062 pieces.append(b"~") 

4063 if maxlen is not None: 

4064 pieces.append(maxlen) 

4065 if minid is not None: 

4066 pieces.append(minid) 

4067 if limit is not None: 

4068 pieces.append(b"LIMIT") 

4069 pieces.append(limit) 

4070 

4071 return self.execute_command("XTRIM", name, *pieces) 

4072 

4073 

4074AsyncStreamCommands = StreamCommands 

4075 

4076 

4077class SortedSetCommands(CommandsProtocol): 

4078 """ 

4079 Redis commands for Sorted Sets data type. 

4080 see: https://redis.io/topics/data-types-intro#redis-sorted-sets 

4081 """ 

4082 

4083 def zadd( 

4084 self, 

4085 name: KeyT, 

4086 mapping: Mapping[AnyKeyT, EncodableT], 

4087 nx: bool = False, 

4088 xx: bool = False, 

4089 ch: bool = False, 

4090 incr: bool = False, 

4091 gt: bool = False, 

4092 lt: bool = False, 

4093 ) -> ResponseT: 

4094 """ 

4095 Set any number of element-name, score pairs to the key ``name``. Pairs 

4096 are specified as a dict of element-names keys to score values. 

4097 

4098 ``nx`` forces ZADD to only create new elements and not to update 

4099 scores for elements that already exist. 

4100 

4101 ``xx`` forces ZADD to only update scores of elements that already 

4102 exist. New elements will not be added. 

4103 

4104 ``ch`` modifies the return value to be the numbers of elements changed. 

4105 Changed elements include new elements that were added and elements 

4106 whose scores changed. 

4107 

4108 ``incr`` modifies ZADD to behave like ZINCRBY. In this mode only a 

4109 single element/score pair can be specified and the score is the amount 

4110 the existing score will be incremented by. When using this mode the 

4111 return value of ZADD will be the new score of the element. 

4112 

4113 ``LT`` Only update existing elements if the new score is less than 

4114 the current score. This flag doesn't prevent adding new elements. 

4115 

4116 ``GT`` Only update existing elements if the new score is greater than 

4117 the current score. This flag doesn't prevent adding new elements. 

4118 

4119 The return value of ZADD varies based on the mode specified. With no 

4120 options, ZADD returns the number of new elements added to the sorted 

4121 set. 

4122 

4123 ``NX``, ``LT``, and ``GT`` are mutually exclusive options. 

4124 

4125 See: https://redis.io/commands/ZADD 

4126 """ 

4127 if not mapping: 

4128 raise DataError("ZADD requires at least one element/score pair") 

4129 if nx and xx: 

4130 raise DataError("ZADD allows either 'nx' or 'xx', not both") 

4131 if gt and lt: 

4132 raise DataError("ZADD allows either 'gt' or 'lt', not both") 

4133 if incr and len(mapping) != 1: 

4134 raise DataError( 

4135 "ZADD option 'incr' only works when passing a single element/score pair" 

4136 ) 

4137 if nx and (gt or lt): 

4138 raise DataError("Only one of 'nx', 'lt', or 'gr' may be defined.") 

4139 

4140 pieces: list[EncodableT] = [] 

4141 options = {} 

4142 if nx: 

4143 pieces.append(b"NX") 

4144 if xx: 

4145 pieces.append(b"XX") 

4146 if ch: 

4147 pieces.append(b"CH") 

4148 if incr: 

4149 pieces.append(b"INCR") 

4150 options["as_score"] = True 

4151 if gt: 

4152 pieces.append(b"GT") 

4153 if lt: 

4154 pieces.append(b"LT") 

4155 for pair in mapping.items(): 

4156 pieces.append(pair[1]) 

4157 pieces.append(pair[0]) 

4158 return self.execute_command("ZADD", name, *pieces, **options) 

4159 

4160 def zcard(self, name: KeyT) -> ResponseT: 

4161 """ 

4162 Return the number of elements in the sorted set ``name`` 

4163 

4164 For more information see https://redis.io/commands/zcard 

4165 """ 

4166 return self.execute_command("ZCARD", name, keys=[name]) 

4167 

4168 def zcount(self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT) -> ResponseT: 

4169 """ 

4170 Returns the number of elements in the sorted set at key ``name`` with 

4171 a score between ``min`` and ``max``. 

4172 

4173 For more information see https://redis.io/commands/zcount 

4174 """ 

4175 return self.execute_command("ZCOUNT", name, min, max, keys=[name]) 

4176 

4177 def zdiff(self, keys: KeysT, withscores: bool = False) -> ResponseT: 

4178 """ 

4179 Returns the difference between the first and all successive input 

4180 sorted sets provided in ``keys``. 

4181 

4182 For more information see https://redis.io/commands/zdiff 

4183 """ 

4184 pieces = [len(keys), *keys] 

4185 if withscores: 

4186 pieces.append("WITHSCORES") 

4187 return self.execute_command("ZDIFF", *pieces, keys=keys) 

4188 

4189 def zdiffstore(self, dest: KeyT, keys: KeysT) -> ResponseT: 

4190 """ 

4191 Computes the difference between the first and all successive input 

4192 sorted sets provided in ``keys`` and stores the result in ``dest``. 

4193 

4194 For more information see https://redis.io/commands/zdiffstore 

4195 """ 

4196 pieces = [len(keys), *keys] 

4197 return self.execute_command("ZDIFFSTORE", dest, *pieces) 

4198 

4199 def zincrby(self, name: KeyT, amount: float, value: EncodableT) -> ResponseT: 

4200 """ 

4201 Increment the score of ``value`` in sorted set ``name`` by ``amount`` 

4202 

4203 For more information see https://redis.io/commands/zincrby 

4204 """ 

4205 return self.execute_command("ZINCRBY", name, amount, value) 

4206 

4207 def zinter( 

4208 self, keys: KeysT, aggregate: Optional[str] = None, withscores: bool = False 

4209 ) -> ResponseT: 

4210 """ 

4211 Return the intersect of multiple sorted sets specified by ``keys``. 

4212 With the ``aggregate`` option, it is possible to specify how the 

4213 results of the union are aggregated. This option defaults to SUM, 

4214 where the score of an element is summed across the inputs where it 

4215 exists. When this option is set to either MIN or MAX, the resulting 

4216 set will contain the minimum or maximum score of an element across 

4217 the inputs where it exists. 

4218 

4219 For more information see https://redis.io/commands/zinter 

4220 """ 

4221 return self._zaggregate("ZINTER", None, keys, aggregate, withscores=withscores) 

4222 

4223 def zinterstore( 

4224 self, 

4225 dest: KeyT, 

4226 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4227 aggregate: Optional[str] = None, 

4228 ) -> ResponseT: 

4229 """ 

4230 Intersect multiple sorted sets specified by ``keys`` into a new 

4231 sorted set, ``dest``. Scores in the destination will be aggregated 

4232 based on the ``aggregate``. This option defaults to SUM, where the 

4233 score of an element is summed across the inputs where it exists. 

4234 When this option is set to either MIN or MAX, the resulting set will 

4235 contain the minimum or maximum score of an element across the inputs 

4236 where it exists. 

4237 

4238 For more information see https://redis.io/commands/zinterstore 

4239 """ 

4240 return self._zaggregate("ZINTERSTORE", dest, keys, aggregate) 

4241 

4242 def zintercard( 

4243 self, numkeys: int, keys: List[str], limit: int = 0 

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

4245 """ 

4246 Return the cardinality of the intersect of multiple sorted sets 

4247 specified by ``keys``. 

4248 When LIMIT provided (defaults to 0 and means unlimited), if the intersection 

4249 cardinality reaches limit partway through the computation, the algorithm will 

4250 exit and yield limit as the cardinality 

4251 

4252 For more information see https://redis.io/commands/zintercard 

4253 """ 

4254 args = [numkeys, *keys, "LIMIT", limit] 

4255 return self.execute_command("ZINTERCARD", *args, keys=keys) 

4256 

4257 def zlexcount(self, name, min, max): 

4258 """ 

4259 Return the number of items in the sorted set ``name`` between the 

4260 lexicographical range ``min`` and ``max``. 

4261 

4262 For more information see https://redis.io/commands/zlexcount 

4263 """ 

4264 return self.execute_command("ZLEXCOUNT", name, min, max, keys=[name]) 

4265 

4266 def zpopmax(self, name: KeyT, count: Optional[int] = None) -> ResponseT: 

4267 """ 

4268 Remove and return up to ``count`` members with the highest scores 

4269 from the sorted set ``name``. 

4270 

4271 For more information see https://redis.io/commands/zpopmax 

4272 """ 

4273 args = (count is not None) and [count] or [] 

4274 options = {"withscores": True} 

4275 return self.execute_command("ZPOPMAX", name, *args, **options) 

4276 

4277 def zpopmin(self, name: KeyT, count: Optional[int] = None) -> ResponseT: 

4278 """ 

4279 Remove and return up to ``count`` members with the lowest scores 

4280 from the sorted set ``name``. 

4281 

4282 For more information see https://redis.io/commands/zpopmin 

4283 """ 

4284 args = (count is not None) and [count] or [] 

4285 options = {"withscores": True} 

4286 return self.execute_command("ZPOPMIN", name, *args, **options) 

4287 

4288 def zrandmember( 

4289 self, key: KeyT, count: Optional[int] = None, withscores: bool = False 

4290 ) -> ResponseT: 

4291 """ 

4292 Return a random element from the sorted set value stored at key. 

4293 

4294 ``count`` if the argument is positive, return an array of distinct 

4295 fields. If called with a negative count, the behavior changes and 

4296 the command is allowed to return the same field multiple times. 

4297 In this case, the number of returned fields is the absolute value 

4298 of the specified count. 

4299 

4300 ``withscores`` The optional WITHSCORES modifier changes the reply so it 

4301 includes the respective scores of the randomly selected elements from 

4302 the sorted set. 

4303 

4304 For more information see https://redis.io/commands/zrandmember 

4305 """ 

4306 params = [] 

4307 if count is not None: 

4308 params.append(count) 

4309 if withscores: 

4310 params.append("WITHSCORES") 

4311 

4312 return self.execute_command("ZRANDMEMBER", key, *params) 

4313 

4314 def bzpopmax(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4315 """ 

4316 ZPOPMAX a value off of the first non-empty sorted set 

4317 named in the ``keys`` list. 

4318 

4319 If none of the sorted sets in ``keys`` has a value to ZPOPMAX, 

4320 then block for ``timeout`` seconds, or until a member gets added 

4321 to one of the sorted sets. 

4322 

4323 If timeout is 0, then block indefinitely. 

4324 

4325 For more information see https://redis.io/commands/bzpopmax 

4326 """ 

4327 if timeout is None: 

4328 timeout = 0 

4329 keys = list_or_args(keys, None) 

4330 keys.append(timeout) 

4331 return self.execute_command("BZPOPMAX", *keys) 

4332 

4333 def bzpopmin(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4334 """ 

4335 ZPOPMIN a value off of the first non-empty sorted set 

4336 named in the ``keys`` list. 

4337 

4338 If none of the sorted sets in ``keys`` has a value to ZPOPMIN, 

4339 then block for ``timeout`` seconds, or until a member gets added 

4340 to one of the sorted sets. 

4341 

4342 If timeout is 0, then block indefinitely. 

4343 

4344 For more information see https://redis.io/commands/bzpopmin 

4345 """ 

4346 if timeout is None: 

4347 timeout = 0 

4348 keys: list[EncodableT] = list_or_args(keys, None) 

4349 keys.append(timeout) 

4350 return self.execute_command("BZPOPMIN", *keys) 

4351 

4352 def zmpop( 

4353 self, 

4354 num_keys: int, 

4355 keys: List[str], 

4356 min: Optional[bool] = False, 

4357 max: Optional[bool] = False, 

4358 count: Optional[int] = 1, 

4359 ) -> Union[Awaitable[list], list]: 

4360 """ 

4361 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4362 named in the ``keys`` list. 

4363 For more information see https://redis.io/commands/zmpop 

4364 """ 

4365 args = [num_keys] + keys 

4366 if (min and max) or (not min and not max): 

4367 raise DataError 

4368 elif min: 

4369 args.append("MIN") 

4370 else: 

4371 args.append("MAX") 

4372 if count != 1: 

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

4374 

4375 return self.execute_command("ZMPOP", *args) 

4376 

4377 def bzmpop( 

4378 self, 

4379 timeout: float, 

4380 numkeys: int, 

4381 keys: List[str], 

4382 min: Optional[bool] = False, 

4383 max: Optional[bool] = False, 

4384 count: Optional[int] = 1, 

4385 ) -> Optional[list]: 

4386 """ 

4387 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4388 named in the ``keys`` list. 

4389 

4390 If none of the sorted sets in ``keys`` has a value to pop, 

4391 then block for ``timeout`` seconds, or until a member gets added 

4392 to one of the sorted sets. 

4393 

4394 If timeout is 0, then block indefinitely. 

4395 

4396 For more information see https://redis.io/commands/bzmpop 

4397 """ 

4398 args = [timeout, numkeys, *keys] 

4399 if (min and max) or (not min and not max): 

4400 raise DataError("Either min or max, but not both must be set") 

4401 elif min: 

4402 args.append("MIN") 

4403 else: 

4404 args.append("MAX") 

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

4406 

4407 return self.execute_command("BZMPOP", *args) 

4408 

4409 def _zrange( 

4410 self, 

4411 command, 

4412 dest: Union[KeyT, None], 

4413 name: KeyT, 

4414 start: int, 

4415 end: int, 

4416 desc: bool = False, 

4417 byscore: bool = False, 

4418 bylex: bool = False, 

4419 withscores: bool = False, 

4420 score_cast_func: Union[type, Callable, None] = float, 

4421 offset: Optional[int] = None, 

4422 num: Optional[int] = None, 

4423 ) -> ResponseT: 

4424 if byscore and bylex: 

4425 raise DataError("``byscore`` and ``bylex`` can not be specified together.") 

4426 if (offset is not None and num is None) or (num is not None and offset is None): 

4427 raise DataError("``offset`` and ``num`` must both be specified.") 

4428 if bylex and withscores: 

4429 raise DataError( 

4430 "``withscores`` not supported in combination with ``bylex``." 

4431 ) 

4432 pieces = [command] 

4433 if dest: 

4434 pieces.append(dest) 

4435 pieces.extend([name, start, end]) 

4436 if byscore: 

4437 pieces.append("BYSCORE") 

4438 if bylex: 

4439 pieces.append("BYLEX") 

4440 if desc: 

4441 pieces.append("REV") 

4442 if offset is not None and num is not None: 

4443 pieces.extend(["LIMIT", offset, num]) 

4444 if withscores: 

4445 pieces.append("WITHSCORES") 

4446 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4447 options["keys"] = [name] 

4448 return self.execute_command(*pieces, **options) 

4449 

4450 def zrange( 

4451 self, 

4452 name: KeyT, 

4453 start: int, 

4454 end: int, 

4455 desc: bool = False, 

4456 withscores: bool = False, 

4457 score_cast_func: Union[type, Callable] = float, 

4458 byscore: bool = False, 

4459 bylex: bool = False, 

4460 offset: Optional[int] = None, 

4461 num: Optional[int] = None, 

4462 ) -> ResponseT: 

4463 """ 

4464 Return a range of values from sorted set ``name`` between 

4465 ``start`` and ``end`` sorted in ascending order. 

4466 

4467 ``start`` and ``end`` can be negative, indicating the end of the range. 

4468 

4469 ``desc`` a boolean indicating whether to sort the results in reversed 

4470 order. 

4471 

4472 ``withscores`` indicates to return the scores along with the values. 

4473 The return type is a list of (value, score) pairs. 

4474 

4475 ``score_cast_func`` a callable used to cast the score return value. 

4476 

4477 ``byscore`` when set to True, returns the range of elements from the 

4478 sorted set having scores equal or between ``start`` and ``end``. 

4479 

4480 ``bylex`` when set to True, returns the range of elements from the 

4481 sorted set between the ``start`` and ``end`` lexicographical closed 

4482 range intervals. 

4483 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

4484 whether the range interval is exclusive or inclusive, respectively. 

4485 

4486 ``offset`` and ``num`` are specified, then return a slice of the range. 

4487 Can't be provided when using ``bylex``. 

4488 

4489 For more information see https://redis.io/commands/zrange 

4490 """ 

4491 # Need to support ``desc`` also when using old redis version 

4492 # because it was supported in 3.5.3 (of redis-py) 

4493 if not byscore and not bylex and (offset is None and num is None) and desc: 

4494 return self.zrevrange(name, start, end, withscores, score_cast_func) 

4495 

4496 return self._zrange( 

4497 "ZRANGE", 

4498 None, 

4499 name, 

4500 start, 

4501 end, 

4502 desc, 

4503 byscore, 

4504 bylex, 

4505 withscores, 

4506 score_cast_func, 

4507 offset, 

4508 num, 

4509 ) 

4510 

4511 def zrevrange( 

4512 self, 

4513 name: KeyT, 

4514 start: int, 

4515 end: int, 

4516 withscores: bool = False, 

4517 score_cast_func: Union[type, Callable] = float, 

4518 ) -> ResponseT: 

4519 """ 

4520 Return a range of values from sorted set ``name`` between 

4521 ``start`` and ``end`` sorted in descending order. 

4522 

4523 ``start`` and ``end`` can be negative, indicating the end of the range. 

4524 

4525 ``withscores`` indicates to return the scores along with the values 

4526 The return type is a list of (value, score) pairs 

4527 

4528 ``score_cast_func`` a callable used to cast the score return value 

4529 

4530 For more information see https://redis.io/commands/zrevrange 

4531 """ 

4532 pieces = ["ZREVRANGE", name, start, end] 

4533 if withscores: 

4534 pieces.append(b"WITHSCORES") 

4535 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4536 options["keys"] = name 

4537 return self.execute_command(*pieces, **options) 

4538 

4539 def zrangestore( 

4540 self, 

4541 dest: KeyT, 

4542 name: KeyT, 

4543 start: int, 

4544 end: int, 

4545 byscore: bool = False, 

4546 bylex: bool = False, 

4547 desc: bool = False, 

4548 offset: Optional[int] = None, 

4549 num: Optional[int] = None, 

4550 ) -> ResponseT: 

4551 """ 

4552 Stores in ``dest`` the result of a range of values from sorted set 

4553 ``name`` between ``start`` and ``end`` sorted in ascending order. 

4554 

4555 ``start`` and ``end`` can be negative, indicating the end of the range. 

4556 

4557 ``byscore`` when set to True, returns the range of elements from the 

4558 sorted set having scores equal or between ``start`` and ``end``. 

4559 

4560 ``bylex`` when set to True, returns the range of elements from the 

4561 sorted set between the ``start`` and ``end`` lexicographical closed 

4562 range intervals. 

4563 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

4564 whether the range interval is exclusive or inclusive, respectively. 

4565 

4566 ``desc`` a boolean indicating whether to sort the results in reversed 

4567 order. 

4568 

4569 ``offset`` and ``num`` are specified, then return a slice of the range. 

4570 Can't be provided when using ``bylex``. 

4571 

4572 For more information see https://redis.io/commands/zrangestore 

4573 """ 

4574 return self._zrange( 

4575 "ZRANGESTORE", 

4576 dest, 

4577 name, 

4578 start, 

4579 end, 

4580 desc, 

4581 byscore, 

4582 bylex, 

4583 False, 

4584 None, 

4585 offset, 

4586 num, 

4587 ) 

4588 

4589 def zrangebylex( 

4590 self, 

4591 name: KeyT, 

4592 min: EncodableT, 

4593 max: EncodableT, 

4594 start: Optional[int] = None, 

4595 num: Optional[int] = None, 

4596 ) -> ResponseT: 

4597 """ 

4598 Return the lexicographical range of values from sorted set ``name`` 

4599 between ``min`` and ``max``. 

4600 

4601 If ``start`` and ``num`` are specified, then return a slice of the 

4602 range. 

4603 

4604 For more information see https://redis.io/commands/zrangebylex 

4605 """ 

4606 if (start is not None and num is None) or (num is not None and start is None): 

4607 raise DataError("``start`` and ``num`` must both be specified") 

4608 pieces = ["ZRANGEBYLEX", name, min, max] 

4609 if start is not None and num is not None: 

4610 pieces.extend([b"LIMIT", start, num]) 

4611 return self.execute_command(*pieces, keys=[name]) 

4612 

4613 def zrevrangebylex( 

4614 self, 

4615 name: KeyT, 

4616 max: EncodableT, 

4617 min: EncodableT, 

4618 start: Optional[int] = None, 

4619 num: Optional[int] = None, 

4620 ) -> ResponseT: 

4621 """ 

4622 Return the reversed lexicographical range of values from sorted set 

4623 ``name`` between ``max`` and ``min``. 

4624 

4625 If ``start`` and ``num`` are specified, then return a slice of the 

4626 range. 

4627 

4628 For more information see https://redis.io/commands/zrevrangebylex 

4629 """ 

4630 if (start is not None and num is None) or (num is not None and start is None): 

4631 raise DataError("``start`` and ``num`` must both be specified") 

4632 pieces = ["ZREVRANGEBYLEX", name, max, min] 

4633 if start is not None and num is not None: 

4634 pieces.extend(["LIMIT", start, num]) 

4635 return self.execute_command(*pieces, keys=[name]) 

4636 

4637 def zrangebyscore( 

4638 self, 

4639 name: KeyT, 

4640 min: ZScoreBoundT, 

4641 max: ZScoreBoundT, 

4642 start: Optional[int] = None, 

4643 num: Optional[int] = None, 

4644 withscores: bool = False, 

4645 score_cast_func: Union[type, Callable] = float, 

4646 ) -> ResponseT: 

4647 """ 

4648 Return a range of values from the sorted set ``name`` with scores 

4649 between ``min`` and ``max``. 

4650 

4651 If ``start`` and ``num`` are specified, then return a slice 

4652 of the range. 

4653 

4654 ``withscores`` indicates to return the scores along with the values. 

4655 The return type is a list of (value, score) pairs 

4656 

4657 `score_cast_func`` a callable used to cast the score return value 

4658 

4659 For more information see https://redis.io/commands/zrangebyscore 

4660 """ 

4661 if (start is not None and num is None) or (num is not None and start is None): 

4662 raise DataError("``start`` and ``num`` must both be specified") 

4663 pieces = ["ZRANGEBYSCORE", name, min, max] 

4664 if start is not None and num is not None: 

4665 pieces.extend(["LIMIT", start, num]) 

4666 if withscores: 

4667 pieces.append("WITHSCORES") 

4668 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4669 options["keys"] = [name] 

4670 return self.execute_command(*pieces, **options) 

4671 

4672 def zrevrangebyscore( 

4673 self, 

4674 name: KeyT, 

4675 max: ZScoreBoundT, 

4676 min: ZScoreBoundT, 

4677 start: Optional[int] = None, 

4678 num: Optional[int] = None, 

4679 withscores: bool = False, 

4680 score_cast_func: Union[type, Callable] = float, 

4681 ): 

4682 """ 

4683 Return a range of values from the sorted set ``name`` with scores 

4684 between ``min`` and ``max`` in descending order. 

4685 

4686 If ``start`` and ``num`` are specified, then return a slice 

4687 of the range. 

4688 

4689 ``withscores`` indicates to return the scores along with the values. 

4690 The return type is a list of (value, score) pairs 

4691 

4692 ``score_cast_func`` a callable used to cast the score return value 

4693 

4694 For more information see https://redis.io/commands/zrevrangebyscore 

4695 """ 

4696 if (start is not None and num is None) or (num is not None and start is None): 

4697 raise DataError("``start`` and ``num`` must both be specified") 

4698 pieces = ["ZREVRANGEBYSCORE", name, max, min] 

4699 if start is not None and num is not None: 

4700 pieces.extend(["LIMIT", start, num]) 

4701 if withscores: 

4702 pieces.append("WITHSCORES") 

4703 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4704 options["keys"] = [name] 

4705 return self.execute_command(*pieces, **options) 

4706 

4707 def zrank( 

4708 self, 

4709 name: KeyT, 

4710 value: EncodableT, 

4711 withscore: bool = False, 

4712 ) -> ResponseT: 

4713 """ 

4714 Returns a 0-based value indicating the rank of ``value`` in sorted set 

4715 ``name``. 

4716 The optional WITHSCORE argument supplements the command's 

4717 reply with the score of the element returned. 

4718 

4719 For more information see https://redis.io/commands/zrank 

4720 """ 

4721 if withscore: 

4722 return self.execute_command("ZRANK", name, value, "WITHSCORE", keys=[name]) 

4723 return self.execute_command("ZRANK", name, value, keys=[name]) 

4724 

4725 def zrem(self, name: KeyT, *values: FieldT) -> ResponseT: 

4726 """ 

4727 Remove member ``values`` from sorted set ``name`` 

4728 

4729 For more information see https://redis.io/commands/zrem 

4730 """ 

4731 return self.execute_command("ZREM", name, *values) 

4732 

4733 def zremrangebylex(self, name: KeyT, min: EncodableT, max: EncodableT) -> ResponseT: 

4734 """ 

4735 Remove all elements in the sorted set ``name`` between the 

4736 lexicographical range specified by ``min`` and ``max``. 

4737 

4738 Returns the number of elements removed. 

4739 

4740 For more information see https://redis.io/commands/zremrangebylex 

4741 """ 

4742 return self.execute_command("ZREMRANGEBYLEX", name, min, max) 

4743 

4744 def zremrangebyrank(self, name: KeyT, min: int, max: int) -> ResponseT: 

4745 """ 

4746 Remove all elements in the sorted set ``name`` with ranks between 

4747 ``min`` and ``max``. Values are 0-based, ordered from smallest score 

4748 to largest. Values can be negative indicating the highest scores. 

4749 Returns the number of elements removed 

4750 

4751 For more information see https://redis.io/commands/zremrangebyrank 

4752 """ 

4753 return self.execute_command("ZREMRANGEBYRANK", name, min, max) 

4754 

4755 def zremrangebyscore( 

4756 self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT 

4757 ) -> ResponseT: 

4758 """ 

4759 Remove all elements in the sorted set ``name`` with scores 

4760 between ``min`` and ``max``. Returns the number of elements removed. 

4761 

4762 For more information see https://redis.io/commands/zremrangebyscore 

4763 """ 

4764 return self.execute_command("ZREMRANGEBYSCORE", name, min, max) 

4765 

4766 def zrevrank( 

4767 self, 

4768 name: KeyT, 

4769 value: EncodableT, 

4770 withscore: bool = False, 

4771 ) -> ResponseT: 

4772 """ 

4773 Returns a 0-based value indicating the descending rank of 

4774 ``value`` in sorted set ``name``. 

4775 The optional ``withscore`` argument supplements the command's 

4776 reply with the score of the element returned. 

4777 

4778 For more information see https://redis.io/commands/zrevrank 

4779 """ 

4780 if withscore: 

4781 return self.execute_command( 

4782 "ZREVRANK", name, value, "WITHSCORE", keys=[name] 

4783 ) 

4784 return self.execute_command("ZREVRANK", name, value, keys=[name]) 

4785 

4786 def zscore(self, name: KeyT, value: EncodableT) -> ResponseT: 

4787 """ 

4788 Return the score of element ``value`` in sorted set ``name`` 

4789 

4790 For more information see https://redis.io/commands/zscore 

4791 """ 

4792 return self.execute_command("ZSCORE", name, value, keys=[name]) 

4793 

4794 def zunion( 

4795 self, 

4796 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4797 aggregate: Optional[str] = None, 

4798 withscores: bool = False, 

4799 ) -> ResponseT: 

4800 """ 

4801 Return the union of multiple sorted sets specified by ``keys``. 

4802 ``keys`` can be provided as dictionary of keys and their weights. 

4803 Scores will be aggregated based on the ``aggregate``, or SUM if 

4804 none is provided. 

4805 

4806 For more information see https://redis.io/commands/zunion 

4807 """ 

4808 return self._zaggregate("ZUNION", None, keys, aggregate, withscores=withscores) 

4809 

4810 def zunionstore( 

4811 self, 

4812 dest: KeyT, 

4813 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4814 aggregate: Optional[str] = None, 

4815 ) -> ResponseT: 

4816 """ 

4817 Union multiple sorted sets specified by ``keys`` into 

4818 a new sorted set, ``dest``. Scores in the destination will be 

4819 aggregated based on the ``aggregate``, or SUM if none is provided. 

4820 

4821 For more information see https://redis.io/commands/zunionstore 

4822 """ 

4823 return self._zaggregate("ZUNIONSTORE", dest, keys, aggregate) 

4824 

4825 def zmscore(self, key: KeyT, members: List[str]) -> ResponseT: 

4826 """ 

4827 Returns the scores associated with the specified members 

4828 in the sorted set stored at key. 

4829 ``members`` should be a list of the member name. 

4830 Return type is a list of score. 

4831 If the member does not exist, a None will be returned 

4832 in corresponding position. 

4833 

4834 For more information see https://redis.io/commands/zmscore 

4835 """ 

4836 if not members: 

4837 raise DataError("ZMSCORE members must be a non-empty list") 

4838 pieces = [key] + members 

4839 return self.execute_command("ZMSCORE", *pieces, keys=[key]) 

4840 

4841 def _zaggregate( 

4842 self, 

4843 command: str, 

4844 dest: Union[KeyT, None], 

4845 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4846 aggregate: Optional[str] = None, 

4847 **options, 

4848 ) -> ResponseT: 

4849 pieces: list[EncodableT] = [command] 

4850 if dest is not None: 

4851 pieces.append(dest) 

4852 pieces.append(len(keys)) 

4853 if isinstance(keys, dict): 

4854 keys, weights = keys.keys(), keys.values() 

4855 else: 

4856 weights = None 

4857 pieces.extend(keys) 

4858 if weights: 

4859 pieces.append(b"WEIGHTS") 

4860 pieces.extend(weights) 

4861 if aggregate: 

4862 if aggregate.upper() in ["SUM", "MIN", "MAX"]: 

4863 pieces.append(b"AGGREGATE") 

4864 pieces.append(aggregate) 

4865 else: 

4866 raise DataError("aggregate can be sum, min or max.") 

4867 if options.get("withscores", False): 

4868 pieces.append(b"WITHSCORES") 

4869 options["keys"] = keys 

4870 return self.execute_command(*pieces, **options) 

4871 

4872 

4873AsyncSortedSetCommands = SortedSetCommands 

4874 

4875 

4876class HyperlogCommands(CommandsProtocol): 

4877 """ 

4878 Redis commands of HyperLogLogs data type. 

4879 see: https://redis.io/topics/data-types-intro#hyperloglogs 

4880 """ 

4881 

4882 def pfadd(self, name: KeyT, *values: FieldT) -> ResponseT: 

4883 """ 

4884 Adds the specified elements to the specified HyperLogLog. 

4885 

4886 For more information see https://redis.io/commands/pfadd 

4887 """ 

4888 return self.execute_command("PFADD", name, *values) 

4889 

4890 def pfcount(self, *sources: KeyT) -> ResponseT: 

4891 """ 

4892 Return the approximated cardinality of 

4893 the set observed by the HyperLogLog at key(s). 

4894 

4895 For more information see https://redis.io/commands/pfcount 

4896 """ 

4897 return self.execute_command("PFCOUNT", *sources) 

4898 

4899 def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT: 

4900 """ 

4901 Merge N different HyperLogLogs into a single one. 

4902 

4903 For more information see https://redis.io/commands/pfmerge 

4904 """ 

4905 return self.execute_command("PFMERGE", dest, *sources) 

4906 

4907 

4908AsyncHyperlogCommands = HyperlogCommands 

4909 

4910 

4911class HashDataPersistOptions(Enum): 

4912 # set the value for each provided key to each 

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

4914 FNX = "FNX" 

4915 

4916 # set the value for each provided key to each 

4917 # provided value only if all already exist. 

4918 FXX = "FXX" 

4919 

4920 

4921class HashCommands(CommandsProtocol): 

4922 """ 

4923 Redis commands for Hash data type. 

4924 see: https://redis.io/topics/data-types-intro#redis-hashes 

4925 """ 

4926 

4927 def hdel(self, name: str, *keys: str) -> Union[Awaitable[int], int]: 

4928 """ 

4929 Delete ``keys`` from hash ``name`` 

4930 

4931 For more information see https://redis.io/commands/hdel 

4932 """ 

4933 return self.execute_command("HDEL", name, *keys) 

4934 

4935 def hexists(self, name: str, key: str) -> Union[Awaitable[bool], bool]: 

4936 """ 

4937 Returns a boolean indicating if ``key`` exists within hash ``name`` 

4938 

4939 For more information see https://redis.io/commands/hexists 

4940 """ 

4941 return self.execute_command("HEXISTS", name, key, keys=[name]) 

4942 

4943 def hget( 

4944 self, name: str, key: str 

4945 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

4946 """ 

4947 Return the value of ``key`` within the hash ``name`` 

4948 

4949 For more information see https://redis.io/commands/hget 

4950 """ 

4951 return self.execute_command("HGET", name, key, keys=[name]) 

4952 

4953 def hgetall(self, name: str) -> Union[Awaitable[dict], dict]: 

4954 """ 

4955 Return a Python dict of the hash's name/value pairs 

4956 

4957 For more information see https://redis.io/commands/hgetall 

4958 """ 

4959 return self.execute_command("HGETALL", name, keys=[name]) 

4960 

4961 def hgetdel( 

4962 self, name: str, *keys: str 

4963 ) -> Union[ 

4964 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]] 

4965 ]: 

4966 """ 

4967 Return the value of ``key`` within the hash ``name`` and 

4968 delete the field in the hash. 

4969 This command is similar to HGET, except for the fact that it also deletes 

4970 the key on success from the hash with the provided ```name```. 

4971 

4972 Available since Redis 8.0 

4973 For more information see https://redis.io/commands/hgetdel 

4974 """ 

4975 if len(keys) == 0: 

4976 raise DataError("'hgetdel' should have at least one key provided") 

4977 

4978 return self.execute_command("HGETDEL", name, "FIELDS", len(keys), *keys) 

4979 

4980 def hgetex( 

4981 self, 

4982 name: KeyT, 

4983 *keys: str, 

4984 ex: Optional[ExpiryT] = None, 

4985 px: Optional[ExpiryT] = None, 

4986 exat: Optional[AbsExpiryT] = None, 

4987 pxat: Optional[AbsExpiryT] = None, 

4988 persist: bool = False, 

4989 ) -> Union[ 

4990 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]] 

4991 ]: 

4992 """ 

4993 Return the values of ``key`` and ``keys`` within the hash ``name`` 

4994 and optionally set their expiration. 

4995 

4996 ``ex`` sets an expire flag on ``kyes`` for ``ex`` seconds. 

4997 

4998 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds. 

4999 

5000 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds, 

5001 specified in unix time. 

5002 

5003 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds, 

5004 specified in unix time. 

5005 

5006 ``persist`` remove the time to live associated with the ``keys``. 

5007 

5008 Available since Redis 8.0 

5009 For more information see https://redis.io/commands/hgetex 

5010 """ 

5011 if not keys: 

5012 raise DataError("'hgetex' should have at least one key provided") 

5013 

5014 opset = {ex, px, exat, pxat} 

5015 if len(opset) > 2 or len(opset) > 1 and persist: 

5016 raise DataError( 

5017 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

5019 ) 

5020 

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

5022 

5023 if persist: 

5024 exp_options.append("PERSIST") 

5025 

5026 return self.execute_command( 

5027 "HGETEX", 

5028 name, 

5029 *exp_options, 

5030 "FIELDS", 

5031 len(keys), 

5032 *keys, 

5033 ) 

5034 

5035 def hincrby( 

5036 self, name: str, key: str, amount: int = 1 

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

5038 """ 

5039 Increment the value of ``key`` in hash ``name`` by ``amount`` 

5040 

5041 For more information see https://redis.io/commands/hincrby 

5042 """ 

5043 return self.execute_command("HINCRBY", name, key, amount) 

5044 

5045 def hincrbyfloat( 

5046 self, name: str, key: str, amount: float = 1.0 

5047 ) -> Union[Awaitable[float], float]: 

5048 """ 

5049 Increment the value of ``key`` in hash ``name`` by floating ``amount`` 

5050 

5051 For more information see https://redis.io/commands/hincrbyfloat 

5052 """ 

5053 return self.execute_command("HINCRBYFLOAT", name, key, amount) 

5054 

5055 def hkeys(self, name: str) -> Union[Awaitable[List], List]: 

5056 """ 

5057 Return the list of keys within hash ``name`` 

5058 

5059 For more information see https://redis.io/commands/hkeys 

5060 """ 

5061 return self.execute_command("HKEYS", name, keys=[name]) 

5062 

5063 def hlen(self, name: str) -> Union[Awaitable[int], int]: 

5064 """ 

5065 Return the number of elements in hash ``name`` 

5066 

5067 For more information see https://redis.io/commands/hlen 

5068 """ 

5069 return self.execute_command("HLEN", name, keys=[name]) 

5070 

5071 def hset( 

5072 self, 

5073 name: str, 

5074 key: Optional[str] = None, 

5075 value: Optional[str] = None, 

5076 mapping: Optional[dict] = None, 

5077 items: Optional[list] = None, 

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

5079 """ 

5080 Set ``key`` to ``value`` within hash ``name``, 

5081 ``mapping`` accepts a dict of key/value pairs that will be 

5082 added to hash ``name``. 

5083 ``items`` accepts a list of key/value pairs that will be 

5084 added to hash ``name``. 

5085 Returns the number of fields that were added. 

5086 

5087 For more information see https://redis.io/commands/hset 

5088 """ 

5089 

5090 if key is None and not mapping and not items: 

5091 raise DataError("'hset' with no key value pairs") 

5092 

5093 pieces = [] 

5094 if items: 

5095 pieces.extend(items) 

5096 if key is not None: 

5097 pieces.extend((key, value)) 

5098 if mapping: 

5099 for pair in mapping.items(): 

5100 pieces.extend(pair) 

5101 

5102 return self.execute_command("HSET", name, *pieces) 

5103 

5104 def hsetex( 

5105 self, 

5106 name: str, 

5107 key: Optional[str] = None, 

5108 value: Optional[str] = None, 

5109 mapping: Optional[dict] = None, 

5110 items: Optional[list] = None, 

5111 ex: Optional[ExpiryT] = None, 

5112 px: Optional[ExpiryT] = None, 

5113 exat: Optional[AbsExpiryT] = None, 

5114 pxat: Optional[AbsExpiryT] = None, 

5115 data_persist_option: Optional[HashDataPersistOptions] = None, 

5116 keepttl: bool = False, 

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

5118 """ 

5119 Set ``key`` to ``value`` within hash ``name`` 

5120 

5121 ``mapping`` accepts a dict of key/value pairs that will be 

5122 added to hash ``name``. 

5123 

5124 ``items`` accepts a list of key/value pairs that will be 

5125 added to hash ``name``. 

5126 

5127 ``ex`` sets an expire flag on ``keys`` for ``ex`` seconds. 

5128 

5129 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds. 

5130 

5131 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds, 

5132 specified in unix time. 

5133 

5134 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds, 

5135 specified in unix time. 

5136 

5137 ``data_persist_option`` can be set to ``FNX`` or ``FXX`` to control the 

5138 behavior of the command. 

5139 ``FNX`` will set the value for each provided key to each 

5140 provided value only if all do not already exist. 

5141 ``FXX`` will set the value for each provided key to each 

5142 provided value only if all already exist. 

5143 

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

5145 

5146 Returns the number of fields that were added. 

5147 

5148 Available since Redis 8.0 

5149 For more information see https://redis.io/commands/hsetex 

5150 """ 

5151 if key is None and not mapping and not items: 

5152 raise DataError("'hsetex' with no key value pairs") 

5153 

5154 if items and len(items) % 2 != 0: 

5155 raise DataError( 

5156 "'hsetex' with odd number of items. " 

5157 "'items' must contain a list of key/value pairs." 

5158 ) 

5159 

5160 opset = {ex, px, exat, pxat} 

5161 if len(opset) > 2 or len(opset) > 1 and keepttl: 

5162 raise DataError( 

5163 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

5165 ) 

5166 

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

5168 if data_persist_option: 

5169 exp_options.append(data_persist_option.value) 

5170 

5171 if keepttl: 

5172 exp_options.append("KEEPTTL") 

5173 

5174 pieces = [] 

5175 if items: 

5176 pieces.extend(items) 

5177 if key is not None: 

5178 pieces.extend((key, value)) 

5179 if mapping: 

5180 for pair in mapping.items(): 

5181 pieces.extend(pair) 

5182 

5183 return self.execute_command( 

5184 "HSETEX", name, *exp_options, "FIELDS", int(len(pieces) / 2), *pieces 

5185 ) 

5186 

5187 def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool]: 

5188 """ 

5189 Set ``key`` to ``value`` within hash ``name`` if ``key`` does not 

5190 exist. Returns 1 if HSETNX created a field, otherwise 0. 

5191 

5192 For more information see https://redis.io/commands/hsetnx 

5193 """ 

5194 return self.execute_command("HSETNX", name, key, value) 

5195 

5196 @deprecated_function( 

5197 version="4.0.0", 

5198 reason="Use 'hset' instead.", 

5199 name="hmset", 

5200 ) 

5201 def hmset(self, name: str, mapping: dict) -> Union[Awaitable[str], str]: 

5202 """ 

5203 Set key to value within hash ``name`` for each corresponding 

5204 key and value from the ``mapping`` dict. 

5205 

5206 For more information see https://redis.io/commands/hmset 

5207 """ 

5208 if not mapping: 

5209 raise DataError("'hmset' with 'mapping' of length 0") 

5210 items = [] 

5211 for pair in mapping.items(): 

5212 items.extend(pair) 

5213 return self.execute_command("HMSET", name, *items) 

5214 

5215 def hmget(self, name: str, keys: List, *args: List) -> Union[Awaitable[List], List]: 

5216 """ 

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

5218 

5219 For more information see https://redis.io/commands/hmget 

5220 """ 

5221 args = list_or_args(keys, args) 

5222 return self.execute_command("HMGET", name, *args, keys=[name]) 

5223 

5224 def hvals(self, name: str) -> Union[Awaitable[List], List]: 

5225 """ 

5226 Return the list of values within hash ``name`` 

5227 

5228 For more information see https://redis.io/commands/hvals 

5229 """ 

5230 return self.execute_command("HVALS", name, keys=[name]) 

5231 

5232 def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]: 

5233 """ 

5234 Return the number of bytes stored in the value of ``key`` 

5235 within hash ``name`` 

5236 

5237 For more information see https://redis.io/commands/hstrlen 

5238 """ 

5239 return self.execute_command("HSTRLEN", name, key, keys=[name]) 

5240 

5241 def hexpire( 

5242 self, 

5243 name: KeyT, 

5244 seconds: ExpiryT, 

5245 *fields: str, 

5246 nx: bool = False, 

5247 xx: bool = False, 

5248 gt: bool = False, 

5249 lt: bool = False, 

5250 ) -> ResponseT: 

5251 """ 

5252 Sets or updates the expiration time for fields within a hash key, using relative 

5253 time in seconds. 

5254 

5255 If a field already has an expiration time, the behavior of the update can be 

5256 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5257 

5258 The return value provides detailed information about the outcome for each field. 

5259 

5260 For more information, see https://redis.io/commands/hexpire 

5261 

5262 Args: 

5263 name: The name of the hash key. 

5264 seconds: Expiration time in seconds, relative. Can be an integer, or a 

5265 Python `timedelta` object. 

5266 fields: List of fields within the hash to apply the expiration time to. 

5267 nx: Set expiry only when the field has no expiry. 

5268 xx: Set expiry only when the field has an existing expiry. 

5269 gt: Set expiry only when the new expiry is greater than the current one. 

5270 lt: Set expiry only when the new expiry is less than the current one. 

5271 

5272 Returns: 

5273 Returns a list which contains for each field in the request: 

5274 - `-2` if the field does not exist, or if the key does not exist. 

5275 - `0` if the specified NX | XX | GT | LT condition was not met. 

5276 - `1` if the expiration time was set or updated. 

5277 - `2` if the field was deleted because the specified expiration time is 

5278 in the past. 

5279 """ 

5280 conditions = [nx, xx, gt, lt] 

5281 if sum(conditions) > 1: 

5282 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5283 

5284 if isinstance(seconds, datetime.timedelta): 

5285 seconds = int(seconds.total_seconds()) 

5286 

5287 options = [] 

5288 if nx: 

5289 options.append("NX") 

5290 if xx: 

5291 options.append("XX") 

5292 if gt: 

5293 options.append("GT") 

5294 if lt: 

5295 options.append("LT") 

5296 

5297 return self.execute_command( 

5298 "HEXPIRE", name, seconds, *options, "FIELDS", len(fields), *fields 

5299 ) 

5300 

5301 def hpexpire( 

5302 self, 

5303 name: KeyT, 

5304 milliseconds: ExpiryT, 

5305 *fields: str, 

5306 nx: bool = False, 

5307 xx: bool = False, 

5308 gt: bool = False, 

5309 lt: bool = False, 

5310 ) -> ResponseT: 

5311 """ 

5312 Sets or updates the expiration time for fields within a hash key, using relative 

5313 time in milliseconds. 

5314 

5315 If a field already has an expiration time, the behavior of the update can be 

5316 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5317 

5318 The return value provides detailed information about the outcome for each field. 

5319 

5320 For more information, see https://redis.io/commands/hpexpire 

5321 

5322 Args: 

5323 name: The name of the hash key. 

5324 milliseconds: Expiration time in milliseconds, relative. Can be an integer, 

5325 or a Python `timedelta` object. 

5326 fields: List of fields within the hash to apply the expiration time to. 

5327 nx: Set expiry only when the field has no expiry. 

5328 xx: Set expiry only when the field has an existing expiry. 

5329 gt: Set expiry only when the new expiry is greater than the current one. 

5330 lt: Set expiry only when the new expiry is less than the current one. 

5331 

5332 Returns: 

5333 Returns a list which contains for each field in the request: 

5334 - `-2` if the field does not exist, or if the key does not exist. 

5335 - `0` if the specified NX | XX | GT | LT condition was not met. 

5336 - `1` if the expiration time was set or updated. 

5337 - `2` if the field was deleted because the specified expiration time is 

5338 in the past. 

5339 """ 

5340 conditions = [nx, xx, gt, lt] 

5341 if sum(conditions) > 1: 

5342 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5343 

5344 if isinstance(milliseconds, datetime.timedelta): 

5345 milliseconds = int(milliseconds.total_seconds() * 1000) 

5346 

5347 options = [] 

5348 if nx: 

5349 options.append("NX") 

5350 if xx: 

5351 options.append("XX") 

5352 if gt: 

5353 options.append("GT") 

5354 if lt: 

5355 options.append("LT") 

5356 

5357 return self.execute_command( 

5358 "HPEXPIRE", name, milliseconds, *options, "FIELDS", len(fields), *fields 

5359 ) 

5360 

5361 def hexpireat( 

5362 self, 

5363 name: KeyT, 

5364 unix_time_seconds: AbsExpiryT, 

5365 *fields: str, 

5366 nx: bool = False, 

5367 xx: bool = False, 

5368 gt: bool = False, 

5369 lt: bool = False, 

5370 ) -> ResponseT: 

5371 """ 

5372 Sets or updates the expiration time for fields within a hash key, using an 

5373 absolute Unix timestamp in seconds. 

5374 

5375 If a field already has an expiration time, the behavior of the update can be 

5376 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5377 

5378 The return value provides detailed information about the outcome for each field. 

5379 

5380 For more information, see https://redis.io/commands/hexpireat 

5381 

5382 Args: 

5383 name: The name of the hash key. 

5384 unix_time_seconds: Expiration time as Unix timestamp in seconds. Can be an 

5385 integer or a Python `datetime` object. 

5386 fields: List of fields within the hash to apply the expiration time to. 

5387 nx: Set expiry only when the field has no expiry. 

5388 xx: Set expiry only when the field has an existing expiration time. 

5389 gt: Set expiry only when the new expiry is greater than the current one. 

5390 lt: Set expiry only when the new expiry is less than the current one. 

5391 

5392 Returns: 

5393 Returns a list which contains for each field in the request: 

5394 - `-2` if the field does not exist, or if the key does not exist. 

5395 - `0` if the specified NX | XX | GT | LT condition was not met. 

5396 - `1` if the expiration time was set or updated. 

5397 - `2` if the field was deleted because the specified expiration time is 

5398 in the past. 

5399 """ 

5400 conditions = [nx, xx, gt, lt] 

5401 if sum(conditions) > 1: 

5402 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5403 

5404 if isinstance(unix_time_seconds, datetime.datetime): 

5405 unix_time_seconds = int(unix_time_seconds.timestamp()) 

5406 

5407 options = [] 

5408 if nx: 

5409 options.append("NX") 

5410 if xx: 

5411 options.append("XX") 

5412 if gt: 

5413 options.append("GT") 

5414 if lt: 

5415 options.append("LT") 

5416 

5417 return self.execute_command( 

5418 "HEXPIREAT", 

5419 name, 

5420 unix_time_seconds, 

5421 *options, 

5422 "FIELDS", 

5423 len(fields), 

5424 *fields, 

5425 ) 

5426 

5427 def hpexpireat( 

5428 self, 

5429 name: KeyT, 

5430 unix_time_milliseconds: AbsExpiryT, 

5431 *fields: str, 

5432 nx: bool = False, 

5433 xx: bool = False, 

5434 gt: bool = False, 

5435 lt: bool = False, 

5436 ) -> ResponseT: 

5437 """ 

5438 Sets or updates the expiration time for fields within a hash key, using an 

5439 absolute Unix timestamp in milliseconds. 

5440 

5441 If a field already has an expiration time, the behavior of the update can be 

5442 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5443 

5444 The return value provides detailed information about the outcome for each field. 

5445 

5446 For more information, see https://redis.io/commands/hpexpireat 

5447 

5448 Args: 

5449 name: The name of the hash key. 

5450 unix_time_milliseconds: Expiration time as Unix timestamp in milliseconds. 

5451 Can be an integer or a Python `datetime` object. 

5452 fields: List of fields within the hash to apply the expiry. 

5453 nx: Set expiry only when the field has no expiry. 

5454 xx: Set expiry only when the field has an existing expiry. 

5455 gt: Set expiry only when the new expiry is greater than the current one. 

5456 lt: Set expiry only when the new expiry is less than the current one. 

5457 

5458 Returns: 

5459 Returns a list which contains for each field in the request: 

5460 - `-2` if the field does not exist, or if the key does not exist. 

5461 - `0` if the specified NX | XX | GT | LT condition was not met. 

5462 - `1` if the expiration time was set or updated. 

5463 - `2` if the field was deleted because the specified expiration time is 

5464 in the past. 

5465 """ 

5466 conditions = [nx, xx, gt, lt] 

5467 if sum(conditions) > 1: 

5468 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5469 

5470 if isinstance(unix_time_milliseconds, datetime.datetime): 

5471 unix_time_milliseconds = int(unix_time_milliseconds.timestamp() * 1000) 

5472 

5473 options = [] 

5474 if nx: 

5475 options.append("NX") 

5476 if xx: 

5477 options.append("XX") 

5478 if gt: 

5479 options.append("GT") 

5480 if lt: 

5481 options.append("LT") 

5482 

5483 return self.execute_command( 

5484 "HPEXPIREAT", 

5485 name, 

5486 unix_time_milliseconds, 

5487 *options, 

5488 "FIELDS", 

5489 len(fields), 

5490 *fields, 

5491 ) 

5492 

5493 def hpersist(self, name: KeyT, *fields: str) -> ResponseT: 

5494 """ 

5495 Removes the expiration time for each specified field in a hash. 

5496 

5497 For more information, see https://redis.io/commands/hpersist 

5498 

5499 Args: 

5500 name: The name of the hash key. 

5501 fields: A list of fields within the hash from which to remove the 

5502 expiration time. 

5503 

5504 Returns: 

5505 Returns a list which contains for each field in the request: 

5506 - `-2` if the field does not exist, or if the key does not exist. 

5507 - `-1` if the field exists but has no associated expiration time. 

5508 - `1` if the expiration time was successfully removed from the field. 

5509 """ 

5510 return self.execute_command("HPERSIST", name, "FIELDS", len(fields), *fields) 

5511 

5512 def hexpiretime(self, key: KeyT, *fields: str) -> ResponseT: 

5513 """ 

5514 Returns the expiration times of hash fields as Unix timestamps in seconds. 

5515 

5516 For more information, see https://redis.io/commands/hexpiretime 

5517 

5518 Args: 

5519 key: The hash key. 

5520 fields: A list of fields within the hash for which to get the expiration 

5521 time. 

5522 

5523 Returns: 

5524 Returns a list which contains for each field in the request: 

5525 - `-2` if the field does not exist, or if the key does not exist. 

5526 - `-1` if the field exists but has no associated expire time. 

5527 - A positive integer representing the expiration Unix timestamp in 

5528 seconds, if the field has an associated expiration time. 

5529 """ 

5530 return self.execute_command( 

5531 "HEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key] 

5532 ) 

5533 

5534 def hpexpiretime(self, key: KeyT, *fields: str) -> ResponseT: 

5535 """ 

5536 Returns the expiration times of hash fields as Unix timestamps in milliseconds. 

5537 

5538 For more information, see https://redis.io/commands/hpexpiretime 

5539 

5540 Args: 

5541 key: The hash key. 

5542 fields: A list of fields within the hash for which to get the expiration 

5543 time. 

5544 

5545 Returns: 

5546 Returns a list which contains for each field in the request: 

5547 - `-2` if the field does not exist, or if the key does not exist. 

5548 - `-1` if the field exists but has no associated expire time. 

5549 - A positive integer representing the expiration Unix timestamp in 

5550 milliseconds, if the field has an associated expiration time. 

5551 """ 

5552 return self.execute_command( 

5553 "HPEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key] 

5554 ) 

5555 

5556 def httl(self, key: KeyT, *fields: str) -> ResponseT: 

5557 """ 

5558 Returns the TTL (Time To Live) in seconds for each specified field within a hash 

5559 key. 

5560 

5561 For more information, see https://redis.io/commands/httl 

5562 

5563 Args: 

5564 key: The hash key. 

5565 fields: A list of fields within the hash for which to get the TTL. 

5566 

5567 Returns: 

5568 Returns a list which contains for each field in the request: 

5569 - `-2` if the field does not exist, or if the key does not exist. 

5570 - `-1` if the field exists but has no associated expire time. 

5571 - A positive integer representing the TTL in seconds if the field has 

5572 an associated expiration time. 

5573 """ 

5574 return self.execute_command( 

5575 "HTTL", key, "FIELDS", len(fields), *fields, keys=[key] 

5576 ) 

5577 

5578 def hpttl(self, key: KeyT, *fields: str) -> ResponseT: 

5579 """ 

5580 Returns the TTL (Time To Live) in milliseconds for each specified field within a 

5581 hash key. 

5582 

5583 For more information, see https://redis.io/commands/hpttl 

5584 

5585 Args: 

5586 key: The hash key. 

5587 fields: A list of fields within the hash for which to get the TTL. 

5588 

5589 Returns: 

5590 Returns a list which contains for each field in the request: 

5591 - `-2` if the field does not exist, or if the key does not exist. 

5592 - `-1` if the field exists but has no associated expire time. 

5593 - A positive integer representing the TTL in milliseconds if the field 

5594 has an associated expiration time. 

5595 """ 

5596 return self.execute_command( 

5597 "HPTTL", key, "FIELDS", len(fields), *fields, keys=[key] 

5598 ) 

5599 

5600 

5601AsyncHashCommands = HashCommands 

5602 

5603 

5604class Script: 

5605 """ 

5606 An executable Lua script object returned by ``register_script`` 

5607 """ 

5608 

5609 def __init__(self, registered_client: "redis.client.Redis", script: ScriptTextT): 

5610 self.registered_client = registered_client 

5611 self.script = script 

5612 # Precalculate and store the SHA1 hex digest of the script. 

5613 

5614 if isinstance(script, str): 

5615 # We need the encoding from the client in order to generate an 

5616 # accurate byte representation of the script 

5617 encoder = self.get_encoder() 

5618 script = encoder.encode(script) 

5619 self.sha = hashlib.sha1(script).hexdigest() 

5620 

5621 def __call__( 

5622 self, 

5623 keys: Union[Sequence[KeyT], None] = None, 

5624 args: Union[Iterable[EncodableT], None] = None, 

5625 client: Union["redis.client.Redis", None] = None, 

5626 ): 

5627 """Execute the script, passing any required ``args``""" 

5628 keys = keys or [] 

5629 args = args or [] 

5630 if client is None: 

5631 client = self.registered_client 

5632 args = tuple(keys) + tuple(args) 

5633 # make sure the Redis server knows about the script 

5634 from redis.client import Pipeline 

5635 

5636 if isinstance(client, Pipeline): 

5637 # Make sure the pipeline can register the script before executing. 

5638 client.scripts.add(self) 

5639 try: 

5640 return client.evalsha(self.sha, len(keys), *args) 

5641 except NoScriptError: 

5642 # Maybe the client is pointed to a different server than the client 

5643 # that created this instance? 

5644 # Overwrite the sha just in case there was a discrepancy. 

5645 self.sha = client.script_load(self.script) 

5646 return client.evalsha(self.sha, len(keys), *args) 

5647 

5648 def get_encoder(self): 

5649 """Get the encoder to encode string scripts into bytes.""" 

5650 try: 

5651 return self.registered_client.get_encoder() 

5652 except AttributeError: 

5653 # DEPRECATED 

5654 # In version <=4.1.2, this was the code we used to get the encoder. 

5655 # However, after 4.1.2 we added support for scripting in clustered 

5656 # redis. ClusteredRedis doesn't have a `.connection_pool` attribute 

5657 # so we changed the Script class to use 

5658 # `self.registered_client.get_encoder` (see above). 

5659 # However, that is technically a breaking change, as consumers who 

5660 # use Scripts directly might inject a `registered_client` that 

5661 # doesn't have a `.get_encoder` field. This try/except prevents us 

5662 # from breaking backward-compatibility. Ideally, it would be 

5663 # removed in the next major release. 

5664 return self.registered_client.connection_pool.get_encoder() 

5665 

5666 

5667class AsyncScript: 

5668 """ 

5669 An executable Lua script object returned by ``register_script`` 

5670 """ 

5671 

5672 def __init__( 

5673 self, 

5674 registered_client: "redis.asyncio.client.Redis", 

5675 script: ScriptTextT, 

5676 ): 

5677 self.registered_client = registered_client 

5678 self.script = script 

5679 # Precalculate and store the SHA1 hex digest of the script. 

5680 

5681 if isinstance(script, str): 

5682 # We need the encoding from the client in order to generate an 

5683 # accurate byte representation of the script 

5684 try: 

5685 encoder = registered_client.connection_pool.get_encoder() 

5686 except AttributeError: 

5687 # Cluster 

5688 encoder = registered_client.get_encoder() 

5689 script = encoder.encode(script) 

5690 self.sha = hashlib.sha1(script).hexdigest() 

5691 

5692 async def __call__( 

5693 self, 

5694 keys: Union[Sequence[KeyT], None] = None, 

5695 args: Union[Iterable[EncodableT], None] = None, 

5696 client: Union["redis.asyncio.client.Redis", None] = None, 

5697 ): 

5698 """Execute the script, passing any required ``args``""" 

5699 keys = keys or [] 

5700 args = args or [] 

5701 if client is None: 

5702 client = self.registered_client 

5703 args = tuple(keys) + tuple(args) 

5704 # make sure the Redis server knows about the script 

5705 from redis.asyncio.client import Pipeline 

5706 

5707 if isinstance(client, Pipeline): 

5708 # Make sure the pipeline can register the script before executing. 

5709 client.scripts.add(self) 

5710 try: 

5711 return await client.evalsha(self.sha, len(keys), *args) 

5712 except NoScriptError: 

5713 # Maybe the client is pointed to a different server than the client 

5714 # that created this instance? 

5715 # Overwrite the sha just in case there was a discrepancy. 

5716 self.sha = await client.script_load(self.script) 

5717 return await client.evalsha(self.sha, len(keys), *args) 

5718 

5719 

5720class PubSubCommands(CommandsProtocol): 

5721 """ 

5722 Redis PubSub commands. 

5723 see https://redis.io/topics/pubsub 

5724 """ 

5725 

5726 def publish(self, channel: ChannelT, message: EncodableT, **kwargs) -> ResponseT: 

5727 """ 

5728 Publish ``message`` on ``channel``. 

5729 Returns the number of subscribers the message was delivered to. 

5730 

5731 For more information see https://redis.io/commands/publish 

5732 """ 

5733 return self.execute_command("PUBLISH", channel, message, **kwargs) 

5734 

5735 def spublish(self, shard_channel: ChannelT, message: EncodableT) -> ResponseT: 

5736 """ 

5737 Posts a message to the given shard channel. 

5738 Returns the number of clients that received the message 

5739 

5740 For more information see https://redis.io/commands/spublish 

5741 """ 

5742 return self.execute_command("SPUBLISH", shard_channel, message) 

5743 

5744 def pubsub_channels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

5745 """ 

5746 Return a list of channels that have at least one subscriber 

5747 

5748 For more information see https://redis.io/commands/pubsub-channels 

5749 """ 

5750 return self.execute_command("PUBSUB CHANNELS", pattern, **kwargs) 

5751 

5752 def pubsub_shardchannels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

5753 """ 

5754 Return a list of shard_channels that have at least one subscriber 

5755 

5756 For more information see https://redis.io/commands/pubsub-shardchannels 

5757 """ 

5758 return self.execute_command("PUBSUB SHARDCHANNELS", pattern, **kwargs) 

5759 

5760 def pubsub_numpat(self, **kwargs) -> ResponseT: 

5761 """ 

5762 Returns the number of subscriptions to patterns 

5763 

5764 For more information see https://redis.io/commands/pubsub-numpat 

5765 """ 

5766 return self.execute_command("PUBSUB NUMPAT", **kwargs) 

5767 

5768 def pubsub_numsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

5769 """ 

5770 Return a list of (channel, number of subscribers) tuples 

5771 for each channel given in ``*args`` 

5772 

5773 For more information see https://redis.io/commands/pubsub-numsub 

5774 """ 

5775 return self.execute_command("PUBSUB NUMSUB", *args, **kwargs) 

5776 

5777 def pubsub_shardnumsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

5778 """ 

5779 Return a list of (shard_channel, number of subscribers) tuples 

5780 for each channel given in ``*args`` 

5781 

5782 For more information see https://redis.io/commands/pubsub-shardnumsub 

5783 """ 

5784 return self.execute_command("PUBSUB SHARDNUMSUB", *args, **kwargs) 

5785 

5786 

5787AsyncPubSubCommands = PubSubCommands 

5788 

5789 

5790class ScriptCommands(CommandsProtocol): 

5791 """ 

5792 Redis Lua script commands. see: 

5793 https://redis.io/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/ 

5794 """ 

5795 

5796 def _eval( 

5797 self, command: str, script: str, numkeys: int, *keys_and_args: str 

5798 ) -> Union[Awaitable[str], str]: 

5799 return self.execute_command(command, script, numkeys, *keys_and_args) 

5800 

5801 def eval( 

5802 self, script: str, numkeys: int, *keys_and_args: str 

5803 ) -> Union[Awaitable[str], str]: 

5804 """ 

5805 Execute the Lua ``script``, specifying the ``numkeys`` the script 

5806 will touch and the key names and argument values in ``keys_and_args``. 

5807 Returns the result of the script. 

5808 

5809 In practice, use the object returned by ``register_script``. This 

5810 function exists purely for Redis API completion. 

5811 

5812 For more information see https://redis.io/commands/eval 

5813 """ 

5814 return self._eval("EVAL", script, numkeys, *keys_and_args) 

5815 

5816 def eval_ro( 

5817 self, script: str, numkeys: int, *keys_and_args: str 

5818 ) -> Union[Awaitable[str], str]: 

5819 """ 

5820 The read-only variant of the EVAL command 

5821 

5822 Execute the read-only Lua ``script`` specifying the ``numkeys`` the script 

5823 will touch and the key names and argument values in ``keys_and_args``. 

5824 Returns the result of the script. 

5825 

5826 For more information see https://redis.io/commands/eval_ro 

5827 """ 

5828 return self._eval("EVAL_RO", script, numkeys, *keys_and_args) 

5829 

5830 def _evalsha( 

5831 self, command: str, sha: str, numkeys: int, *keys_and_args: list 

5832 ) -> Union[Awaitable[str], str]: 

5833 return self.execute_command(command, sha, numkeys, *keys_and_args) 

5834 

5835 def evalsha( 

5836 self, sha: str, numkeys: int, *keys_and_args: str 

5837 ) -> Union[Awaitable[str], str]: 

5838 """ 

5839 Use the ``sha`` to execute a Lua script already registered via EVAL 

5840 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

5841 key names and argument values in ``keys_and_args``. Returns the result 

5842 of the script. 

5843 

5844 In practice, use the object returned by ``register_script``. This 

5845 function exists purely for Redis API completion. 

5846 

5847 For more information see https://redis.io/commands/evalsha 

5848 """ 

5849 return self._evalsha("EVALSHA", sha, numkeys, *keys_and_args) 

5850 

5851 def evalsha_ro( 

5852 self, sha: str, numkeys: int, *keys_and_args: str 

5853 ) -> Union[Awaitable[str], str]: 

5854 """ 

5855 The read-only variant of the EVALSHA command 

5856 

5857 Use the ``sha`` to execute a read-only Lua script already registered via EVAL 

5858 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

5859 key names and argument values in ``keys_and_args``. Returns the result 

5860 of the script. 

5861 

5862 For more information see https://redis.io/commands/evalsha_ro 

5863 """ 

5864 return self._evalsha("EVALSHA_RO", sha, numkeys, *keys_and_args) 

5865 

5866 def script_exists(self, *args: str) -> ResponseT: 

5867 """ 

5868 Check if a script exists in the script cache by specifying the SHAs of 

5869 each script as ``args``. Returns a list of boolean values indicating if 

5870 if each already script exists in the cache_data. 

5871 

5872 For more information see https://redis.io/commands/script-exists 

5873 """ 

5874 return self.execute_command("SCRIPT EXISTS", *args) 

5875 

5876 def script_debug(self, *args) -> None: 

5877 raise NotImplementedError( 

5878 "SCRIPT DEBUG is intentionally not implemented in the client." 

5879 ) 

5880 

5881 def script_flush( 

5882 self, sync_type: Union[Literal["SYNC"], Literal["ASYNC"]] = None 

5883 ) -> ResponseT: 

5884 """Flush all scripts from the script cache_data. 

5885 

5886 ``sync_type`` is by default SYNC (synchronous) but it can also be 

5887 ASYNC. 

5888 

5889 For more information see https://redis.io/commands/script-flush 

5890 """ 

5891 

5892 # Redis pre 6 had no sync_type. 

5893 if sync_type not in ["SYNC", "ASYNC", None]: 

5894 raise DataError( 

5895 "SCRIPT FLUSH defaults to SYNC in redis > 6.2, or " 

5896 "accepts SYNC/ASYNC. For older versions, " 

5897 "of redis leave as None." 

5898 ) 

5899 if sync_type is None: 

5900 pieces = [] 

5901 else: 

5902 pieces = [sync_type] 

5903 return self.execute_command("SCRIPT FLUSH", *pieces) 

5904 

5905 def script_kill(self) -> ResponseT: 

5906 """ 

5907 Kill the currently executing Lua script 

5908 

5909 For more information see https://redis.io/commands/script-kill 

5910 """ 

5911 return self.execute_command("SCRIPT KILL") 

5912 

5913 def script_load(self, script: ScriptTextT) -> ResponseT: 

5914 """ 

5915 Load a Lua ``script`` into the script cache_data. Returns the SHA. 

5916 

5917 For more information see https://redis.io/commands/script-load 

5918 """ 

5919 return self.execute_command("SCRIPT LOAD", script) 

5920 

5921 def register_script(self: "redis.client.Redis", script: ScriptTextT) -> Script: 

5922 """ 

5923 Register a Lua ``script`` specifying the ``keys`` it will touch. 

5924 Returns a Script object that is callable and hides the complexity of 

5925 deal with scripts, keys, and shas. This is the preferred way to work 

5926 with Lua scripts. 

5927 """ 

5928 return Script(self, script) 

5929 

5930 

5931class AsyncScriptCommands(ScriptCommands): 

5932 async def script_debug(self, *args) -> None: 

5933 return super().script_debug() 

5934 

5935 def register_script( 

5936 self: "redis.asyncio.client.Redis", 

5937 script: ScriptTextT, 

5938 ) -> AsyncScript: 

5939 """ 

5940 Register a Lua ``script`` specifying the ``keys`` it will touch. 

5941 Returns a Script object that is callable and hides the complexity of 

5942 deal with scripts, keys, and shas. This is the preferred way to work 

5943 with Lua scripts. 

5944 """ 

5945 return AsyncScript(self, script) 

5946 

5947 

5948class GeoCommands(CommandsProtocol): 

5949 """ 

5950 Redis Geospatial commands. 

5951 see: https://redis.com/redis-best-practices/indexing-patterns/geospatial/ 

5952 """ 

5953 

5954 def geoadd( 

5955 self, 

5956 name: KeyT, 

5957 values: Sequence[EncodableT], 

5958 nx: bool = False, 

5959 xx: bool = False, 

5960 ch: bool = False, 

5961 ) -> ResponseT: 

5962 """ 

5963 Add the specified geospatial items to the specified key identified 

5964 by the ``name`` argument. The Geospatial items are given as ordered 

5965 members of the ``values`` argument, each item or place is formed by 

5966 the triad longitude, latitude and name. 

5967 

5968 Note: You can use ZREM to remove elements. 

5969 

5970 ``nx`` forces ZADD to only create new elements and not to update 

5971 scores for elements that already exist. 

5972 

5973 ``xx`` forces ZADD to only update scores of elements that already 

5974 exist. New elements will not be added. 

5975 

5976 ``ch`` modifies the return value to be the numbers of elements changed. 

5977 Changed elements include new elements that were added and elements 

5978 whose scores changed. 

5979 

5980 For more information see https://redis.io/commands/geoadd 

5981 """ 

5982 if nx and xx: 

5983 raise DataError("GEOADD allows either 'nx' or 'xx', not both") 

5984 if len(values) % 3 != 0: 

5985 raise DataError("GEOADD requires places with lon, lat and name values") 

5986 pieces = [name] 

5987 if nx: 

5988 pieces.append("NX") 

5989 if xx: 

5990 pieces.append("XX") 

5991 if ch: 

5992 pieces.append("CH") 

5993 pieces.extend(values) 

5994 return self.execute_command("GEOADD", *pieces) 

5995 

5996 def geodist( 

5997 self, name: KeyT, place1: FieldT, place2: FieldT, unit: Optional[str] = None 

5998 ) -> ResponseT: 

5999 """ 

6000 Return the distance between ``place1`` and ``place2`` members of the 

6001 ``name`` key. 

6002 The units must be one of the following : m, km mi, ft. By default 

6003 meters are used. 

6004 

6005 For more information see https://redis.io/commands/geodist 

6006 """ 

6007 pieces: list[EncodableT] = [name, place1, place2] 

6008 if unit and unit not in ("m", "km", "mi", "ft"): 

6009 raise DataError("GEODIST invalid unit") 

6010 elif unit: 

6011 pieces.append(unit) 

6012 return self.execute_command("GEODIST", *pieces, keys=[name]) 

6013 

6014 def geohash(self, name: KeyT, *values: FieldT) -> ResponseT: 

6015 """ 

6016 Return the geo hash string for each item of ``values`` members of 

6017 the specified key identified by the ``name`` argument. 

6018 

6019 For more information see https://redis.io/commands/geohash 

6020 """ 

6021 return self.execute_command("GEOHASH", name, *values, keys=[name]) 

6022 

6023 def geopos(self, name: KeyT, *values: FieldT) -> ResponseT: 

6024 """ 

6025 Return the positions of each item of ``values`` as members of 

6026 the specified key identified by the ``name`` argument. Each position 

6027 is represented by the pairs lon and lat. 

6028 

6029 For more information see https://redis.io/commands/geopos 

6030 """ 

6031 return self.execute_command("GEOPOS", name, *values, keys=[name]) 

6032 

6033 def georadius( 

6034 self, 

6035 name: KeyT, 

6036 longitude: float, 

6037 latitude: float, 

6038 radius: float, 

6039 unit: Optional[str] = None, 

6040 withdist: bool = False, 

6041 withcoord: bool = False, 

6042 withhash: bool = False, 

6043 count: Optional[int] = None, 

6044 sort: Optional[str] = None, 

6045 store: Optional[KeyT] = None, 

6046 store_dist: Optional[KeyT] = None, 

6047 any: bool = False, 

6048 ) -> ResponseT: 

6049 """ 

6050 Return the members of the specified key identified by the 

6051 ``name`` argument which are within the borders of the area specified 

6052 with the ``latitude`` and ``longitude`` location and the maximum 

6053 distance from the center specified by the ``radius`` value. 

6054 

6055 The units must be one of the following : m, km mi, ft. By default 

6056 

6057 ``withdist`` indicates to return the distances of each place. 

6058 

6059 ``withcoord`` indicates to return the latitude and longitude of 

6060 each place. 

6061 

6062 ``withhash`` indicates to return the geohash string of each place. 

6063 

6064 ``count`` indicates to return the number of elements up to N. 

6065 

6066 ``sort`` indicates to return the places in a sorted way, ASC for 

6067 nearest to fairest and DESC for fairest to nearest. 

6068 

6069 ``store`` indicates to save the places names in a sorted set named 

6070 with a specific key, each element of the destination sorted set is 

6071 populated with the score got from the original geo sorted set. 

6072 

6073 ``store_dist`` indicates to save the places names in a sorted set 

6074 named with a specific key, instead of ``store`` the sorted set 

6075 destination score is set with the distance. 

6076 

6077 For more information see https://redis.io/commands/georadius 

6078 """ 

6079 return self._georadiusgeneric( 

6080 "GEORADIUS", 

6081 name, 

6082 longitude, 

6083 latitude, 

6084 radius, 

6085 unit=unit, 

6086 withdist=withdist, 

6087 withcoord=withcoord, 

6088 withhash=withhash, 

6089 count=count, 

6090 sort=sort, 

6091 store=store, 

6092 store_dist=store_dist, 

6093 any=any, 

6094 ) 

6095 

6096 def georadiusbymember( 

6097 self, 

6098 name: KeyT, 

6099 member: FieldT, 

6100 radius: float, 

6101 unit: Optional[str] = None, 

6102 withdist: bool = False, 

6103 withcoord: bool = False, 

6104 withhash: bool = False, 

6105 count: Optional[int] = None, 

6106 sort: Optional[str] = None, 

6107 store: Union[KeyT, None] = None, 

6108 store_dist: Union[KeyT, None] = None, 

6109 any: bool = False, 

6110 ) -> ResponseT: 

6111 """ 

6112 This command is exactly like ``georadius`` with the sole difference 

6113 that instead of taking, as the center of the area to query, a longitude 

6114 and latitude value, it takes the name of a member already existing 

6115 inside the geospatial index represented by the sorted set. 

6116 

6117 For more information see https://redis.io/commands/georadiusbymember 

6118 """ 

6119 return self._georadiusgeneric( 

6120 "GEORADIUSBYMEMBER", 

6121 name, 

6122 member, 

6123 radius, 

6124 unit=unit, 

6125 withdist=withdist, 

6126 withcoord=withcoord, 

6127 withhash=withhash, 

6128 count=count, 

6129 sort=sort, 

6130 store=store, 

6131 store_dist=store_dist, 

6132 any=any, 

6133 ) 

6134 

6135 def _georadiusgeneric( 

6136 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

6137 ) -> ResponseT: 

6138 pieces = list(args) 

6139 if kwargs["unit"] and kwargs["unit"] not in ("m", "km", "mi", "ft"): 

6140 raise DataError("GEORADIUS invalid unit") 

6141 elif kwargs["unit"]: 

6142 pieces.append(kwargs["unit"]) 

6143 else: 

6144 pieces.append("m") 

6145 

6146 if kwargs["any"] and kwargs["count"] is None: 

6147 raise DataError("``any`` can't be provided without ``count``") 

6148 

6149 for arg_name, byte_repr in ( 

6150 ("withdist", "WITHDIST"), 

6151 ("withcoord", "WITHCOORD"), 

6152 ("withhash", "WITHHASH"), 

6153 ): 

6154 if kwargs[arg_name]: 

6155 pieces.append(byte_repr) 

6156 

6157 if kwargs["count"] is not None: 

6158 pieces.extend(["COUNT", kwargs["count"]]) 

6159 if kwargs["any"]: 

6160 pieces.append("ANY") 

6161 

6162 if kwargs["sort"]: 

6163 if kwargs["sort"] == "ASC": 

6164 pieces.append("ASC") 

6165 elif kwargs["sort"] == "DESC": 

6166 pieces.append("DESC") 

6167 else: 

6168 raise DataError("GEORADIUS invalid sort") 

6169 

6170 if kwargs["store"] and kwargs["store_dist"]: 

6171 raise DataError("GEORADIUS store and store_dist cant be set together") 

6172 

6173 if kwargs["store"]: 

6174 pieces.extend([b"STORE", kwargs["store"]]) 

6175 

6176 if kwargs["store_dist"]: 

6177 pieces.extend([b"STOREDIST", kwargs["store_dist"]]) 

6178 

6179 return self.execute_command(command, *pieces, **kwargs) 

6180 

6181 def geosearch( 

6182 self, 

6183 name: KeyT, 

6184 member: Union[FieldT, None] = None, 

6185 longitude: Union[float, None] = None, 

6186 latitude: Union[float, None] = None, 

6187 unit: str = "m", 

6188 radius: Union[float, None] = None, 

6189 width: Union[float, None] = None, 

6190 height: Union[float, None] = None, 

6191 sort: Optional[str] = None, 

6192 count: Optional[int] = None, 

6193 any: bool = False, 

6194 withcoord: bool = False, 

6195 withdist: bool = False, 

6196 withhash: bool = False, 

6197 ) -> ResponseT: 

6198 """ 

6199 Return the members of specified key identified by the 

6200 ``name`` argument, which are within the borders of the 

6201 area specified by a given shape. This command extends the 

6202 GEORADIUS command, so in addition to searching within circular 

6203 areas, it supports searching within rectangular areas. 

6204 

6205 This command should be used in place of the deprecated 

6206 GEORADIUS and GEORADIUSBYMEMBER commands. 

6207 

6208 ``member`` Use the position of the given existing 

6209 member in the sorted set. Can't be given with ``longitude`` 

6210 and ``latitude``. 

6211 

6212 ``longitude`` and ``latitude`` Use the position given by 

6213 this coordinates. Can't be given with ``member`` 

6214 ``radius`` Similar to GEORADIUS, search inside circular 

6215 area according the given radius. Can't be given with 

6216 ``height`` and ``width``. 

6217 ``height`` and ``width`` Search inside an axis-aligned 

6218 rectangle, determined by the given height and width. 

6219 Can't be given with ``radius`` 

6220 

6221 ``unit`` must be one of the following : m, km, mi, ft. 

6222 `m` for meters (the default value), `km` for kilometers, 

6223 `mi` for miles and `ft` for feet. 

6224 

6225 ``sort`` indicates to return the places in a sorted way, 

6226 ASC for nearest to furthest and DESC for furthest to nearest. 

6227 

6228 ``count`` limit the results to the first count matching items. 

6229 

6230 ``any`` is set to True, the command will return as soon as 

6231 enough matches are found. Can't be provided without ``count`` 

6232 

6233 ``withdist`` indicates to return the distances of each place. 

6234 ``withcoord`` indicates to return the latitude and longitude of 

6235 each place. 

6236 

6237 ``withhash`` indicates to return the geohash string of each place. 

6238 

6239 For more information see https://redis.io/commands/geosearch 

6240 """ 

6241 

6242 return self._geosearchgeneric( 

6243 "GEOSEARCH", 

6244 name, 

6245 member=member, 

6246 longitude=longitude, 

6247 latitude=latitude, 

6248 unit=unit, 

6249 radius=radius, 

6250 width=width, 

6251 height=height, 

6252 sort=sort, 

6253 count=count, 

6254 any=any, 

6255 withcoord=withcoord, 

6256 withdist=withdist, 

6257 withhash=withhash, 

6258 store=None, 

6259 store_dist=None, 

6260 ) 

6261 

6262 def geosearchstore( 

6263 self, 

6264 dest: KeyT, 

6265 name: KeyT, 

6266 member: Optional[FieldT] = None, 

6267 longitude: Optional[float] = None, 

6268 latitude: Optional[float] = None, 

6269 unit: str = "m", 

6270 radius: Optional[float] = None, 

6271 width: Optional[float] = None, 

6272 height: Optional[float] = None, 

6273 sort: Optional[str] = None, 

6274 count: Optional[int] = None, 

6275 any: bool = False, 

6276 storedist: bool = False, 

6277 ) -> ResponseT: 

6278 """ 

6279 This command is like GEOSEARCH, but stores the result in 

6280 ``dest``. By default, it stores the results in the destination 

6281 sorted set with their geospatial information. 

6282 if ``store_dist`` set to True, the command will stores the 

6283 items in a sorted set populated with their distance from the 

6284 center of the circle or box, as a floating-point number. 

6285 

6286 For more information see https://redis.io/commands/geosearchstore 

6287 """ 

6288 return self._geosearchgeneric( 

6289 "GEOSEARCHSTORE", 

6290 dest, 

6291 name, 

6292 member=member, 

6293 longitude=longitude, 

6294 latitude=latitude, 

6295 unit=unit, 

6296 radius=radius, 

6297 width=width, 

6298 height=height, 

6299 sort=sort, 

6300 count=count, 

6301 any=any, 

6302 withcoord=None, 

6303 withdist=None, 

6304 withhash=None, 

6305 store=None, 

6306 store_dist=storedist, 

6307 ) 

6308 

6309 def _geosearchgeneric( 

6310 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

6311 ) -> ResponseT: 

6312 pieces = list(args) 

6313 

6314 # FROMMEMBER or FROMLONLAT 

6315 if kwargs["member"] is None: 

6316 if kwargs["longitude"] is None or kwargs["latitude"] is None: 

6317 raise DataError("GEOSEARCH must have member or longitude and latitude") 

6318 if kwargs["member"]: 

6319 if kwargs["longitude"] or kwargs["latitude"]: 

6320 raise DataError( 

6321 "GEOSEARCH member and longitude or latitude cant be set together" 

6322 ) 

6323 pieces.extend([b"FROMMEMBER", kwargs["member"]]) 

6324 if kwargs["longitude"] is not None and kwargs["latitude"] is not None: 

6325 pieces.extend([b"FROMLONLAT", kwargs["longitude"], kwargs["latitude"]]) 

6326 

6327 # BYRADIUS or BYBOX 

6328 if kwargs["radius"] is None: 

6329 if kwargs["width"] is None or kwargs["height"] is None: 

6330 raise DataError("GEOSEARCH must have radius or width and height") 

6331 if kwargs["unit"] is None: 

6332 raise DataError("GEOSEARCH must have unit") 

6333 if kwargs["unit"].lower() not in ("m", "km", "mi", "ft"): 

6334 raise DataError("GEOSEARCH invalid unit") 

6335 if kwargs["radius"]: 

6336 if kwargs["width"] or kwargs["height"]: 

6337 raise DataError( 

6338 "GEOSEARCH radius and width or height cant be set together" 

6339 ) 

6340 pieces.extend([b"BYRADIUS", kwargs["radius"], kwargs["unit"]]) 

6341 if kwargs["width"] and kwargs["height"]: 

6342 pieces.extend([b"BYBOX", kwargs["width"], kwargs["height"], kwargs["unit"]]) 

6343 

6344 # sort 

6345 if kwargs["sort"]: 

6346 if kwargs["sort"].upper() == "ASC": 

6347 pieces.append(b"ASC") 

6348 elif kwargs["sort"].upper() == "DESC": 

6349 pieces.append(b"DESC") 

6350 else: 

6351 raise DataError("GEOSEARCH invalid sort") 

6352 

6353 # count any 

6354 if kwargs["count"]: 

6355 pieces.extend([b"COUNT", kwargs["count"]]) 

6356 if kwargs["any"]: 

6357 pieces.append(b"ANY") 

6358 elif kwargs["any"]: 

6359 raise DataError("GEOSEARCH ``any`` can't be provided without count") 

6360 

6361 # other properties 

6362 for arg_name, byte_repr in ( 

6363 ("withdist", b"WITHDIST"), 

6364 ("withcoord", b"WITHCOORD"), 

6365 ("withhash", b"WITHHASH"), 

6366 ("store_dist", b"STOREDIST"), 

6367 ): 

6368 if kwargs[arg_name]: 

6369 pieces.append(byte_repr) 

6370 

6371 kwargs["keys"] = [args[0] if command == "GEOSEARCH" else args[1]] 

6372 

6373 return self.execute_command(command, *pieces, **kwargs) 

6374 

6375 

6376AsyncGeoCommands = GeoCommands 

6377 

6378 

6379class ModuleCommands(CommandsProtocol): 

6380 """ 

6381 Redis Module commands. 

6382 see: https://redis.io/topics/modules-intro 

6383 """ 

6384 

6385 def module_load(self, path, *args) -> ResponseT: 

6386 """ 

6387 Loads the module from ``path``. 

6388 Passes all ``*args`` to the module, during loading. 

6389 Raises ``ModuleError`` if a module is not found at ``path``. 

6390 

6391 For more information see https://redis.io/commands/module-load 

6392 """ 

6393 return self.execute_command("MODULE LOAD", path, *args) 

6394 

6395 def module_loadex( 

6396 self, 

6397 path: str, 

6398 options: Optional[List[str]] = None, 

6399 args: Optional[List[str]] = None, 

6400 ) -> ResponseT: 

6401 """ 

6402 Loads a module from a dynamic library at runtime with configuration directives. 

6403 

6404 For more information see https://redis.io/commands/module-loadex 

6405 """ 

6406 pieces = [] 

6407 if options is not None: 

6408 pieces.append("CONFIG") 

6409 pieces.extend(options) 

6410 if args is not None: 

6411 pieces.append("ARGS") 

6412 pieces.extend(args) 

6413 

6414 return self.execute_command("MODULE LOADEX", path, *pieces) 

6415 

6416 def module_unload(self, name) -> ResponseT: 

6417 """ 

6418 Unloads the module ``name``. 

6419 Raises ``ModuleError`` if ``name`` is not in loaded modules. 

6420 

6421 For more information see https://redis.io/commands/module-unload 

6422 """ 

6423 return self.execute_command("MODULE UNLOAD", name) 

6424 

6425 def module_list(self) -> ResponseT: 

6426 """ 

6427 Returns a list of dictionaries containing the name and version of 

6428 all loaded modules. 

6429 

6430 For more information see https://redis.io/commands/module-list 

6431 """ 

6432 return self.execute_command("MODULE LIST") 

6433 

6434 def command_info(self) -> None: 

6435 raise NotImplementedError( 

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

6437 ) 

6438 

6439 def command_count(self) -> ResponseT: 

6440 return self.execute_command("COMMAND COUNT") 

6441 

6442 def command_getkeys(self, *args) -> ResponseT: 

6443 return self.execute_command("COMMAND GETKEYS", *args) 

6444 

6445 def command(self) -> ResponseT: 

6446 return self.execute_command("COMMAND") 

6447 

6448 

6449class AsyncModuleCommands(ModuleCommands): 

6450 async def command_info(self) -> None: 

6451 return super().command_info() 

6452 

6453 

6454class ClusterCommands(CommandsProtocol): 

6455 """ 

6456 Class for Redis Cluster commands 

6457 """ 

6458 

6459 def cluster(self, cluster_arg, *args, **kwargs) -> ResponseT: 

6460 return self.execute_command(f"CLUSTER {cluster_arg.upper()}", *args, **kwargs) 

6461 

6462 def readwrite(self, **kwargs) -> ResponseT: 

6463 """ 

6464 Disables read queries for a connection to a Redis Cluster slave node. 

6465 

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

6467 """ 

6468 return self.execute_command("READWRITE", **kwargs) 

6469 

6470 def readonly(self, **kwargs) -> ResponseT: 

6471 """ 

6472 Enables read queries for a connection to a Redis Cluster replica node. 

6473 

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

6475 """ 

6476 return self.execute_command("READONLY", **kwargs) 

6477 

6478 

6479AsyncClusterCommands = ClusterCommands 

6480 

6481 

6482class FunctionCommands: 

6483 """ 

6484 Redis Function commands 

6485 """ 

6486 

6487 def function_load( 

6488 self, code: str, replace: Optional[bool] = False 

6489 ) -> Union[Awaitable[str], str]: 

6490 """ 

6491 Load a library to Redis. 

6492 :param code: the source code (must start with 

6493 Shebang statement that provides a metadata about the library) 

6494 :param replace: changes the behavior to overwrite the existing library 

6495 with the new contents. 

6496 Return the library name that was loaded. 

6497 

6498 For more information see https://redis.io/commands/function-load 

6499 """ 

6500 pieces = ["REPLACE"] if replace else [] 

6501 pieces.append(code) 

6502 return self.execute_command("FUNCTION LOAD", *pieces) 

6503 

6504 def function_delete(self, library: str) -> Union[Awaitable[str], str]: 

6505 """ 

6506 Delete the library called ``library`` and all its functions. 

6507 

6508 For more information see https://redis.io/commands/function-delete 

6509 """ 

6510 return self.execute_command("FUNCTION DELETE", library) 

6511 

6512 def function_flush(self, mode: str = "SYNC") -> Union[Awaitable[str], str]: 

6513 """ 

6514 Deletes all the libraries. 

6515 

6516 For more information see https://redis.io/commands/function-flush 

6517 """ 

6518 return self.execute_command("FUNCTION FLUSH", mode) 

6519 

6520 def function_list( 

6521 self, library: Optional[str] = "*", withcode: Optional[bool] = False 

6522 ) -> Union[Awaitable[List], List]: 

6523 """ 

6524 Return information about the functions and libraries. 

6525 

6526 Args: 

6527 

6528 library: specify a pattern for matching library names 

6529 withcode: cause the server to include the libraries source implementation 

6530 in the reply 

6531 """ 

6532 args = ["LIBRARYNAME", library] 

6533 if withcode: 

6534 args.append("WITHCODE") 

6535 return self.execute_command("FUNCTION LIST", *args) 

6536 

6537 def _fcall( 

6538 self, command: str, function, numkeys: int, *keys_and_args: Any 

6539 ) -> Union[Awaitable[str], str]: 

6540 return self.execute_command(command, function, numkeys, *keys_and_args) 

6541 

6542 def fcall( 

6543 self, function, numkeys: int, *keys_and_args: Any 

6544 ) -> Union[Awaitable[str], str]: 

6545 """ 

6546 Invoke a function. 

6547 

6548 For more information see https://redis.io/commands/fcall 

6549 """ 

6550 return self._fcall("FCALL", function, numkeys, *keys_and_args) 

6551 

6552 def fcall_ro( 

6553 self, function, numkeys: int, *keys_and_args: Any 

6554 ) -> Union[Awaitable[str], str]: 

6555 """ 

6556 This is a read-only variant of the FCALL command that cannot 

6557 execute commands that modify data. 

6558 

6559 For more information see https://redis.io/commands/fcall_ro 

6560 """ 

6561 return self._fcall("FCALL_RO", function, numkeys, *keys_and_args) 

6562 

6563 def function_dump(self) -> Union[Awaitable[str], str]: 

6564 """ 

6565 Return the serialized payload of loaded libraries. 

6566 

6567 For more information see https://redis.io/commands/function-dump 

6568 """ 

6569 from redis.client import NEVER_DECODE 

6570 

6571 options = {} 

6572 options[NEVER_DECODE] = [] 

6573 

6574 return self.execute_command("FUNCTION DUMP", **options) 

6575 

6576 def function_restore( 

6577 self, payload: str, policy: Optional[str] = "APPEND" 

6578 ) -> Union[Awaitable[str], str]: 

6579 """ 

6580 Restore libraries from the serialized ``payload``. 

6581 You can use the optional policy argument to provide a policy 

6582 for handling existing libraries. 

6583 

6584 For more information see https://redis.io/commands/function-restore 

6585 """ 

6586 return self.execute_command("FUNCTION RESTORE", payload, policy) 

6587 

6588 def function_kill(self) -> Union[Awaitable[str], str]: 

6589 """ 

6590 Kill a function that is currently executing. 

6591 

6592 For more information see https://redis.io/commands/function-kill 

6593 """ 

6594 return self.execute_command("FUNCTION KILL") 

6595 

6596 def function_stats(self) -> Union[Awaitable[List], List]: 

6597 """ 

6598 Return information about the function that's currently running 

6599 and information about the available execution engines. 

6600 

6601 For more information see https://redis.io/commands/function-stats 

6602 """ 

6603 return self.execute_command("FUNCTION STATS") 

6604 

6605 

6606AsyncFunctionCommands = FunctionCommands 

6607 

6608 

6609class DataAccessCommands( 

6610 BasicKeyCommands, 

6611 HyperlogCommands, 

6612 HashCommands, 

6613 GeoCommands, 

6614 ListCommands, 

6615 ScanCommands, 

6616 SetCommands, 

6617 StreamCommands, 

6618 SortedSetCommands, 

6619): 

6620 """ 

6621 A class containing all of the implemented data access redis commands. 

6622 This class is to be used as a mixin for synchronous Redis clients. 

6623 """ 

6624 

6625 

6626class AsyncDataAccessCommands( 

6627 AsyncBasicKeyCommands, 

6628 AsyncHyperlogCommands, 

6629 AsyncHashCommands, 

6630 AsyncGeoCommands, 

6631 AsyncListCommands, 

6632 AsyncScanCommands, 

6633 AsyncSetCommands, 

6634 AsyncStreamCommands, 

6635 AsyncSortedSetCommands, 

6636): 

6637 """ 

6638 A class containing all of the implemented data access redis commands. 

6639 This class is to be used as a mixin for asynchronous Redis clients. 

6640 """ 

6641 

6642 

6643class CoreCommands( 

6644 ACLCommands, 

6645 ClusterCommands, 

6646 DataAccessCommands, 

6647 ManagementCommands, 

6648 ModuleCommands, 

6649 PubSubCommands, 

6650 ScriptCommands, 

6651 FunctionCommands, 

6652): 

6653 """ 

6654 A class containing all of the implemented redis commands. This class is 

6655 to be used as a mixin for synchronous Redis clients. 

6656 """ 

6657 

6658 

6659class AsyncCoreCommands( 

6660 AsyncACLCommands, 

6661 AsyncClusterCommands, 

6662 AsyncDataAccessCommands, 

6663 AsyncManagementCommands, 

6664 AsyncModuleCommands, 

6665 AsyncPubSubCommands, 

6666 AsyncScriptCommands, 

6667 AsyncFunctionCommands, 

6668): 

6669 """ 

6670 A class containing all of the implemented redis commands. This class is 

6671 to be used as a mixin for asynchronous Redis clients. 

6672 """