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

1914 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 default 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 function 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 function 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 context. 

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 xackdel( 

3488 self, 

3489 name: KeyT, 

3490 groupname: GroupT, 

3491 *ids: StreamIdT, 

3492 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF", 

3493 ) -> ResponseT: 

3494 """ 

3495 Combines the functionality of XACK and XDEL. Acknowledges the specified 

3496 message IDs in the given consumer group and simultaneously attempts to 

3497 delete the corresponding entries from the stream. 

3498 """ 

3499 if not ids: 

3500 raise DataError("XACKDEL requires at least one message ID") 

3501 

3502 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

3503 raise DataError("XACKDEL ref_policy must be one of: KEEPREF, DELREF, ACKED") 

3504 

3505 pieces = [name, groupname, ref_policy, "IDS", len(ids)] 

3506 pieces.extend(ids) 

3507 return self.execute_command("XACKDEL", *pieces) 

3508 

3509 def xadd( 

3510 self, 

3511 name: KeyT, 

3512 fields: Dict[FieldT, EncodableT], 

3513 id: StreamIdT = "*", 

3514 maxlen: Optional[int] = None, 

3515 approximate: bool = True, 

3516 nomkstream: bool = False, 

3517 minid: Union[StreamIdT, None] = None, 

3518 limit: Optional[int] = None, 

3519 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None, 

3520 ) -> ResponseT: 

3521 """ 

3522 Add to a stream. 

3523 name: name of the stream 

3524 fields: dict of field/value pairs to insert into the stream 

3525 id: Location to insert this record. By default it is appended. 

3526 maxlen: truncate old stream members beyond this size. 

3527 Can't be specified with minid. 

3528 approximate: actual stream length may be slightly more than maxlen 

3529 nomkstream: When set to true, do not make a stream 

3530 minid: the minimum id in the stream to query. 

3531 Can't be specified with maxlen. 

3532 limit: specifies the maximum number of entries to retrieve 

3533 ref_policy: optional reference policy for consumer groups when trimming: 

3534 - KEEPREF (default): When trimming, preserves references in consumer groups' PEL 

3535 - DELREF: When trimming, removes all references from consumer groups' PEL 

3536 - ACKED: When trimming, only removes entries acknowledged by all consumer groups 

3537 

3538 For more information, see https://redis.io/commands/xadd 

3539 """ 

3540 pieces: list[EncodableT] = [] 

3541 if maxlen is not None and minid is not None: 

3542 raise DataError("Only one of ```maxlen``` or ```minid``` may be specified") 

3543 

3544 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

3545 raise DataError("XADD ref_policy must be one of: KEEPREF, DELREF, ACKED") 

3546 

3547 if maxlen is not None: 

3548 if not isinstance(maxlen, int) or maxlen < 0: 

3549 raise DataError("XADD maxlen must be non-negative integer") 

3550 pieces.append(b"MAXLEN") 

3551 if approximate: 

3552 pieces.append(b"~") 

3553 pieces.append(str(maxlen)) 

3554 if minid is not None: 

3555 pieces.append(b"MINID") 

3556 if approximate: 

3557 pieces.append(b"~") 

3558 pieces.append(minid) 

3559 if limit is not None: 

3560 pieces.extend([b"LIMIT", limit]) 

3561 if nomkstream: 

3562 pieces.append(b"NOMKSTREAM") 

3563 if ref_policy is not None: 

3564 pieces.append(ref_policy) 

3565 pieces.append(id) 

3566 if not isinstance(fields, dict) or len(fields) == 0: 

3567 raise DataError("XADD fields must be a non-empty dict") 

3568 for pair in fields.items(): 

3569 pieces.extend(pair) 

3570 return self.execute_command("XADD", name, *pieces) 

3571 

3572 def xautoclaim( 

3573 self, 

3574 name: KeyT, 

3575 groupname: GroupT, 

3576 consumername: ConsumerT, 

3577 min_idle_time: int, 

3578 start_id: StreamIdT = "0-0", 

3579 count: Optional[int] = None, 

3580 justid: bool = False, 

3581 ) -> ResponseT: 

3582 """ 

3583 Transfers ownership of pending stream entries that match the specified 

3584 criteria. Conceptually, equivalent to calling XPENDING and then XCLAIM, 

3585 but provides a more straightforward way to deal with message delivery 

3586 failures via SCAN-like semantics. 

3587 name: name of the stream. 

3588 groupname: name of the consumer group. 

3589 consumername: name of a consumer that claims the message. 

3590 min_idle_time: filter messages that were idle less than this amount of 

3591 milliseconds. 

3592 start_id: filter messages with equal or greater ID. 

3593 count: optional integer, upper limit of the number of entries that the 

3594 command attempts to claim. Set to 100 by default. 

3595 justid: optional boolean, false by default. Return just an array of IDs 

3596 of messages successfully claimed, without returning the actual message 

3597 

3598 For more information, see https://redis.io/commands/xautoclaim 

3599 """ 

3600 try: 

3601 if int(min_idle_time) < 0: 

3602 raise DataError( 

3603 "XAUTOCLAIM min_idle_time must be a nonnegative integer" 

3604 ) 

3605 except TypeError: 

3606 pass 

3607 

3608 kwargs = {} 

3609 pieces = [name, groupname, consumername, min_idle_time, start_id] 

3610 

3611 try: 

3612 if int(count) < 0: 

3613 raise DataError("XPENDING count must be a integer >= 0") 

3614 pieces.extend([b"COUNT", count]) 

3615 except TypeError: 

3616 pass 

3617 if justid: 

3618 pieces.append(b"JUSTID") 

3619 kwargs["parse_justid"] = True 

3620 

3621 return self.execute_command("XAUTOCLAIM", *pieces, **kwargs) 

3622 

3623 def xclaim( 

3624 self, 

3625 name: KeyT, 

3626 groupname: GroupT, 

3627 consumername: ConsumerT, 

3628 min_idle_time: int, 

3629 message_ids: Union[List[StreamIdT], Tuple[StreamIdT]], 

3630 idle: Optional[int] = None, 

3631 time: Optional[int] = None, 

3632 retrycount: Optional[int] = None, 

3633 force: bool = False, 

3634 justid: bool = False, 

3635 ) -> ResponseT: 

3636 """ 

3637 Changes the ownership of a pending message. 

3638 

3639 name: name of the stream. 

3640 

3641 groupname: name of the consumer group. 

3642 

3643 consumername: name of a consumer that claims the message. 

3644 

3645 min_idle_time: filter messages that were idle less than this amount of 

3646 milliseconds 

3647 

3648 message_ids: non-empty list or tuple of message IDs to claim 

3649 

3650 idle: optional. Set the idle time (last time it was delivered) of the 

3651 message in ms 

3652 

3653 time: optional integer. This is the same as idle but instead of a 

3654 relative amount of milliseconds, it sets the idle time to a specific 

3655 Unix time (in milliseconds). 

3656 

3657 retrycount: optional integer. set the retry counter to the specified 

3658 value. This counter is incremented every time a message is delivered 

3659 again. 

3660 

3661 force: optional boolean, false by default. Creates the pending message 

3662 entry in the PEL even if certain specified IDs are not already in the 

3663 PEL assigned to a different client. 

3664 

3665 justid: optional boolean, false by default. Return just an array of IDs 

3666 of messages successfully claimed, without returning the actual message 

3667 

3668 For more information, see https://redis.io/commands/xclaim 

3669 """ 

3670 if not isinstance(min_idle_time, int) or min_idle_time < 0: 

3671 raise DataError("XCLAIM min_idle_time must be a non negative integer") 

3672 if not isinstance(message_ids, (list, tuple)) or not message_ids: 

3673 raise DataError( 

3674 "XCLAIM message_ids must be a non empty list or " 

3675 "tuple of message IDs to claim" 

3676 ) 

3677 

3678 kwargs = {} 

3679 pieces: list[EncodableT] = [name, groupname, consumername, str(min_idle_time)] 

3680 pieces.extend(list(message_ids)) 

3681 

3682 if idle is not None: 

3683 if not isinstance(idle, int): 

3684 raise DataError("XCLAIM idle must be an integer") 

3685 pieces.extend((b"IDLE", str(idle))) 

3686 if time is not None: 

3687 if not isinstance(time, int): 

3688 raise DataError("XCLAIM time must be an integer") 

3689 pieces.extend((b"TIME", str(time))) 

3690 if retrycount is not None: 

3691 if not isinstance(retrycount, int): 

3692 raise DataError("XCLAIM retrycount must be an integer") 

3693 pieces.extend((b"RETRYCOUNT", str(retrycount))) 

3694 

3695 if force: 

3696 if not isinstance(force, bool): 

3697 raise DataError("XCLAIM force must be a boolean") 

3698 pieces.append(b"FORCE") 

3699 if justid: 

3700 if not isinstance(justid, bool): 

3701 raise DataError("XCLAIM justid must be a boolean") 

3702 pieces.append(b"JUSTID") 

3703 kwargs["parse_justid"] = True 

3704 return self.execute_command("XCLAIM", *pieces, **kwargs) 

3705 

3706 def xdel(self, name: KeyT, *ids: StreamIdT) -> ResponseT: 

3707 """ 

3708 Deletes one or more messages from a stream. 

3709 

3710 Args: 

3711 name: name of the stream. 

3712 *ids: message ids to delete. 

3713 

3714 For more information, see https://redis.io/commands/xdel 

3715 """ 

3716 return self.execute_command("XDEL", name, *ids) 

3717 

3718 def xdelex( 

3719 self, 

3720 name: KeyT, 

3721 *ids: StreamIdT, 

3722 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF", 

3723 ) -> ResponseT: 

3724 """ 

3725 Extended version of XDEL that provides more control over how message entries 

3726 are deleted concerning consumer groups. 

3727 """ 

3728 if not ids: 

3729 raise DataError("XDELEX requires at least one message ID") 

3730 

3731 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

3732 raise DataError("XDELEX ref_policy must be one of: KEEPREF, DELREF, ACKED") 

3733 

3734 pieces = [name, ref_policy, "IDS", len(ids)] 

3735 pieces.extend(ids) 

3736 return self.execute_command("XDELEX", *pieces) 

3737 

3738 def xgroup_create( 

3739 self, 

3740 name: KeyT, 

3741 groupname: GroupT, 

3742 id: StreamIdT = "$", 

3743 mkstream: bool = False, 

3744 entries_read: Optional[int] = None, 

3745 ) -> ResponseT: 

3746 """ 

3747 Create a new consumer group associated with a stream. 

3748 name: name of the stream. 

3749 groupname: name of the consumer group. 

3750 id: ID of the last item in the stream to consider already delivered. 

3751 

3752 For more information, see https://redis.io/commands/xgroup-create 

3753 """ 

3754 pieces: list[EncodableT] = ["XGROUP CREATE", name, groupname, id] 

3755 if mkstream: 

3756 pieces.append(b"MKSTREAM") 

3757 if entries_read is not None: 

3758 pieces.extend(["ENTRIESREAD", entries_read]) 

3759 

3760 return self.execute_command(*pieces) 

3761 

3762 def xgroup_delconsumer( 

3763 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

3764 ) -> ResponseT: 

3765 """ 

3766 Remove a specific consumer from a consumer group. 

3767 Returns the number of pending messages that the consumer had before it 

3768 was deleted. 

3769 name: name of the stream. 

3770 groupname: name of the consumer group. 

3771 consumername: name of consumer to delete 

3772 

3773 For more information, see https://redis.io/commands/xgroup-delconsumer 

3774 """ 

3775 return self.execute_command("XGROUP DELCONSUMER", name, groupname, consumername) 

3776 

3777 def xgroup_destroy(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3778 """ 

3779 Destroy a consumer group. 

3780 name: name of the stream. 

3781 groupname: name of the consumer group. 

3782 

3783 For more information, see https://redis.io/commands/xgroup-destroy 

3784 """ 

3785 return self.execute_command("XGROUP DESTROY", name, groupname) 

3786 

3787 def xgroup_createconsumer( 

3788 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

3789 ) -> ResponseT: 

3790 """ 

3791 Consumers in a consumer group are auto-created every time a new 

3792 consumer name is mentioned by some command. 

3793 They can be explicitly created by using this command. 

3794 name: name of the stream. 

3795 groupname: name of the consumer group. 

3796 consumername: name of consumer to create. 

3797 

3798 See: https://redis.io/commands/xgroup-createconsumer 

3799 """ 

3800 return self.execute_command( 

3801 "XGROUP CREATECONSUMER", name, groupname, consumername 

3802 ) 

3803 

3804 def xgroup_setid( 

3805 self, 

3806 name: KeyT, 

3807 groupname: GroupT, 

3808 id: StreamIdT, 

3809 entries_read: Optional[int] = None, 

3810 ) -> ResponseT: 

3811 """ 

3812 Set the consumer group last delivered ID to something else. 

3813 name: name of the stream. 

3814 groupname: name of the consumer group. 

3815 id: ID of the last item in the stream to consider already delivered. 

3816 

3817 For more information, see https://redis.io/commands/xgroup-setid 

3818 """ 

3819 pieces = [name, groupname, id] 

3820 if entries_read is not None: 

3821 pieces.extend(["ENTRIESREAD", entries_read]) 

3822 return self.execute_command("XGROUP SETID", *pieces) 

3823 

3824 def xinfo_consumers(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3825 """ 

3826 Returns general information about the consumers in the group. 

3827 name: name of the stream. 

3828 groupname: name of the consumer group. 

3829 

3830 For more information, see https://redis.io/commands/xinfo-consumers 

3831 """ 

3832 return self.execute_command("XINFO CONSUMERS", name, groupname) 

3833 

3834 def xinfo_groups(self, name: KeyT) -> ResponseT: 

3835 """ 

3836 Returns general information about the consumer groups of the stream. 

3837 name: name of the stream. 

3838 

3839 For more information, see https://redis.io/commands/xinfo-groups 

3840 """ 

3841 return self.execute_command("XINFO GROUPS", name) 

3842 

3843 def xinfo_stream(self, name: KeyT, full: bool = False) -> ResponseT: 

3844 """ 

3845 Returns general information about the stream. 

3846 name: name of the stream. 

3847 full: optional boolean, false by default. Return full summary 

3848 

3849 For more information, see https://redis.io/commands/xinfo-stream 

3850 """ 

3851 pieces = [name] 

3852 options = {} 

3853 if full: 

3854 pieces.append(b"FULL") 

3855 options = {"full": full} 

3856 return self.execute_command("XINFO STREAM", *pieces, **options) 

3857 

3858 def xlen(self, name: KeyT) -> ResponseT: 

3859 """ 

3860 Returns the number of elements in a given stream. 

3861 

3862 For more information, see https://redis.io/commands/xlen 

3863 """ 

3864 return self.execute_command("XLEN", name, keys=[name]) 

3865 

3866 def xpending(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3867 """ 

3868 Returns information about pending messages of a group. 

3869 name: name of the stream. 

3870 groupname: name of the consumer group. 

3871 

3872 For more information, see https://redis.io/commands/xpending 

3873 """ 

3874 return self.execute_command("XPENDING", name, groupname, keys=[name]) 

3875 

3876 def xpending_range( 

3877 self, 

3878 name: KeyT, 

3879 groupname: GroupT, 

3880 min: StreamIdT, 

3881 max: StreamIdT, 

3882 count: int, 

3883 consumername: Union[ConsumerT, None] = None, 

3884 idle: Optional[int] = None, 

3885 ) -> ResponseT: 

3886 """ 

3887 Returns information about pending messages, in a range. 

3888 

3889 name: name of the stream. 

3890 groupname: name of the consumer group. 

3891 idle: available from version 6.2. filter entries by their 

3892 idle-time, given in milliseconds (optional). 

3893 min: minimum stream ID. 

3894 max: maximum stream ID. 

3895 count: number of messages to return 

3896 consumername: name of a consumer to filter by (optional). 

3897 """ 

3898 if {min, max, count} == {None}: 

3899 if idle is not None or consumername is not None: 

3900 raise DataError( 

3901 "if XPENDING is provided with idle time" 

3902 " or consumername, it must be provided" 

3903 " with min, max and count parameters" 

3904 ) 

3905 return self.xpending(name, groupname) 

3906 

3907 pieces = [name, groupname] 

3908 if min is None or max is None or count is None: 

3909 raise DataError( 

3910 "XPENDING must be provided with min, max " 

3911 "and count parameters, or none of them." 

3912 ) 

3913 # idle 

3914 try: 

3915 if int(idle) < 0: 

3916 raise DataError("XPENDING idle must be a integer >= 0") 

3917 pieces.extend(["IDLE", idle]) 

3918 except TypeError: 

3919 pass 

3920 # count 

3921 try: 

3922 if int(count) < 0: 

3923 raise DataError("XPENDING count must be a integer >= 0") 

3924 pieces.extend([min, max, count]) 

3925 except TypeError: 

3926 pass 

3927 # consumername 

3928 if consumername: 

3929 pieces.append(consumername) 

3930 

3931 return self.execute_command("XPENDING", *pieces, parse_detail=True) 

3932 

3933 def xrange( 

3934 self, 

3935 name: KeyT, 

3936 min: StreamIdT = "-", 

3937 max: StreamIdT = "+", 

3938 count: Optional[int] = None, 

3939 ) -> ResponseT: 

3940 """ 

3941 Read stream values within an interval. 

3942 

3943 name: name of the stream. 

3944 

3945 start: first stream ID. defaults to '-', 

3946 meaning the earliest available. 

3947 

3948 finish: last stream ID. defaults to '+', 

3949 meaning the latest available. 

3950 

3951 count: if set, only return this many items, beginning with the 

3952 earliest available. 

3953 

3954 For more information, see https://redis.io/commands/xrange 

3955 """ 

3956 pieces = [min, max] 

3957 if count is not None: 

3958 if not isinstance(count, int) or count < 1: 

3959 raise DataError("XRANGE count must be a positive integer") 

3960 pieces.append(b"COUNT") 

3961 pieces.append(str(count)) 

3962 

3963 return self.execute_command("XRANGE", name, *pieces, keys=[name]) 

3964 

3965 def xread( 

3966 self, 

3967 streams: Dict[KeyT, StreamIdT], 

3968 count: Optional[int] = None, 

3969 block: Optional[int] = None, 

3970 ) -> ResponseT: 

3971 """ 

3972 Block and monitor multiple streams for new data. 

3973 

3974 streams: a dict of stream names to stream IDs, where 

3975 IDs indicate the last ID already seen. 

3976 

3977 count: if set, only return this many items, beginning with the 

3978 earliest available. 

3979 

3980 block: number of milliseconds to wait, if nothing already present. 

3981 

3982 For more information, see https://redis.io/commands/xread 

3983 """ 

3984 pieces = [] 

3985 if block is not None: 

3986 if not isinstance(block, int) or block < 0: 

3987 raise DataError("XREAD block must be a non-negative integer") 

3988 pieces.append(b"BLOCK") 

3989 pieces.append(str(block)) 

3990 if count is not None: 

3991 if not isinstance(count, int) or count < 1: 

3992 raise DataError("XREAD count must be a positive integer") 

3993 pieces.append(b"COUNT") 

3994 pieces.append(str(count)) 

3995 if not isinstance(streams, dict) or len(streams) == 0: 

3996 raise DataError("XREAD streams must be a non empty dict") 

3997 pieces.append(b"STREAMS") 

3998 keys, values = zip(*streams.items()) 

3999 pieces.extend(keys) 

4000 pieces.extend(values) 

4001 return self.execute_command("XREAD", *pieces, keys=keys) 

4002 

4003 def xreadgroup( 

4004 self, 

4005 groupname: str, 

4006 consumername: str, 

4007 streams: Dict[KeyT, StreamIdT], 

4008 count: Optional[int] = None, 

4009 block: Optional[int] = None, 

4010 noack: bool = False, 

4011 ) -> ResponseT: 

4012 """ 

4013 Read from a stream via a consumer group. 

4014 

4015 groupname: name of the consumer group. 

4016 

4017 consumername: name of the requesting consumer. 

4018 

4019 streams: a dict of stream names to stream IDs, where 

4020 IDs indicate the last ID already seen. 

4021 

4022 count: if set, only return this many items, beginning with the 

4023 earliest available. 

4024 

4025 block: number of milliseconds to wait, if nothing already present. 

4026 noack: do not add messages to the PEL 

4027 

4028 For more information, see https://redis.io/commands/xreadgroup 

4029 """ 

4030 pieces: list[EncodableT] = [b"GROUP", groupname, consumername] 

4031 if count is not None: 

4032 if not isinstance(count, int) or count < 1: 

4033 raise DataError("XREADGROUP count must be a positive integer") 

4034 pieces.append(b"COUNT") 

4035 pieces.append(str(count)) 

4036 if block is not None: 

4037 if not isinstance(block, int) or block < 0: 

4038 raise DataError("XREADGROUP block must be a non-negative integer") 

4039 pieces.append(b"BLOCK") 

4040 pieces.append(str(block)) 

4041 if noack: 

4042 pieces.append(b"NOACK") 

4043 if not isinstance(streams, dict) or len(streams) == 0: 

4044 raise DataError("XREADGROUP streams must be a non empty dict") 

4045 pieces.append(b"STREAMS") 

4046 pieces.extend(streams.keys()) 

4047 pieces.extend(streams.values()) 

4048 return self.execute_command("XREADGROUP", *pieces) 

4049 

4050 def xrevrange( 

4051 self, 

4052 name: KeyT, 

4053 max: StreamIdT = "+", 

4054 min: StreamIdT = "-", 

4055 count: Optional[int] = None, 

4056 ) -> ResponseT: 

4057 """ 

4058 Read stream values within an interval, in reverse order. 

4059 

4060 name: name of the stream 

4061 

4062 start: first stream ID. defaults to '+', 

4063 meaning the latest available. 

4064 

4065 finish: last stream ID. defaults to '-', 

4066 meaning the earliest available. 

4067 

4068 count: if set, only return this many items, beginning with the 

4069 latest available. 

4070 

4071 For more information, see https://redis.io/commands/xrevrange 

4072 """ 

4073 pieces: list[EncodableT] = [max, min] 

4074 if count is not None: 

4075 if not isinstance(count, int) or count < 1: 

4076 raise DataError("XREVRANGE count must be a positive integer") 

4077 pieces.append(b"COUNT") 

4078 pieces.append(str(count)) 

4079 

4080 return self.execute_command("XREVRANGE", name, *pieces, keys=[name]) 

4081 

4082 def xtrim( 

4083 self, 

4084 name: KeyT, 

4085 maxlen: Optional[int] = None, 

4086 approximate: bool = True, 

4087 minid: Union[StreamIdT, None] = None, 

4088 limit: Optional[int] = None, 

4089 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None, 

4090 ) -> ResponseT: 

4091 """ 

4092 Trims old messages from a stream. 

4093 name: name of the stream. 

4094 maxlen: truncate old stream messages beyond this size 

4095 Can't be specified with minid. 

4096 approximate: actual stream length may be slightly more than maxlen 

4097 minid: the minimum id in the stream to query 

4098 Can't be specified with maxlen. 

4099 limit: specifies the maximum number of entries to retrieve 

4100 ref_policy: optional reference policy for consumer groups: 

4101 - KEEPREF (default): Trims entries but preserves references in consumer groups' PEL 

4102 - DELREF: Trims entries and removes all references from consumer groups' PEL 

4103 - ACKED: Only trims entries that were read and acknowledged by all consumer groups 

4104 

4105 For more information, see https://redis.io/commands/xtrim 

4106 """ 

4107 pieces: list[EncodableT] = [] 

4108 if maxlen is not None and minid is not None: 

4109 raise DataError("Only one of ``maxlen`` or ``minid`` may be specified") 

4110 

4111 if maxlen is None and minid is None: 

4112 raise DataError("One of ``maxlen`` or ``minid`` must be specified") 

4113 

4114 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

4115 raise DataError("XTRIM ref_policy must be one of: KEEPREF, DELREF, ACKED") 

4116 

4117 if maxlen is not None: 

4118 pieces.append(b"MAXLEN") 

4119 if minid is not None: 

4120 pieces.append(b"MINID") 

4121 if approximate: 

4122 pieces.append(b"~") 

4123 if maxlen is not None: 

4124 pieces.append(maxlen) 

4125 if minid is not None: 

4126 pieces.append(minid) 

4127 if limit is not None: 

4128 pieces.append(b"LIMIT") 

4129 pieces.append(limit) 

4130 if ref_policy is not None: 

4131 pieces.append(ref_policy) 

4132 

4133 return self.execute_command("XTRIM", name, *pieces) 

4134 

4135 

4136AsyncStreamCommands = StreamCommands 

4137 

4138 

4139class SortedSetCommands(CommandsProtocol): 

4140 """ 

4141 Redis commands for Sorted Sets data type. 

4142 see: https://redis.io/topics/data-types-intro#redis-sorted-sets 

4143 """ 

4144 

4145 def zadd( 

4146 self, 

4147 name: KeyT, 

4148 mapping: Mapping[AnyKeyT, EncodableT], 

4149 nx: bool = False, 

4150 xx: bool = False, 

4151 ch: bool = False, 

4152 incr: bool = False, 

4153 gt: bool = False, 

4154 lt: bool = False, 

4155 ) -> ResponseT: 

4156 """ 

4157 Set any number of element-name, score pairs to the key ``name``. Pairs 

4158 are specified as a dict of element-names keys to score values. 

4159 

4160 ``nx`` forces ZADD to only create new elements and not to update 

4161 scores for elements that already exist. 

4162 

4163 ``xx`` forces ZADD to only update scores of elements that already 

4164 exist. New elements will not be added. 

4165 

4166 ``ch`` modifies the return value to be the numbers of elements changed. 

4167 Changed elements include new elements that were added and elements 

4168 whose scores changed. 

4169 

4170 ``incr`` modifies ZADD to behave like ZINCRBY. In this mode only a 

4171 single element/score pair can be specified and the score is the amount 

4172 the existing score will be incremented by. When using this mode the 

4173 return value of ZADD will be the new score of the element. 

4174 

4175 ``lt`` only updates existing elements if the new score is less than 

4176 the current score. This flag doesn't prevent adding new elements. 

4177 

4178 ``gt`` only updates existing elements if the new score is greater than 

4179 the current score. This flag doesn't prevent adding new elements. 

4180 

4181 The return value of ZADD varies based on the mode specified. With no 

4182 options, ZADD returns the number of new elements added to the sorted 

4183 set. 

4184 

4185 ``nx``, ``lt``, and ``gt`` are mutually exclusive options. 

4186 

4187 See: https://redis.io/commands/ZADD 

4188 """ 

4189 if not mapping: 

4190 raise DataError("ZADD requires at least one element/score pair") 

4191 if nx and xx: 

4192 raise DataError("ZADD allows either 'nx' or 'xx', not both") 

4193 if gt and lt: 

4194 raise DataError("ZADD allows either 'gt' or 'lt', not both") 

4195 if incr and len(mapping) != 1: 

4196 raise DataError( 

4197 "ZADD option 'incr' only works when passing a single element/score pair" 

4198 ) 

4199 if nx and (gt or lt): 

4200 raise DataError("Only one of 'nx', 'lt', or 'gr' may be defined.") 

4201 

4202 pieces: list[EncodableT] = [] 

4203 options = {} 

4204 if nx: 

4205 pieces.append(b"NX") 

4206 if xx: 

4207 pieces.append(b"XX") 

4208 if ch: 

4209 pieces.append(b"CH") 

4210 if incr: 

4211 pieces.append(b"INCR") 

4212 options["as_score"] = True 

4213 if gt: 

4214 pieces.append(b"GT") 

4215 if lt: 

4216 pieces.append(b"LT") 

4217 for pair in mapping.items(): 

4218 pieces.append(pair[1]) 

4219 pieces.append(pair[0]) 

4220 return self.execute_command("ZADD", name, *pieces, **options) 

4221 

4222 def zcard(self, name: KeyT) -> ResponseT: 

4223 """ 

4224 Return the number of elements in the sorted set ``name`` 

4225 

4226 For more information, see https://redis.io/commands/zcard 

4227 """ 

4228 return self.execute_command("ZCARD", name, keys=[name]) 

4229 

4230 def zcount(self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT) -> ResponseT: 

4231 """ 

4232 Returns the number of elements in the sorted set at key ``name`` with 

4233 a score between ``min`` and ``max``. 

4234 

4235 For more information, see https://redis.io/commands/zcount 

4236 """ 

4237 return self.execute_command("ZCOUNT", name, min, max, keys=[name]) 

4238 

4239 def zdiff(self, keys: KeysT, withscores: bool = False) -> ResponseT: 

4240 """ 

4241 Returns the difference between the first and all successive input 

4242 sorted sets provided in ``keys``. 

4243 

4244 For more information, see https://redis.io/commands/zdiff 

4245 """ 

4246 pieces = [len(keys), *keys] 

4247 if withscores: 

4248 pieces.append("WITHSCORES") 

4249 return self.execute_command("ZDIFF", *pieces, keys=keys) 

4250 

4251 def zdiffstore(self, dest: KeyT, keys: KeysT) -> ResponseT: 

4252 """ 

4253 Computes the difference between the first and all successive input 

4254 sorted sets provided in ``keys`` and stores the result in ``dest``. 

4255 

4256 For more information, see https://redis.io/commands/zdiffstore 

4257 """ 

4258 pieces = [len(keys), *keys] 

4259 return self.execute_command("ZDIFFSTORE", dest, *pieces) 

4260 

4261 def zincrby(self, name: KeyT, amount: float, value: EncodableT) -> ResponseT: 

4262 """ 

4263 Increment the score of ``value`` in sorted set ``name`` by ``amount`` 

4264 

4265 For more information, see https://redis.io/commands/zincrby 

4266 """ 

4267 return self.execute_command("ZINCRBY", name, amount, value) 

4268 

4269 def zinter( 

4270 self, keys: KeysT, aggregate: Optional[str] = None, withscores: bool = False 

4271 ) -> ResponseT: 

4272 """ 

4273 Return the intersect of multiple sorted sets specified by ``keys``. 

4274 With the ``aggregate`` option, it is possible to specify how the 

4275 results of the union are aggregated. This option defaults to SUM, 

4276 where the score of an element is summed across the inputs where it 

4277 exists. When this option is set to either MIN or MAX, the resulting 

4278 set will contain the minimum or maximum score of an element across 

4279 the inputs where it exists. 

4280 

4281 For more information, see https://redis.io/commands/zinter 

4282 """ 

4283 return self._zaggregate("ZINTER", None, keys, aggregate, withscores=withscores) 

4284 

4285 def zinterstore( 

4286 self, 

4287 dest: KeyT, 

4288 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4289 aggregate: Optional[str] = None, 

4290 ) -> ResponseT: 

4291 """ 

4292 Intersect multiple sorted sets specified by ``keys`` into a new 

4293 sorted set, ``dest``. Scores in the destination will be aggregated 

4294 based on the ``aggregate``. This option defaults to SUM, where the 

4295 score of an element is summed across the inputs where it exists. 

4296 When this option is set to either MIN or MAX, the resulting set will 

4297 contain the minimum or maximum score of an element across the inputs 

4298 where it exists. 

4299 

4300 For more information, see https://redis.io/commands/zinterstore 

4301 """ 

4302 return self._zaggregate("ZINTERSTORE", dest, keys, aggregate) 

4303 

4304 def zintercard( 

4305 self, numkeys: int, keys: List[str], limit: int = 0 

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

4307 """ 

4308 Return the cardinality of the intersect of multiple sorted sets 

4309 specified by ``keys``. 

4310 When LIMIT provided (defaults to 0 and means unlimited), if the intersection 

4311 cardinality reaches limit partway through the computation, the algorithm will 

4312 exit and yield limit as the cardinality 

4313 

4314 For more information, see https://redis.io/commands/zintercard 

4315 """ 

4316 args = [numkeys, *keys, "LIMIT", limit] 

4317 return self.execute_command("ZINTERCARD", *args, keys=keys) 

4318 

4319 def zlexcount(self, name, min, max): 

4320 """ 

4321 Return the number of items in the sorted set ``name`` between the 

4322 lexicographical range ``min`` and ``max``. 

4323 

4324 For more information, see https://redis.io/commands/zlexcount 

4325 """ 

4326 return self.execute_command("ZLEXCOUNT", name, min, max, keys=[name]) 

4327 

4328 def zpopmax(self, name: KeyT, count: Optional[int] = None) -> ResponseT: 

4329 """ 

4330 Remove and return up to ``count`` members with the highest scores 

4331 from the sorted set ``name``. 

4332 

4333 For more information, see https://redis.io/commands/zpopmax 

4334 """ 

4335 args = (count is not None) and [count] or [] 

4336 options = {"withscores": True} 

4337 return self.execute_command("ZPOPMAX", name, *args, **options) 

4338 

4339 def zpopmin(self, name: KeyT, count: Optional[int] = None) -> ResponseT: 

4340 """ 

4341 Remove and return up to ``count`` members with the lowest scores 

4342 from the sorted set ``name``. 

4343 

4344 For more information, see https://redis.io/commands/zpopmin 

4345 """ 

4346 args = (count is not None) and [count] or [] 

4347 options = {"withscores": True} 

4348 return self.execute_command("ZPOPMIN", name, *args, **options) 

4349 

4350 def zrandmember( 

4351 self, key: KeyT, count: Optional[int] = None, withscores: bool = False 

4352 ) -> ResponseT: 

4353 """ 

4354 Return a random element from the sorted set value stored at key. 

4355 

4356 ``count`` if the argument is positive, return an array of distinct 

4357 fields. If called with a negative count, the behavior changes and 

4358 the command is allowed to return the same field multiple times. 

4359 In this case, the number of returned fields is the absolute value 

4360 of the specified count. 

4361 

4362 ``withscores`` The optional WITHSCORES modifier changes the reply so it 

4363 includes the respective scores of the randomly selected elements from 

4364 the sorted set. 

4365 

4366 For more information, see https://redis.io/commands/zrandmember 

4367 """ 

4368 params = [] 

4369 if count is not None: 

4370 params.append(count) 

4371 if withscores: 

4372 params.append("WITHSCORES") 

4373 

4374 return self.execute_command("ZRANDMEMBER", key, *params) 

4375 

4376 def bzpopmax(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4377 """ 

4378 ZPOPMAX a value off of the first non-empty sorted set 

4379 named in the ``keys`` list. 

4380 

4381 If none of the sorted sets in ``keys`` has a value to ZPOPMAX, 

4382 then block for ``timeout`` seconds, or until a member gets added 

4383 to one of the sorted sets. 

4384 

4385 If timeout is 0, then block indefinitely. 

4386 

4387 For more information, see https://redis.io/commands/bzpopmax 

4388 """ 

4389 if timeout is None: 

4390 timeout = 0 

4391 keys = list_or_args(keys, None) 

4392 keys.append(timeout) 

4393 return self.execute_command("BZPOPMAX", *keys) 

4394 

4395 def bzpopmin(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4396 """ 

4397 ZPOPMIN a value off of the first non-empty sorted set 

4398 named in the ``keys`` list. 

4399 

4400 If none of the sorted sets in ``keys`` has a value to ZPOPMIN, 

4401 then block for ``timeout`` seconds, or until a member gets added 

4402 to one of the sorted sets. 

4403 

4404 If timeout is 0, then block indefinitely. 

4405 

4406 For more information, see https://redis.io/commands/bzpopmin 

4407 """ 

4408 if timeout is None: 

4409 timeout = 0 

4410 keys: list[EncodableT] = list_or_args(keys, None) 

4411 keys.append(timeout) 

4412 return self.execute_command("BZPOPMIN", *keys) 

4413 

4414 def zmpop( 

4415 self, 

4416 num_keys: int, 

4417 keys: List[str], 

4418 min: Optional[bool] = False, 

4419 max: Optional[bool] = False, 

4420 count: Optional[int] = 1, 

4421 ) -> Union[Awaitable[list], list]: 

4422 """ 

4423 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4424 named in the ``keys`` list. 

4425 For more information, see https://redis.io/commands/zmpop 

4426 """ 

4427 args = [num_keys] + keys 

4428 if (min and max) or (not min and not max): 

4429 raise DataError 

4430 elif min: 

4431 args.append("MIN") 

4432 else: 

4433 args.append("MAX") 

4434 if count != 1: 

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

4436 

4437 return self.execute_command("ZMPOP", *args) 

4438 

4439 def bzmpop( 

4440 self, 

4441 timeout: float, 

4442 numkeys: int, 

4443 keys: List[str], 

4444 min: Optional[bool] = False, 

4445 max: Optional[bool] = False, 

4446 count: Optional[int] = 1, 

4447 ) -> Optional[list]: 

4448 """ 

4449 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4450 named in the ``keys`` list. 

4451 

4452 If none of the sorted sets in ``keys`` has a value to pop, 

4453 then block for ``timeout`` seconds, or until a member gets added 

4454 to one of the sorted sets. 

4455 

4456 If timeout is 0, then block indefinitely. 

4457 

4458 For more information, see https://redis.io/commands/bzmpop 

4459 """ 

4460 args = [timeout, numkeys, *keys] 

4461 if (min and max) or (not min and not max): 

4462 raise DataError("Either min or max, but not both must be set") 

4463 elif min: 

4464 args.append("MIN") 

4465 else: 

4466 args.append("MAX") 

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

4468 

4469 return self.execute_command("BZMPOP", *args) 

4470 

4471 def _zrange( 

4472 self, 

4473 command, 

4474 dest: Union[KeyT, None], 

4475 name: KeyT, 

4476 start: int, 

4477 end: int, 

4478 desc: bool = False, 

4479 byscore: bool = False, 

4480 bylex: bool = False, 

4481 withscores: bool = False, 

4482 score_cast_func: Union[type, Callable, None] = float, 

4483 offset: Optional[int] = None, 

4484 num: Optional[int] = None, 

4485 ) -> ResponseT: 

4486 if byscore and bylex: 

4487 raise DataError("``byscore`` and ``bylex`` can not be specified together.") 

4488 if (offset is not None and num is None) or (num is not None and offset is None): 

4489 raise DataError("``offset`` and ``num`` must both be specified.") 

4490 if bylex and withscores: 

4491 raise DataError( 

4492 "``withscores`` not supported in combination with ``bylex``." 

4493 ) 

4494 pieces = [command] 

4495 if dest: 

4496 pieces.append(dest) 

4497 pieces.extend([name, start, end]) 

4498 if byscore: 

4499 pieces.append("BYSCORE") 

4500 if bylex: 

4501 pieces.append("BYLEX") 

4502 if desc: 

4503 pieces.append("REV") 

4504 if offset is not None and num is not None: 

4505 pieces.extend(["LIMIT", offset, num]) 

4506 if withscores: 

4507 pieces.append("WITHSCORES") 

4508 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4509 options["keys"] = [name] 

4510 return self.execute_command(*pieces, **options) 

4511 

4512 def zrange( 

4513 self, 

4514 name: KeyT, 

4515 start: int, 

4516 end: int, 

4517 desc: bool = False, 

4518 withscores: bool = False, 

4519 score_cast_func: Union[type, Callable] = float, 

4520 byscore: bool = False, 

4521 bylex: bool = False, 

4522 offset: Optional[int] = None, 

4523 num: Optional[int] = None, 

4524 ) -> ResponseT: 

4525 """ 

4526 Return a range of values from sorted set ``name`` between 

4527 ``start`` and ``end`` sorted in ascending order. 

4528 

4529 ``start`` and ``end`` can be negative, indicating the end of the range. 

4530 

4531 ``desc`` a boolean indicating whether to sort the results in reversed 

4532 order. 

4533 

4534 ``withscores`` indicates to return the scores along with the values. 

4535 The return type is a list of (value, score) pairs. 

4536 

4537 ``score_cast_func`` a callable used to cast the score return value. 

4538 

4539 ``byscore`` when set to True, returns the range of elements from the 

4540 sorted set having scores equal or between ``start`` and ``end``. 

4541 

4542 ``bylex`` when set to True, returns the range of elements from the 

4543 sorted set between the ``start`` and ``end`` lexicographical closed 

4544 range intervals. 

4545 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

4546 whether the range interval is exclusive or inclusive, respectively. 

4547 

4548 ``offset`` and ``num`` are specified, then return a slice of the range. 

4549 Can't be provided when using ``bylex``. 

4550 

4551 For more information, see https://redis.io/commands/zrange 

4552 """ 

4553 # Need to support ``desc`` also when using old redis version 

4554 # because it was supported in 3.5.3 (of redis-py) 

4555 if not byscore and not bylex and (offset is None and num is None) and desc: 

4556 return self.zrevrange(name, start, end, withscores, score_cast_func) 

4557 

4558 return self._zrange( 

4559 "ZRANGE", 

4560 None, 

4561 name, 

4562 start, 

4563 end, 

4564 desc, 

4565 byscore, 

4566 bylex, 

4567 withscores, 

4568 score_cast_func, 

4569 offset, 

4570 num, 

4571 ) 

4572 

4573 def zrevrange( 

4574 self, 

4575 name: KeyT, 

4576 start: int, 

4577 end: int, 

4578 withscores: bool = False, 

4579 score_cast_func: Union[type, Callable] = float, 

4580 ) -> ResponseT: 

4581 """ 

4582 Return a range of values from sorted set ``name`` between 

4583 ``start`` and ``end`` sorted in descending order. 

4584 

4585 ``start`` and ``end`` can be negative, indicating the end of the range. 

4586 

4587 ``withscores`` indicates to return the scores along with the values 

4588 The return type is a list of (value, score) pairs 

4589 

4590 ``score_cast_func`` a callable used to cast the score return value 

4591 

4592 For more information, see https://redis.io/commands/zrevrange 

4593 """ 

4594 pieces = ["ZREVRANGE", name, start, end] 

4595 if withscores: 

4596 pieces.append(b"WITHSCORES") 

4597 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4598 options["keys"] = name 

4599 return self.execute_command(*pieces, **options) 

4600 

4601 def zrangestore( 

4602 self, 

4603 dest: KeyT, 

4604 name: KeyT, 

4605 start: int, 

4606 end: int, 

4607 byscore: bool = False, 

4608 bylex: bool = False, 

4609 desc: bool = False, 

4610 offset: Optional[int] = None, 

4611 num: Optional[int] = None, 

4612 ) -> ResponseT: 

4613 """ 

4614 Stores in ``dest`` the result of a range of values from sorted set 

4615 ``name`` between ``start`` and ``end`` sorted in ascending order. 

4616 

4617 ``start`` and ``end`` can be negative, indicating the end of the range. 

4618 

4619 ``byscore`` when set to True, returns the range of elements from the 

4620 sorted set having scores equal or between ``start`` and ``end``. 

4621 

4622 ``bylex`` when set to True, returns the range of elements from the 

4623 sorted set between the ``start`` and ``end`` lexicographical closed 

4624 range intervals. 

4625 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

4626 whether the range interval is exclusive or inclusive, respectively. 

4627 

4628 ``desc`` a boolean indicating whether to sort the results in reversed 

4629 order. 

4630 

4631 ``offset`` and ``num`` are specified, then return a slice of the range. 

4632 Can't be provided when using ``bylex``. 

4633 

4634 For more information, see https://redis.io/commands/zrangestore 

4635 """ 

4636 return self._zrange( 

4637 "ZRANGESTORE", 

4638 dest, 

4639 name, 

4640 start, 

4641 end, 

4642 desc, 

4643 byscore, 

4644 bylex, 

4645 False, 

4646 None, 

4647 offset, 

4648 num, 

4649 ) 

4650 

4651 def zrangebylex( 

4652 self, 

4653 name: KeyT, 

4654 min: EncodableT, 

4655 max: EncodableT, 

4656 start: Optional[int] = None, 

4657 num: Optional[int] = None, 

4658 ) -> ResponseT: 

4659 """ 

4660 Return the lexicographical range of values from sorted set ``name`` 

4661 between ``min`` and ``max``. 

4662 

4663 If ``start`` and ``num`` are specified, then return a slice of the 

4664 range. 

4665 

4666 For more information, see https://redis.io/commands/zrangebylex 

4667 """ 

4668 if (start is not None and num is None) or (num is not None and start is None): 

4669 raise DataError("``start`` and ``num`` must both be specified") 

4670 pieces = ["ZRANGEBYLEX", name, min, max] 

4671 if start is not None and num is not None: 

4672 pieces.extend([b"LIMIT", start, num]) 

4673 return self.execute_command(*pieces, keys=[name]) 

4674 

4675 def zrevrangebylex( 

4676 self, 

4677 name: KeyT, 

4678 max: EncodableT, 

4679 min: EncodableT, 

4680 start: Optional[int] = None, 

4681 num: Optional[int] = None, 

4682 ) -> ResponseT: 

4683 """ 

4684 Return the reversed lexicographical range of values from sorted set 

4685 ``name`` between ``max`` and ``min``. 

4686 

4687 If ``start`` and ``num`` are specified, then return a slice of the 

4688 range. 

4689 

4690 For more information, see https://redis.io/commands/zrevrangebylex 

4691 """ 

4692 if (start is not None and num is None) or (num is not None and start is None): 

4693 raise DataError("``start`` and ``num`` must both be specified") 

4694 pieces = ["ZREVRANGEBYLEX", name, max, min] 

4695 if start is not None and num is not None: 

4696 pieces.extend(["LIMIT", start, num]) 

4697 return self.execute_command(*pieces, keys=[name]) 

4698 

4699 def zrangebyscore( 

4700 self, 

4701 name: KeyT, 

4702 min: ZScoreBoundT, 

4703 max: ZScoreBoundT, 

4704 start: Optional[int] = None, 

4705 num: Optional[int] = None, 

4706 withscores: bool = False, 

4707 score_cast_func: Union[type, Callable] = float, 

4708 ) -> ResponseT: 

4709 """ 

4710 Return a range of values from the sorted set ``name`` with scores 

4711 between ``min`` and ``max``. 

4712 

4713 If ``start`` and ``num`` are specified, then return a slice 

4714 of the range. 

4715 

4716 ``withscores`` indicates to return the scores along with the values. 

4717 The return type is a list of (value, score) pairs 

4718 

4719 `score_cast_func`` a callable used to cast the score return value 

4720 

4721 For more information, see https://redis.io/commands/zrangebyscore 

4722 """ 

4723 if (start is not None and num is None) or (num is not None and start is None): 

4724 raise DataError("``start`` and ``num`` must both be specified") 

4725 pieces = ["ZRANGEBYSCORE", name, min, max] 

4726 if start is not None and num is not None: 

4727 pieces.extend(["LIMIT", start, num]) 

4728 if withscores: 

4729 pieces.append("WITHSCORES") 

4730 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4731 options["keys"] = [name] 

4732 return self.execute_command(*pieces, **options) 

4733 

4734 def zrevrangebyscore( 

4735 self, 

4736 name: KeyT, 

4737 max: ZScoreBoundT, 

4738 min: ZScoreBoundT, 

4739 start: Optional[int] = None, 

4740 num: Optional[int] = None, 

4741 withscores: bool = False, 

4742 score_cast_func: Union[type, Callable] = float, 

4743 ): 

4744 """ 

4745 Return a range of values from the sorted set ``name`` with scores 

4746 between ``min`` and ``max`` in descending order. 

4747 

4748 If ``start`` and ``num`` are specified, then return a slice 

4749 of the range. 

4750 

4751 ``withscores`` indicates to return the scores along with the values. 

4752 The return type is a list of (value, score) pairs 

4753 

4754 ``score_cast_func`` a callable used to cast the score return value 

4755 

4756 For more information, see https://redis.io/commands/zrevrangebyscore 

4757 """ 

4758 if (start is not None and num is None) or (num is not None and start is None): 

4759 raise DataError("``start`` and ``num`` must both be specified") 

4760 pieces = ["ZREVRANGEBYSCORE", name, max, min] 

4761 if start is not None and num is not None: 

4762 pieces.extend(["LIMIT", start, num]) 

4763 if withscores: 

4764 pieces.append("WITHSCORES") 

4765 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4766 options["keys"] = [name] 

4767 return self.execute_command(*pieces, **options) 

4768 

4769 def zrank( 

4770 self, 

4771 name: KeyT, 

4772 value: EncodableT, 

4773 withscore: bool = False, 

4774 ) -> ResponseT: 

4775 """ 

4776 Returns a 0-based value indicating the rank of ``value`` in sorted set 

4777 ``name``. 

4778 The optional WITHSCORE argument supplements the command's 

4779 reply with the score of the element returned. 

4780 

4781 For more information, see https://redis.io/commands/zrank 

4782 """ 

4783 if withscore: 

4784 return self.execute_command("ZRANK", name, value, "WITHSCORE", keys=[name]) 

4785 return self.execute_command("ZRANK", name, value, keys=[name]) 

4786 

4787 def zrem(self, name: KeyT, *values: FieldT) -> ResponseT: 

4788 """ 

4789 Remove member ``values`` from sorted set ``name`` 

4790 

4791 For more information, see https://redis.io/commands/zrem 

4792 """ 

4793 return self.execute_command("ZREM", name, *values) 

4794 

4795 def zremrangebylex(self, name: KeyT, min: EncodableT, max: EncodableT) -> ResponseT: 

4796 """ 

4797 Remove all elements in the sorted set ``name`` between the 

4798 lexicographical range specified by ``min`` and ``max``. 

4799 

4800 Returns the number of elements removed. 

4801 

4802 For more information, see https://redis.io/commands/zremrangebylex 

4803 """ 

4804 return self.execute_command("ZREMRANGEBYLEX", name, min, max) 

4805 

4806 def zremrangebyrank(self, name: KeyT, min: int, max: int) -> ResponseT: 

4807 """ 

4808 Remove all elements in the sorted set ``name`` with ranks between 

4809 ``min`` and ``max``. Values are 0-based, ordered from smallest score 

4810 to largest. Values can be negative indicating the highest scores. 

4811 Returns the number of elements removed 

4812 

4813 For more information, see https://redis.io/commands/zremrangebyrank 

4814 """ 

4815 return self.execute_command("ZREMRANGEBYRANK", name, min, max) 

4816 

4817 def zremrangebyscore( 

4818 self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT 

4819 ) -> ResponseT: 

4820 """ 

4821 Remove all elements in the sorted set ``name`` with scores 

4822 between ``min`` and ``max``. Returns the number of elements removed. 

4823 

4824 For more information, see https://redis.io/commands/zremrangebyscore 

4825 """ 

4826 return self.execute_command("ZREMRANGEBYSCORE", name, min, max) 

4827 

4828 def zrevrank( 

4829 self, 

4830 name: KeyT, 

4831 value: EncodableT, 

4832 withscore: bool = False, 

4833 ) -> ResponseT: 

4834 """ 

4835 Returns a 0-based value indicating the descending rank of 

4836 ``value`` in sorted set ``name``. 

4837 The optional ``withscore`` argument supplements the command's 

4838 reply with the score of the element returned. 

4839 

4840 For more information, see https://redis.io/commands/zrevrank 

4841 """ 

4842 if withscore: 

4843 return self.execute_command( 

4844 "ZREVRANK", name, value, "WITHSCORE", keys=[name] 

4845 ) 

4846 return self.execute_command("ZREVRANK", name, value, keys=[name]) 

4847 

4848 def zscore(self, name: KeyT, value: EncodableT) -> ResponseT: 

4849 """ 

4850 Return the score of element ``value`` in sorted set ``name`` 

4851 

4852 For more information, see https://redis.io/commands/zscore 

4853 """ 

4854 return self.execute_command("ZSCORE", name, value, keys=[name]) 

4855 

4856 def zunion( 

4857 self, 

4858 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4859 aggregate: Optional[str] = None, 

4860 withscores: bool = False, 

4861 ) -> ResponseT: 

4862 """ 

4863 Return the union of multiple sorted sets specified by ``keys``. 

4864 ``keys`` can be provided as dictionary of keys and their weights. 

4865 Scores will be aggregated based on the ``aggregate``, or SUM if 

4866 none is provided. 

4867 

4868 For more information, see https://redis.io/commands/zunion 

4869 """ 

4870 return self._zaggregate("ZUNION", None, keys, aggregate, withscores=withscores) 

4871 

4872 def zunionstore( 

4873 self, 

4874 dest: KeyT, 

4875 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4876 aggregate: Optional[str] = None, 

4877 ) -> ResponseT: 

4878 """ 

4879 Union multiple sorted sets specified by ``keys`` into 

4880 a new sorted set, ``dest``. Scores in the destination will be 

4881 aggregated based on the ``aggregate``, or SUM if none is provided. 

4882 

4883 For more information, see https://redis.io/commands/zunionstore 

4884 """ 

4885 return self._zaggregate("ZUNIONSTORE", dest, keys, aggregate) 

4886 

4887 def zmscore(self, key: KeyT, members: List[str]) -> ResponseT: 

4888 """ 

4889 Returns the scores associated with the specified members 

4890 in the sorted set stored at key. 

4891 ``members`` should be a list of the member name. 

4892 Return type is a list of score. 

4893 If the member does not exist, a None will be returned 

4894 in corresponding position. 

4895 

4896 For more information, see https://redis.io/commands/zmscore 

4897 """ 

4898 if not members: 

4899 raise DataError("ZMSCORE members must be a non-empty list") 

4900 pieces = [key] + members 

4901 return self.execute_command("ZMSCORE", *pieces, keys=[key]) 

4902 

4903 def _zaggregate( 

4904 self, 

4905 command: str, 

4906 dest: Union[KeyT, None], 

4907 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4908 aggregate: Optional[str] = None, 

4909 **options, 

4910 ) -> ResponseT: 

4911 pieces: list[EncodableT] = [command] 

4912 if dest is not None: 

4913 pieces.append(dest) 

4914 pieces.append(len(keys)) 

4915 if isinstance(keys, dict): 

4916 keys, weights = keys.keys(), keys.values() 

4917 else: 

4918 weights = None 

4919 pieces.extend(keys) 

4920 if weights: 

4921 pieces.append(b"WEIGHTS") 

4922 pieces.extend(weights) 

4923 if aggregate: 

4924 if aggregate.upper() in ["SUM", "MIN", "MAX"]: 

4925 pieces.append(b"AGGREGATE") 

4926 pieces.append(aggregate) 

4927 else: 

4928 raise DataError("aggregate can be sum, min or max.") 

4929 if options.get("withscores", False): 

4930 pieces.append(b"WITHSCORES") 

4931 options["keys"] = keys 

4932 return self.execute_command(*pieces, **options) 

4933 

4934 

4935AsyncSortedSetCommands = SortedSetCommands 

4936 

4937 

4938class HyperlogCommands(CommandsProtocol): 

4939 """ 

4940 Redis commands of HyperLogLogs data type. 

4941 see: https://redis.io/topics/data-types-intro#hyperloglogs 

4942 """ 

4943 

4944 def pfadd(self, name: KeyT, *values: FieldT) -> ResponseT: 

4945 """ 

4946 Adds the specified elements to the specified HyperLogLog. 

4947 

4948 For more information, see https://redis.io/commands/pfadd 

4949 """ 

4950 return self.execute_command("PFADD", name, *values) 

4951 

4952 def pfcount(self, *sources: KeyT) -> ResponseT: 

4953 """ 

4954 Return the approximated cardinality of 

4955 the set observed by the HyperLogLog at key(s). 

4956 

4957 For more information, see https://redis.io/commands/pfcount 

4958 """ 

4959 return self.execute_command("PFCOUNT", *sources) 

4960 

4961 def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT: 

4962 """ 

4963 Merge N different HyperLogLogs into a single one. 

4964 

4965 For more information, see https://redis.io/commands/pfmerge 

4966 """ 

4967 return self.execute_command("PFMERGE", dest, *sources) 

4968 

4969 

4970AsyncHyperlogCommands = HyperlogCommands 

4971 

4972 

4973class HashDataPersistOptions(Enum): 

4974 # set the value for each provided key to each 

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

4976 FNX = "FNX" 

4977 

4978 # set the value for each provided key to each 

4979 # provided value only if all already exist. 

4980 FXX = "FXX" 

4981 

4982 

4983class HashCommands(CommandsProtocol): 

4984 """ 

4985 Redis commands for Hash data type. 

4986 see: https://redis.io/topics/data-types-intro#redis-hashes 

4987 """ 

4988 

4989 def hdel(self, name: str, *keys: str) -> Union[Awaitable[int], int]: 

4990 """ 

4991 Delete ``keys`` from hash ``name`` 

4992 

4993 For more information, see https://redis.io/commands/hdel 

4994 """ 

4995 return self.execute_command("HDEL", name, *keys) 

4996 

4997 def hexists(self, name: str, key: str) -> Union[Awaitable[bool], bool]: 

4998 """ 

4999 Returns a boolean indicating if ``key`` exists within hash ``name`` 

5000 

5001 For more information, see https://redis.io/commands/hexists 

5002 """ 

5003 return self.execute_command("HEXISTS", name, key, keys=[name]) 

5004 

5005 def hget( 

5006 self, name: str, key: str 

5007 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

5008 """ 

5009 Return the value of ``key`` within the hash ``name`` 

5010 

5011 For more information, see https://redis.io/commands/hget 

5012 """ 

5013 return self.execute_command("HGET", name, key, keys=[name]) 

5014 

5015 def hgetall(self, name: str) -> Union[Awaitable[dict], dict]: 

5016 """ 

5017 Return a Python dict of the hash's name/value pairs 

5018 

5019 For more information, see https://redis.io/commands/hgetall 

5020 """ 

5021 return self.execute_command("HGETALL", name, keys=[name]) 

5022 

5023 def hgetdel( 

5024 self, name: str, *keys: str 

5025 ) -> Union[ 

5026 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]] 

5027 ]: 

5028 """ 

5029 Return the value of ``key`` within the hash ``name`` and 

5030 delete the field in the hash. 

5031 This command is similar to HGET, except for the fact that it also deletes 

5032 the key on success from the hash with the provided ```name```. 

5033 

5034 Available since Redis 8.0 

5035 For more information, see https://redis.io/commands/hgetdel 

5036 """ 

5037 if len(keys) == 0: 

5038 raise DataError("'hgetdel' should have at least one key provided") 

5039 

5040 return self.execute_command("HGETDEL", name, "FIELDS", len(keys), *keys) 

5041 

5042 def hgetex( 

5043 self, 

5044 name: KeyT, 

5045 *keys: str, 

5046 ex: Optional[ExpiryT] = None, 

5047 px: Optional[ExpiryT] = None, 

5048 exat: Optional[AbsExpiryT] = None, 

5049 pxat: Optional[AbsExpiryT] = None, 

5050 persist: bool = False, 

5051 ) -> Union[ 

5052 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]] 

5053 ]: 

5054 """ 

5055 Return the values of ``key`` and ``keys`` within the hash ``name`` 

5056 and optionally set their expiration. 

5057 

5058 ``ex`` sets an expire flag on ``kyes`` for ``ex`` seconds. 

5059 

5060 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds. 

5061 

5062 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds, 

5063 specified in unix time. 

5064 

5065 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds, 

5066 specified in unix time. 

5067 

5068 ``persist`` remove the time to live associated with the ``keys``. 

5069 

5070 Available since Redis 8.0 

5071 For more information, see https://redis.io/commands/hgetex 

5072 """ 

5073 if not keys: 

5074 raise DataError("'hgetex' should have at least one key provided") 

5075 

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

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

5078 raise DataError( 

5079 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

5081 ) 

5082 

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

5084 

5085 if persist: 

5086 exp_options.append("PERSIST") 

5087 

5088 return self.execute_command( 

5089 "HGETEX", 

5090 name, 

5091 *exp_options, 

5092 "FIELDS", 

5093 len(keys), 

5094 *keys, 

5095 ) 

5096 

5097 def hincrby( 

5098 self, name: str, key: str, amount: int = 1 

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

5100 """ 

5101 Increment the value of ``key`` in hash ``name`` by ``amount`` 

5102 

5103 For more information, see https://redis.io/commands/hincrby 

5104 """ 

5105 return self.execute_command("HINCRBY", name, key, amount) 

5106 

5107 def hincrbyfloat( 

5108 self, name: str, key: str, amount: float = 1.0 

5109 ) -> Union[Awaitable[float], float]: 

5110 """ 

5111 Increment the value of ``key`` in hash ``name`` by floating ``amount`` 

5112 

5113 For more information, see https://redis.io/commands/hincrbyfloat 

5114 """ 

5115 return self.execute_command("HINCRBYFLOAT", name, key, amount) 

5116 

5117 def hkeys(self, name: str) -> Union[Awaitable[List], List]: 

5118 """ 

5119 Return the list of keys within hash ``name`` 

5120 

5121 For more information, see https://redis.io/commands/hkeys 

5122 """ 

5123 return self.execute_command("HKEYS", name, keys=[name]) 

5124 

5125 def hlen(self, name: str) -> Union[Awaitable[int], int]: 

5126 """ 

5127 Return the number of elements in hash ``name`` 

5128 

5129 For more information, see https://redis.io/commands/hlen 

5130 """ 

5131 return self.execute_command("HLEN", name, keys=[name]) 

5132 

5133 def hset( 

5134 self, 

5135 name: str, 

5136 key: Optional[str] = None, 

5137 value: Optional[str] = None, 

5138 mapping: Optional[dict] = None, 

5139 items: Optional[list] = None, 

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

5141 """ 

5142 Set ``key`` to ``value`` within hash ``name``, 

5143 ``mapping`` accepts a dict of key/value pairs that will be 

5144 added to hash ``name``. 

5145 ``items`` accepts a list of key/value pairs that will be 

5146 added to hash ``name``. 

5147 Returns the number of fields that were added. 

5148 

5149 For more information, see https://redis.io/commands/hset 

5150 """ 

5151 

5152 if key is None and not mapping and not items: 

5153 raise DataError("'hset' with no key value pairs") 

5154 

5155 pieces = [] 

5156 if items: 

5157 pieces.extend(items) 

5158 if key is not None: 

5159 pieces.extend((key, value)) 

5160 if mapping: 

5161 for pair in mapping.items(): 

5162 pieces.extend(pair) 

5163 

5164 return self.execute_command("HSET", name, *pieces) 

5165 

5166 def hsetex( 

5167 self, 

5168 name: str, 

5169 key: Optional[str] = None, 

5170 value: Optional[str] = None, 

5171 mapping: Optional[dict] = None, 

5172 items: Optional[list] = None, 

5173 ex: Optional[ExpiryT] = None, 

5174 px: Optional[ExpiryT] = None, 

5175 exat: Optional[AbsExpiryT] = None, 

5176 pxat: Optional[AbsExpiryT] = None, 

5177 data_persist_option: Optional[HashDataPersistOptions] = None, 

5178 keepttl: bool = False, 

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

5180 """ 

5181 Set ``key`` to ``value`` within hash ``name`` 

5182 

5183 ``mapping`` accepts a dict of key/value pairs that will be 

5184 added to hash ``name``. 

5185 

5186 ``items`` accepts a list of key/value pairs that will be 

5187 added to hash ``name``. 

5188 

5189 ``ex`` sets an expire flag on ``keys`` for ``ex`` seconds. 

5190 

5191 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds. 

5192 

5193 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds, 

5194 specified in unix time. 

5195 

5196 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds, 

5197 specified in unix time. 

5198 

5199 ``data_persist_option`` can be set to ``FNX`` or ``FXX`` to control the 

5200 behavior of the command. 

5201 ``FNX`` will set the value for each provided key to each 

5202 provided value only if all do not already exist. 

5203 ``FXX`` will set the value for each provided key to each 

5204 provided value only if all already exist. 

5205 

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

5207 

5208 Returns the number of fields that were added. 

5209 

5210 Available since Redis 8.0 

5211 For more information, see https://redis.io/commands/hsetex 

5212 """ 

5213 if key is None and not mapping and not items: 

5214 raise DataError("'hsetex' with no key value pairs") 

5215 

5216 if items and len(items) % 2 != 0: 

5217 raise DataError( 

5218 "'hsetex' with odd number of items. " 

5219 "'items' must contain a list of key/value pairs." 

5220 ) 

5221 

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

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

5224 raise DataError( 

5225 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

5227 ) 

5228 

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

5230 if data_persist_option: 

5231 exp_options.append(data_persist_option.value) 

5232 

5233 if keepttl: 

5234 exp_options.append("KEEPTTL") 

5235 

5236 pieces = [] 

5237 if items: 

5238 pieces.extend(items) 

5239 if key is not None: 

5240 pieces.extend((key, value)) 

5241 if mapping: 

5242 for pair in mapping.items(): 

5243 pieces.extend(pair) 

5244 

5245 return self.execute_command( 

5246 "HSETEX", name, *exp_options, "FIELDS", int(len(pieces) / 2), *pieces 

5247 ) 

5248 

5249 def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool]: 

5250 """ 

5251 Set ``key`` to ``value`` within hash ``name`` if ``key`` does not 

5252 exist. Returns 1 if HSETNX created a field, otherwise 0. 

5253 

5254 For more information, see https://redis.io/commands/hsetnx 

5255 """ 

5256 return self.execute_command("HSETNX", name, key, value) 

5257 

5258 @deprecated_function( 

5259 version="4.0.0", 

5260 reason="Use 'hset' instead.", 

5261 name="hmset", 

5262 ) 

5263 def hmset(self, name: str, mapping: dict) -> Union[Awaitable[str], str]: 

5264 """ 

5265 Set key to value within hash ``name`` for each corresponding 

5266 key and value from the ``mapping`` dict. 

5267 

5268 For more information, see https://redis.io/commands/hmset 

5269 """ 

5270 if not mapping: 

5271 raise DataError("'hmset' with 'mapping' of length 0") 

5272 items = [] 

5273 for pair in mapping.items(): 

5274 items.extend(pair) 

5275 return self.execute_command("HMSET", name, *items) 

5276 

5277 def hmget(self, name: str, keys: List, *args: List) -> Union[Awaitable[List], List]: 

5278 """ 

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

5280 

5281 For more information, see https://redis.io/commands/hmget 

5282 """ 

5283 args = list_or_args(keys, args) 

5284 return self.execute_command("HMGET", name, *args, keys=[name]) 

5285 

5286 def hvals(self, name: str) -> Union[Awaitable[List], List]: 

5287 """ 

5288 Return the list of values within hash ``name`` 

5289 

5290 For more information, see https://redis.io/commands/hvals 

5291 """ 

5292 return self.execute_command("HVALS", name, keys=[name]) 

5293 

5294 def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]: 

5295 """ 

5296 Return the number of bytes stored in the value of ``key`` 

5297 within hash ``name`` 

5298 

5299 For more information, see https://redis.io/commands/hstrlen 

5300 """ 

5301 return self.execute_command("HSTRLEN", name, key, keys=[name]) 

5302 

5303 def hexpire( 

5304 self, 

5305 name: KeyT, 

5306 seconds: ExpiryT, 

5307 *fields: str, 

5308 nx: bool = False, 

5309 xx: bool = False, 

5310 gt: bool = False, 

5311 lt: bool = False, 

5312 ) -> ResponseT: 

5313 """ 

5314 Sets or updates the expiration time for fields within a hash key, using relative 

5315 time in seconds. 

5316 

5317 If a field already has an expiration time, the behavior of the update can be 

5318 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5319 

5320 The return value provides detailed information about the outcome for each field. 

5321 

5322 For more information, see https://redis.io/commands/hexpire 

5323 

5324 Args: 

5325 name: The name of the hash key. 

5326 seconds: Expiration time in seconds, relative. Can be an integer, or a 

5327 Python `timedelta` object. 

5328 fields: List of fields within the hash to apply the expiration time to. 

5329 nx: Set expiry only when the field has no expiry. 

5330 xx: Set expiry only when the field has an existing expiry. 

5331 gt: Set expiry only when the new expiry is greater than the current one. 

5332 lt: Set expiry only when the new expiry is less than the current one. 

5333 

5334 Returns: 

5335 Returns a list which contains for each field in the request: 

5336 - `-2` if the field does not exist, or if the key does not exist. 

5337 - `0` if the specified NX | XX | GT | LT condition was not met. 

5338 - `1` if the expiration time was set or updated. 

5339 - `2` if the field was deleted because the specified expiration time is 

5340 in the past. 

5341 """ 

5342 conditions = [nx, xx, gt, lt] 

5343 if sum(conditions) > 1: 

5344 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5345 

5346 if isinstance(seconds, datetime.timedelta): 

5347 seconds = int(seconds.total_seconds()) 

5348 

5349 options = [] 

5350 if nx: 

5351 options.append("NX") 

5352 if xx: 

5353 options.append("XX") 

5354 if gt: 

5355 options.append("GT") 

5356 if lt: 

5357 options.append("LT") 

5358 

5359 return self.execute_command( 

5360 "HEXPIRE", name, seconds, *options, "FIELDS", len(fields), *fields 

5361 ) 

5362 

5363 def hpexpire( 

5364 self, 

5365 name: KeyT, 

5366 milliseconds: ExpiryT, 

5367 *fields: str, 

5368 nx: bool = False, 

5369 xx: bool = False, 

5370 gt: bool = False, 

5371 lt: bool = False, 

5372 ) -> ResponseT: 

5373 """ 

5374 Sets or updates the expiration time for fields within a hash key, using relative 

5375 time in milliseconds. 

5376 

5377 If a field already has an expiration time, the behavior of the update can be 

5378 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5379 

5380 The return value provides detailed information about the outcome for each field. 

5381 

5382 For more information, see https://redis.io/commands/hpexpire 

5383 

5384 Args: 

5385 name: The name of the hash key. 

5386 milliseconds: Expiration time in milliseconds, relative. Can be an integer, 

5387 or a Python `timedelta` object. 

5388 fields: List of fields within the hash to apply the expiration time to. 

5389 nx: Set expiry only when the field has no expiry. 

5390 xx: Set expiry only when the field has an existing expiry. 

5391 gt: Set expiry only when the new expiry is greater than the current one. 

5392 lt: Set expiry only when the new expiry is less than the current one. 

5393 

5394 Returns: 

5395 Returns a list which contains for each field in the request: 

5396 - `-2` if the field does not exist, or if the key does not exist. 

5397 - `0` if the specified NX | XX | GT | LT condition was not met. 

5398 - `1` if the expiration time was set or updated. 

5399 - `2` if the field was deleted because the specified expiration time is 

5400 in the past. 

5401 """ 

5402 conditions = [nx, xx, gt, lt] 

5403 if sum(conditions) > 1: 

5404 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5405 

5406 if isinstance(milliseconds, datetime.timedelta): 

5407 milliseconds = int(milliseconds.total_seconds() * 1000) 

5408 

5409 options = [] 

5410 if nx: 

5411 options.append("NX") 

5412 if xx: 

5413 options.append("XX") 

5414 if gt: 

5415 options.append("GT") 

5416 if lt: 

5417 options.append("LT") 

5418 

5419 return self.execute_command( 

5420 "HPEXPIRE", name, milliseconds, *options, "FIELDS", len(fields), *fields 

5421 ) 

5422 

5423 def hexpireat( 

5424 self, 

5425 name: KeyT, 

5426 unix_time_seconds: AbsExpiryT, 

5427 *fields: str, 

5428 nx: bool = False, 

5429 xx: bool = False, 

5430 gt: bool = False, 

5431 lt: bool = False, 

5432 ) -> ResponseT: 

5433 """ 

5434 Sets or updates the expiration time for fields within a hash key, using an 

5435 absolute Unix timestamp in seconds. 

5436 

5437 If a field already has an expiration time, the behavior of the update can be 

5438 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5439 

5440 The return value provides detailed information about the outcome for each field. 

5441 

5442 For more information, see https://redis.io/commands/hexpireat 

5443 

5444 Args: 

5445 name: The name of the hash key. 

5446 unix_time_seconds: Expiration time as Unix timestamp in seconds. Can be an 

5447 integer or a Python `datetime` object. 

5448 fields: List of fields within the hash to apply the expiration time to. 

5449 nx: Set expiry only when the field has no expiry. 

5450 xx: Set expiry only when the field has an existing expiration time. 

5451 gt: Set expiry only when the new expiry is greater than the current one. 

5452 lt: Set expiry only when the new expiry is less than the current one. 

5453 

5454 Returns: 

5455 Returns a list which contains for each field in the request: 

5456 - `-2` if the field does not exist, or if the key does not exist. 

5457 - `0` if the specified NX | XX | GT | LT condition was not met. 

5458 - `1` if the expiration time was set or updated. 

5459 - `2` if the field was deleted because the specified expiration time is 

5460 in the past. 

5461 """ 

5462 conditions = [nx, xx, gt, lt] 

5463 if sum(conditions) > 1: 

5464 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5465 

5466 if isinstance(unix_time_seconds, datetime.datetime): 

5467 unix_time_seconds = int(unix_time_seconds.timestamp()) 

5468 

5469 options = [] 

5470 if nx: 

5471 options.append("NX") 

5472 if xx: 

5473 options.append("XX") 

5474 if gt: 

5475 options.append("GT") 

5476 if lt: 

5477 options.append("LT") 

5478 

5479 return self.execute_command( 

5480 "HEXPIREAT", 

5481 name, 

5482 unix_time_seconds, 

5483 *options, 

5484 "FIELDS", 

5485 len(fields), 

5486 *fields, 

5487 ) 

5488 

5489 def hpexpireat( 

5490 self, 

5491 name: KeyT, 

5492 unix_time_milliseconds: AbsExpiryT, 

5493 *fields: str, 

5494 nx: bool = False, 

5495 xx: bool = False, 

5496 gt: bool = False, 

5497 lt: bool = False, 

5498 ) -> ResponseT: 

5499 """ 

5500 Sets or updates the expiration time for fields within a hash key, using an 

5501 absolute Unix timestamp in milliseconds. 

5502 

5503 If a field already has an expiration time, the behavior of the update can be 

5504 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5505 

5506 The return value provides detailed information about the outcome for each field. 

5507 

5508 For more information, see https://redis.io/commands/hpexpireat 

5509 

5510 Args: 

5511 name: The name of the hash key. 

5512 unix_time_milliseconds: Expiration time as Unix timestamp in milliseconds. 

5513 Can be an integer or a Python `datetime` object. 

5514 fields: List of fields within the hash to apply the expiry. 

5515 nx: Set expiry only when the field has no expiry. 

5516 xx: Set expiry only when the field has an existing expiry. 

5517 gt: Set expiry only when the new expiry is greater than the current one. 

5518 lt: Set expiry only when the new expiry is less than the current one. 

5519 

5520 Returns: 

5521 Returns a list which contains for each field in the request: 

5522 - `-2` if the field does not exist, or if the key does not exist. 

5523 - `0` if the specified NX | XX | GT | LT condition was not met. 

5524 - `1` if the expiration time was set or updated. 

5525 - `2` if the field was deleted because the specified expiration time is 

5526 in the past. 

5527 """ 

5528 conditions = [nx, xx, gt, lt] 

5529 if sum(conditions) > 1: 

5530 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5531 

5532 if isinstance(unix_time_milliseconds, datetime.datetime): 

5533 unix_time_milliseconds = int(unix_time_milliseconds.timestamp() * 1000) 

5534 

5535 options = [] 

5536 if nx: 

5537 options.append("NX") 

5538 if xx: 

5539 options.append("XX") 

5540 if gt: 

5541 options.append("GT") 

5542 if lt: 

5543 options.append("LT") 

5544 

5545 return self.execute_command( 

5546 "HPEXPIREAT", 

5547 name, 

5548 unix_time_milliseconds, 

5549 *options, 

5550 "FIELDS", 

5551 len(fields), 

5552 *fields, 

5553 ) 

5554 

5555 def hpersist(self, name: KeyT, *fields: str) -> ResponseT: 

5556 """ 

5557 Removes the expiration time for each specified field in a hash. 

5558 

5559 For more information, see https://redis.io/commands/hpersist 

5560 

5561 Args: 

5562 name: The name of the hash key. 

5563 fields: A list of fields within the hash from which to remove the 

5564 expiration time. 

5565 

5566 Returns: 

5567 Returns a list which contains for each field in the request: 

5568 - `-2` if the field does not exist, or if the key does not exist. 

5569 - `-1` if the field exists but has no associated expiration time. 

5570 - `1` if the expiration time was successfully removed from the field. 

5571 """ 

5572 return self.execute_command("HPERSIST", name, "FIELDS", len(fields), *fields) 

5573 

5574 def hexpiretime(self, key: KeyT, *fields: str) -> ResponseT: 

5575 """ 

5576 Returns the expiration times of hash fields as Unix timestamps in seconds. 

5577 

5578 For more information, see https://redis.io/commands/hexpiretime 

5579 

5580 Args: 

5581 key: The hash key. 

5582 fields: A list of fields within the hash for which to get the expiration 

5583 time. 

5584 

5585 Returns: 

5586 Returns a list which contains for each field in the request: 

5587 - `-2` if the field does not exist, or if the key does not exist. 

5588 - `-1` if the field exists but has no associated expire time. 

5589 - A positive integer representing the expiration Unix timestamp in 

5590 seconds, if the field has an associated expiration time. 

5591 """ 

5592 return self.execute_command( 

5593 "HEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key] 

5594 ) 

5595 

5596 def hpexpiretime(self, key: KeyT, *fields: str) -> ResponseT: 

5597 """ 

5598 Returns the expiration times of hash fields as Unix timestamps in milliseconds. 

5599 

5600 For more information, see https://redis.io/commands/hpexpiretime 

5601 

5602 Args: 

5603 key: The hash key. 

5604 fields: A list of fields within the hash for which to get the expiration 

5605 time. 

5606 

5607 Returns: 

5608 Returns a list which contains for each field in the request: 

5609 - `-2` if the field does not exist, or if the key does not exist. 

5610 - `-1` if the field exists but has no associated expire time. 

5611 - A positive integer representing the expiration Unix timestamp in 

5612 milliseconds, if the field has an associated expiration time. 

5613 """ 

5614 return self.execute_command( 

5615 "HPEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key] 

5616 ) 

5617 

5618 def httl(self, key: KeyT, *fields: str) -> ResponseT: 

5619 """ 

5620 Returns the TTL (Time To Live) in seconds for each specified field within a hash 

5621 key. 

5622 

5623 For more information, see https://redis.io/commands/httl 

5624 

5625 Args: 

5626 key: The hash key. 

5627 fields: A list of fields within the hash for which to get the TTL. 

5628 

5629 Returns: 

5630 Returns a list which contains for each field in the request: 

5631 - `-2` if the field does not exist, or if the key does not exist. 

5632 - `-1` if the field exists but has no associated expire time. 

5633 - A positive integer representing the TTL in seconds if the field has 

5634 an associated expiration time. 

5635 """ 

5636 return self.execute_command( 

5637 "HTTL", key, "FIELDS", len(fields), *fields, keys=[key] 

5638 ) 

5639 

5640 def hpttl(self, key: KeyT, *fields: str) -> ResponseT: 

5641 """ 

5642 Returns the TTL (Time To Live) in milliseconds for each specified field within a 

5643 hash key. 

5644 

5645 For more information, see https://redis.io/commands/hpttl 

5646 

5647 Args: 

5648 key: The hash key. 

5649 fields: A list of fields within the hash for which to get the TTL. 

5650 

5651 Returns: 

5652 Returns a list which contains for each field in the request: 

5653 - `-2` if the field does not exist, or if the key does not exist. 

5654 - `-1` if the field exists but has no associated expire time. 

5655 - A positive integer representing the TTL in milliseconds if the field 

5656 has an associated expiration time. 

5657 """ 

5658 return self.execute_command( 

5659 "HPTTL", key, "FIELDS", len(fields), *fields, keys=[key] 

5660 ) 

5661 

5662 

5663AsyncHashCommands = HashCommands 

5664 

5665 

5666class Script: 

5667 """ 

5668 An executable Lua script object returned by ``register_script`` 

5669 """ 

5670 

5671 def __init__(self, registered_client: "redis.client.Redis", script: ScriptTextT): 

5672 self.registered_client = registered_client 

5673 self.script = script 

5674 # Precalculate and store the SHA1 hex digest of the script. 

5675 

5676 if isinstance(script, str): 

5677 # We need the encoding from the client in order to generate an 

5678 # accurate byte representation of the script 

5679 encoder = self.get_encoder() 

5680 script = encoder.encode(script) 

5681 self.sha = hashlib.sha1(script).hexdigest() 

5682 

5683 def __call__( 

5684 self, 

5685 keys: Union[Sequence[KeyT], None] = None, 

5686 args: Union[Iterable[EncodableT], None] = None, 

5687 client: Union["redis.client.Redis", None] = None, 

5688 ): 

5689 """Execute the script, passing any required ``args``""" 

5690 keys = keys or [] 

5691 args = args or [] 

5692 if client is None: 

5693 client = self.registered_client 

5694 args = tuple(keys) + tuple(args) 

5695 # make sure the Redis server knows about the script 

5696 from redis.client import Pipeline 

5697 

5698 if isinstance(client, Pipeline): 

5699 # Make sure the pipeline can register the script before executing. 

5700 client.scripts.add(self) 

5701 try: 

5702 return client.evalsha(self.sha, len(keys), *args) 

5703 except NoScriptError: 

5704 # Maybe the client is pointed to a different server than the client 

5705 # that created this instance? 

5706 # Overwrite the sha just in case there was a discrepancy. 

5707 self.sha = client.script_load(self.script) 

5708 return client.evalsha(self.sha, len(keys), *args) 

5709 

5710 def get_encoder(self): 

5711 """Get the encoder to encode string scripts into bytes.""" 

5712 try: 

5713 return self.registered_client.get_encoder() 

5714 except AttributeError: 

5715 # DEPRECATED 

5716 # In version <=4.1.2, this was the code we used to get the encoder. 

5717 # However, after 4.1.2 we added support for scripting in clustered 

5718 # redis. ClusteredRedis doesn't have a `.connection_pool` attribute 

5719 # so we changed the Script class to use 

5720 # `self.registered_client.get_encoder` (see above). 

5721 # However, that is technically a breaking change, as consumers who 

5722 # use Scripts directly might inject a `registered_client` that 

5723 # doesn't have a `.get_encoder` field. This try/except prevents us 

5724 # from breaking backward-compatibility. Ideally, it would be 

5725 # removed in the next major release. 

5726 return self.registered_client.connection_pool.get_encoder() 

5727 

5728 

5729class AsyncScript: 

5730 """ 

5731 An executable Lua script object returned by ``register_script`` 

5732 """ 

5733 

5734 def __init__( 

5735 self, 

5736 registered_client: "redis.asyncio.client.Redis", 

5737 script: ScriptTextT, 

5738 ): 

5739 self.registered_client = registered_client 

5740 self.script = script 

5741 # Precalculate and store the SHA1 hex digest of the script. 

5742 

5743 if isinstance(script, str): 

5744 # We need the encoding from the client in order to generate an 

5745 # accurate byte representation of the script 

5746 try: 

5747 encoder = registered_client.connection_pool.get_encoder() 

5748 except AttributeError: 

5749 # Cluster 

5750 encoder = registered_client.get_encoder() 

5751 script = encoder.encode(script) 

5752 self.sha = hashlib.sha1(script).hexdigest() 

5753 

5754 async def __call__( 

5755 self, 

5756 keys: Union[Sequence[KeyT], None] = None, 

5757 args: Union[Iterable[EncodableT], None] = None, 

5758 client: Union["redis.asyncio.client.Redis", None] = None, 

5759 ): 

5760 """Execute the script, passing any required ``args``""" 

5761 keys = keys or [] 

5762 args = args or [] 

5763 if client is None: 

5764 client = self.registered_client 

5765 args = tuple(keys) + tuple(args) 

5766 # make sure the Redis server knows about the script 

5767 from redis.asyncio.client import Pipeline 

5768 

5769 if isinstance(client, Pipeline): 

5770 # Make sure the pipeline can register the script before executing. 

5771 client.scripts.add(self) 

5772 try: 

5773 return await client.evalsha(self.sha, len(keys), *args) 

5774 except NoScriptError: 

5775 # Maybe the client is pointed to a different server than the client 

5776 # that created this instance? 

5777 # Overwrite the sha just in case there was a discrepancy. 

5778 self.sha = await client.script_load(self.script) 

5779 return await client.evalsha(self.sha, len(keys), *args) 

5780 

5781 

5782class PubSubCommands(CommandsProtocol): 

5783 """ 

5784 Redis PubSub commands. 

5785 see https://redis.io/topics/pubsub 

5786 """ 

5787 

5788 def publish(self, channel: ChannelT, message: EncodableT, **kwargs) -> ResponseT: 

5789 """ 

5790 Publish ``message`` on ``channel``. 

5791 Returns the number of subscribers the message was delivered to. 

5792 

5793 For more information, see https://redis.io/commands/publish 

5794 """ 

5795 return self.execute_command("PUBLISH", channel, message, **kwargs) 

5796 

5797 def spublish(self, shard_channel: ChannelT, message: EncodableT) -> ResponseT: 

5798 """ 

5799 Posts a message to the given shard channel. 

5800 Returns the number of clients that received the message 

5801 

5802 For more information, see https://redis.io/commands/spublish 

5803 """ 

5804 return self.execute_command("SPUBLISH", shard_channel, message) 

5805 

5806 def pubsub_channels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

5807 """ 

5808 Return a list of channels that have at least one subscriber 

5809 

5810 For more information, see https://redis.io/commands/pubsub-channels 

5811 """ 

5812 return self.execute_command("PUBSUB CHANNELS", pattern, **kwargs) 

5813 

5814 def pubsub_shardchannels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

5815 """ 

5816 Return a list of shard_channels that have at least one subscriber 

5817 

5818 For more information, see https://redis.io/commands/pubsub-shardchannels 

5819 """ 

5820 return self.execute_command("PUBSUB SHARDCHANNELS", pattern, **kwargs) 

5821 

5822 def pubsub_numpat(self, **kwargs) -> ResponseT: 

5823 """ 

5824 Returns the number of subscriptions to patterns 

5825 

5826 For more information, see https://redis.io/commands/pubsub-numpat 

5827 """ 

5828 return self.execute_command("PUBSUB NUMPAT", **kwargs) 

5829 

5830 def pubsub_numsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

5831 """ 

5832 Return a list of (channel, number of subscribers) tuples 

5833 for each channel given in ``*args`` 

5834 

5835 For more information, see https://redis.io/commands/pubsub-numsub 

5836 """ 

5837 return self.execute_command("PUBSUB NUMSUB", *args, **kwargs) 

5838 

5839 def pubsub_shardnumsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

5840 """ 

5841 Return a list of (shard_channel, number of subscribers) tuples 

5842 for each channel given in ``*args`` 

5843 

5844 For more information, see https://redis.io/commands/pubsub-shardnumsub 

5845 """ 

5846 return self.execute_command("PUBSUB SHARDNUMSUB", *args, **kwargs) 

5847 

5848 

5849AsyncPubSubCommands = PubSubCommands 

5850 

5851 

5852class ScriptCommands(CommandsProtocol): 

5853 """ 

5854 Redis Lua script commands. see: 

5855 https://redis.io/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/ 

5856 """ 

5857 

5858 def _eval( 

5859 self, command: str, script: str, numkeys: int, *keys_and_args: str 

5860 ) -> Union[Awaitable[str], str]: 

5861 return self.execute_command(command, script, numkeys, *keys_and_args) 

5862 

5863 def eval( 

5864 self, script: str, numkeys: int, *keys_and_args: str 

5865 ) -> Union[Awaitable[str], str]: 

5866 """ 

5867 Execute the Lua ``script``, specifying the ``numkeys`` the script 

5868 will touch and the key names and argument values in ``keys_and_args``. 

5869 Returns the result of the script. 

5870 

5871 In practice, use the object returned by ``register_script``. This 

5872 function exists purely for Redis API completion. 

5873 

5874 For more information, see https://redis.io/commands/eval 

5875 """ 

5876 return self._eval("EVAL", script, numkeys, *keys_and_args) 

5877 

5878 def eval_ro( 

5879 self, script: str, numkeys: int, *keys_and_args: str 

5880 ) -> Union[Awaitable[str], str]: 

5881 """ 

5882 The read-only variant of the EVAL command 

5883 

5884 Execute the read-only Lua ``script`` specifying the ``numkeys`` the script 

5885 will touch and the key names and argument values in ``keys_and_args``. 

5886 Returns the result of the script. 

5887 

5888 For more information, see https://redis.io/commands/eval_ro 

5889 """ 

5890 return self._eval("EVAL_RO", script, numkeys, *keys_and_args) 

5891 

5892 def _evalsha( 

5893 self, command: str, sha: str, numkeys: int, *keys_and_args: list 

5894 ) -> Union[Awaitable[str], str]: 

5895 return self.execute_command(command, sha, numkeys, *keys_and_args) 

5896 

5897 def evalsha( 

5898 self, sha: str, numkeys: int, *keys_and_args: str 

5899 ) -> Union[Awaitable[str], str]: 

5900 """ 

5901 Use the ``sha`` to execute a Lua script already registered via EVAL 

5902 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

5903 key names and argument values in ``keys_and_args``. Returns the result 

5904 of the script. 

5905 

5906 In practice, use the object returned by ``register_script``. This 

5907 function exists purely for Redis API completion. 

5908 

5909 For more information, see https://redis.io/commands/evalsha 

5910 """ 

5911 return self._evalsha("EVALSHA", sha, numkeys, *keys_and_args) 

5912 

5913 def evalsha_ro( 

5914 self, sha: str, numkeys: int, *keys_and_args: str 

5915 ) -> Union[Awaitable[str], str]: 

5916 """ 

5917 The read-only variant of the EVALSHA command 

5918 

5919 Use the ``sha`` to execute a read-only Lua script already registered via EVAL 

5920 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

5921 key names and argument values in ``keys_and_args``. Returns the result 

5922 of the script. 

5923 

5924 For more information, see https://redis.io/commands/evalsha_ro 

5925 """ 

5926 return self._evalsha("EVALSHA_RO", sha, numkeys, *keys_and_args) 

5927 

5928 def script_exists(self, *args: str) -> ResponseT: 

5929 """ 

5930 Check if a script exists in the script cache by specifying the SHAs of 

5931 each script as ``args``. Returns a list of boolean values indicating if 

5932 if each already script exists in the cache_data. 

5933 

5934 For more information, see https://redis.io/commands/script-exists 

5935 """ 

5936 return self.execute_command("SCRIPT EXISTS", *args) 

5937 

5938 def script_debug(self, *args) -> None: 

5939 raise NotImplementedError( 

5940 "SCRIPT DEBUG is intentionally not implemented in the client." 

5941 ) 

5942 

5943 def script_flush( 

5944 self, sync_type: Union[Literal["SYNC"], Literal["ASYNC"]] = None 

5945 ) -> ResponseT: 

5946 """Flush all scripts from the script cache_data. 

5947 

5948 ``sync_type`` is by default SYNC (synchronous) but it can also be 

5949 ASYNC. 

5950 

5951 For more information, see https://redis.io/commands/script-flush 

5952 """ 

5953 

5954 # Redis pre 6 had no sync_type. 

5955 if sync_type not in ["SYNC", "ASYNC", None]: 

5956 raise DataError( 

5957 "SCRIPT FLUSH defaults to SYNC in redis > 6.2, or " 

5958 "accepts SYNC/ASYNC. For older versions, " 

5959 "of redis leave as None." 

5960 ) 

5961 if sync_type is None: 

5962 pieces = [] 

5963 else: 

5964 pieces = [sync_type] 

5965 return self.execute_command("SCRIPT FLUSH", *pieces) 

5966 

5967 def script_kill(self) -> ResponseT: 

5968 """ 

5969 Kill the currently executing Lua script 

5970 

5971 For more information, see https://redis.io/commands/script-kill 

5972 """ 

5973 return self.execute_command("SCRIPT KILL") 

5974 

5975 def script_load(self, script: ScriptTextT) -> ResponseT: 

5976 """ 

5977 Load a Lua ``script`` into the script cache_data. Returns the SHA. 

5978 

5979 For more information, see https://redis.io/commands/script-load 

5980 """ 

5981 return self.execute_command("SCRIPT LOAD", script) 

5982 

5983 def register_script(self: "redis.client.Redis", script: ScriptTextT) -> Script: 

5984 """ 

5985 Register a Lua ``script`` specifying the ``keys`` it will touch. 

5986 Returns a Script object that is callable and hides the complexity of 

5987 deal with scripts, keys, and shas. This is the preferred way to work 

5988 with Lua scripts. 

5989 """ 

5990 return Script(self, script) 

5991 

5992 

5993class AsyncScriptCommands(ScriptCommands): 

5994 async def script_debug(self, *args) -> None: 

5995 return super().script_debug() 

5996 

5997 def register_script( 

5998 self: "redis.asyncio.client.Redis", 

5999 script: ScriptTextT, 

6000 ) -> AsyncScript: 

6001 """ 

6002 Register a Lua ``script`` specifying the ``keys`` it will touch. 

6003 Returns a Script object that is callable and hides the complexity of 

6004 deal with scripts, keys, and shas. This is the preferred way to work 

6005 with Lua scripts. 

6006 """ 

6007 return AsyncScript(self, script) 

6008 

6009 

6010class GeoCommands(CommandsProtocol): 

6011 """ 

6012 Redis Geospatial commands. 

6013 see: https://redis.com/redis-best-practices/indexing-patterns/geospatial/ 

6014 """ 

6015 

6016 def geoadd( 

6017 self, 

6018 name: KeyT, 

6019 values: Sequence[EncodableT], 

6020 nx: bool = False, 

6021 xx: bool = False, 

6022 ch: bool = False, 

6023 ) -> ResponseT: 

6024 """ 

6025 Add the specified geospatial items to the specified key identified 

6026 by the ``name`` argument. The Geospatial items are given as ordered 

6027 members of the ``values`` argument, each item or place is formed by 

6028 the triad longitude, latitude and name. 

6029 

6030 Note: You can use ZREM to remove elements. 

6031 

6032 ``nx`` forces ZADD to only create new elements and not to update 

6033 scores for elements that already exist. 

6034 

6035 ``xx`` forces ZADD to only update scores of elements that already 

6036 exist. New elements will not be added. 

6037 

6038 ``ch`` modifies the return value to be the numbers of elements changed. 

6039 Changed elements include new elements that were added and elements 

6040 whose scores changed. 

6041 

6042 For more information, see https://redis.io/commands/geoadd 

6043 """ 

6044 if nx and xx: 

6045 raise DataError("GEOADD allows either 'nx' or 'xx', not both") 

6046 if len(values) % 3 != 0: 

6047 raise DataError("GEOADD requires places with lon, lat and name values") 

6048 pieces = [name] 

6049 if nx: 

6050 pieces.append("NX") 

6051 if xx: 

6052 pieces.append("XX") 

6053 if ch: 

6054 pieces.append("CH") 

6055 pieces.extend(values) 

6056 return self.execute_command("GEOADD", *pieces) 

6057 

6058 def geodist( 

6059 self, name: KeyT, place1: FieldT, place2: FieldT, unit: Optional[str] = None 

6060 ) -> ResponseT: 

6061 """ 

6062 Return the distance between ``place1`` and ``place2`` members of the 

6063 ``name`` key. 

6064 The units must be one of the following : m, km mi, ft. By default 

6065 meters are used. 

6066 

6067 For more information, see https://redis.io/commands/geodist 

6068 """ 

6069 pieces: list[EncodableT] = [name, place1, place2] 

6070 if unit and unit not in ("m", "km", "mi", "ft"): 

6071 raise DataError("GEODIST invalid unit") 

6072 elif unit: 

6073 pieces.append(unit) 

6074 return self.execute_command("GEODIST", *pieces, keys=[name]) 

6075 

6076 def geohash(self, name: KeyT, *values: FieldT) -> ResponseT: 

6077 """ 

6078 Return the geo hash string for each item of ``values`` members of 

6079 the specified key identified by the ``name`` argument. 

6080 

6081 For more information, see https://redis.io/commands/geohash 

6082 """ 

6083 return self.execute_command("GEOHASH", name, *values, keys=[name]) 

6084 

6085 def geopos(self, name: KeyT, *values: FieldT) -> ResponseT: 

6086 """ 

6087 Return the positions of each item of ``values`` as members of 

6088 the specified key identified by the ``name`` argument. Each position 

6089 is represented by the pairs lon and lat. 

6090 

6091 For more information, see https://redis.io/commands/geopos 

6092 """ 

6093 return self.execute_command("GEOPOS", name, *values, keys=[name]) 

6094 

6095 def georadius( 

6096 self, 

6097 name: KeyT, 

6098 longitude: float, 

6099 latitude: float, 

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: Optional[KeyT] = None, 

6108 store_dist: Optional[KeyT] = None, 

6109 any: bool = False, 

6110 ) -> ResponseT: 

6111 """ 

6112 Return the members of the specified key identified by the 

6113 ``name`` argument which are within the borders of the area specified 

6114 with the ``latitude`` and ``longitude`` location and the maximum 

6115 distance from the center specified by the ``radius`` value. 

6116 

6117 The units must be one of the following : m, km mi, ft. By default 

6118 

6119 ``withdist`` indicates to return the distances of each place. 

6120 

6121 ``withcoord`` indicates to return the latitude and longitude of 

6122 each place. 

6123 

6124 ``withhash`` indicates to return the geohash string of each place. 

6125 

6126 ``count`` indicates to return the number of elements up to N. 

6127 

6128 ``sort`` indicates to return the places in a sorted way, ASC for 

6129 nearest to fairest and DESC for fairest to nearest. 

6130 

6131 ``store`` indicates to save the places names in a sorted set named 

6132 with a specific key, each element of the destination sorted set is 

6133 populated with the score got from the original geo sorted set. 

6134 

6135 ``store_dist`` indicates to save the places names in a sorted set 

6136 named with a specific key, instead of ``store`` the sorted set 

6137 destination score is set with the distance. 

6138 

6139 For more information, see https://redis.io/commands/georadius 

6140 """ 

6141 return self._georadiusgeneric( 

6142 "GEORADIUS", 

6143 name, 

6144 longitude, 

6145 latitude, 

6146 radius, 

6147 unit=unit, 

6148 withdist=withdist, 

6149 withcoord=withcoord, 

6150 withhash=withhash, 

6151 count=count, 

6152 sort=sort, 

6153 store=store, 

6154 store_dist=store_dist, 

6155 any=any, 

6156 ) 

6157 

6158 def georadiusbymember( 

6159 self, 

6160 name: KeyT, 

6161 member: FieldT, 

6162 radius: float, 

6163 unit: Optional[str] = None, 

6164 withdist: bool = False, 

6165 withcoord: bool = False, 

6166 withhash: bool = False, 

6167 count: Optional[int] = None, 

6168 sort: Optional[str] = None, 

6169 store: Union[KeyT, None] = None, 

6170 store_dist: Union[KeyT, None] = None, 

6171 any: bool = False, 

6172 ) -> ResponseT: 

6173 """ 

6174 This command is exactly like ``georadius`` with the sole difference 

6175 that instead of taking, as the center of the area to query, a longitude 

6176 and latitude value, it takes the name of a member already existing 

6177 inside the geospatial index represented by the sorted set. 

6178 

6179 For more information, see https://redis.io/commands/georadiusbymember 

6180 """ 

6181 return self._georadiusgeneric( 

6182 "GEORADIUSBYMEMBER", 

6183 name, 

6184 member, 

6185 radius, 

6186 unit=unit, 

6187 withdist=withdist, 

6188 withcoord=withcoord, 

6189 withhash=withhash, 

6190 count=count, 

6191 sort=sort, 

6192 store=store, 

6193 store_dist=store_dist, 

6194 any=any, 

6195 ) 

6196 

6197 def _georadiusgeneric( 

6198 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

6199 ) -> ResponseT: 

6200 pieces = list(args) 

6201 if kwargs["unit"] and kwargs["unit"] not in ("m", "km", "mi", "ft"): 

6202 raise DataError("GEORADIUS invalid unit") 

6203 elif kwargs["unit"]: 

6204 pieces.append(kwargs["unit"]) 

6205 else: 

6206 pieces.append("m") 

6207 

6208 if kwargs["any"] and kwargs["count"] is None: 

6209 raise DataError("``any`` can't be provided without ``count``") 

6210 

6211 for arg_name, byte_repr in ( 

6212 ("withdist", "WITHDIST"), 

6213 ("withcoord", "WITHCOORD"), 

6214 ("withhash", "WITHHASH"), 

6215 ): 

6216 if kwargs[arg_name]: 

6217 pieces.append(byte_repr) 

6218 

6219 if kwargs["count"] is not None: 

6220 pieces.extend(["COUNT", kwargs["count"]]) 

6221 if kwargs["any"]: 

6222 pieces.append("ANY") 

6223 

6224 if kwargs["sort"]: 

6225 if kwargs["sort"] == "ASC": 

6226 pieces.append("ASC") 

6227 elif kwargs["sort"] == "DESC": 

6228 pieces.append("DESC") 

6229 else: 

6230 raise DataError("GEORADIUS invalid sort") 

6231 

6232 if kwargs["store"] and kwargs["store_dist"]: 

6233 raise DataError("GEORADIUS store and store_dist cant be set together") 

6234 

6235 if kwargs["store"]: 

6236 pieces.extend([b"STORE", kwargs["store"]]) 

6237 

6238 if kwargs["store_dist"]: 

6239 pieces.extend([b"STOREDIST", kwargs["store_dist"]]) 

6240 

6241 return self.execute_command(command, *pieces, **kwargs) 

6242 

6243 def geosearch( 

6244 self, 

6245 name: KeyT, 

6246 member: Union[FieldT, None] = None, 

6247 longitude: Union[float, None] = None, 

6248 latitude: Union[float, None] = None, 

6249 unit: str = "m", 

6250 radius: Union[float, None] = None, 

6251 width: Union[float, None] = None, 

6252 height: Union[float, None] = None, 

6253 sort: Optional[str] = None, 

6254 count: Optional[int] = None, 

6255 any: bool = False, 

6256 withcoord: bool = False, 

6257 withdist: bool = False, 

6258 withhash: bool = False, 

6259 ) -> ResponseT: 

6260 """ 

6261 Return the members of specified key identified by the 

6262 ``name`` argument, which are within the borders of the 

6263 area specified by a given shape. This command extends the 

6264 GEORADIUS command, so in addition to searching within circular 

6265 areas, it supports searching within rectangular areas. 

6266 

6267 This command should be used in place of the deprecated 

6268 GEORADIUS and GEORADIUSBYMEMBER commands. 

6269 

6270 ``member`` Use the position of the given existing 

6271 member in the sorted set. Can't be given with ``longitude`` 

6272 and ``latitude``. 

6273 

6274 ``longitude`` and ``latitude`` Use the position given by 

6275 this coordinates. Can't be given with ``member`` 

6276 ``radius`` Similar to GEORADIUS, search inside circular 

6277 area according the given radius. Can't be given with 

6278 ``height`` and ``width``. 

6279 ``height`` and ``width`` Search inside an axis-aligned 

6280 rectangle, determined by the given height and width. 

6281 Can't be given with ``radius`` 

6282 

6283 ``unit`` must be one of the following : m, km, mi, ft. 

6284 `m` for meters (the default value), `km` for kilometers, 

6285 `mi` for miles and `ft` for feet. 

6286 

6287 ``sort`` indicates to return the places in a sorted way, 

6288 ASC for nearest to furthest and DESC for furthest to nearest. 

6289 

6290 ``count`` limit the results to the first count matching items. 

6291 

6292 ``any`` is set to True, the command will return as soon as 

6293 enough matches are found. Can't be provided without ``count`` 

6294 

6295 ``withdist`` indicates to return the distances of each place. 

6296 ``withcoord`` indicates to return the latitude and longitude of 

6297 each place. 

6298 

6299 ``withhash`` indicates to return the geohash string of each place. 

6300 

6301 For more information, see https://redis.io/commands/geosearch 

6302 """ 

6303 

6304 return self._geosearchgeneric( 

6305 "GEOSEARCH", 

6306 name, 

6307 member=member, 

6308 longitude=longitude, 

6309 latitude=latitude, 

6310 unit=unit, 

6311 radius=radius, 

6312 width=width, 

6313 height=height, 

6314 sort=sort, 

6315 count=count, 

6316 any=any, 

6317 withcoord=withcoord, 

6318 withdist=withdist, 

6319 withhash=withhash, 

6320 store=None, 

6321 store_dist=None, 

6322 ) 

6323 

6324 def geosearchstore( 

6325 self, 

6326 dest: KeyT, 

6327 name: KeyT, 

6328 member: Optional[FieldT] = None, 

6329 longitude: Optional[float] = None, 

6330 latitude: Optional[float] = None, 

6331 unit: str = "m", 

6332 radius: Optional[float] = None, 

6333 width: Optional[float] = None, 

6334 height: Optional[float] = None, 

6335 sort: Optional[str] = None, 

6336 count: Optional[int] = None, 

6337 any: bool = False, 

6338 storedist: bool = False, 

6339 ) -> ResponseT: 

6340 """ 

6341 This command is like GEOSEARCH, but stores the result in 

6342 ``dest``. By default, it stores the results in the destination 

6343 sorted set with their geospatial information. 

6344 if ``store_dist`` set to True, the command will stores the 

6345 items in a sorted set populated with their distance from the 

6346 center of the circle or box, as a floating-point number. 

6347 

6348 For more information, see https://redis.io/commands/geosearchstore 

6349 """ 

6350 return self._geosearchgeneric( 

6351 "GEOSEARCHSTORE", 

6352 dest, 

6353 name, 

6354 member=member, 

6355 longitude=longitude, 

6356 latitude=latitude, 

6357 unit=unit, 

6358 radius=radius, 

6359 width=width, 

6360 height=height, 

6361 sort=sort, 

6362 count=count, 

6363 any=any, 

6364 withcoord=None, 

6365 withdist=None, 

6366 withhash=None, 

6367 store=None, 

6368 store_dist=storedist, 

6369 ) 

6370 

6371 def _geosearchgeneric( 

6372 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

6373 ) -> ResponseT: 

6374 pieces = list(args) 

6375 

6376 # FROMMEMBER or FROMLONLAT 

6377 if kwargs["member"] is None: 

6378 if kwargs["longitude"] is None or kwargs["latitude"] is None: 

6379 raise DataError("GEOSEARCH must have member or longitude and latitude") 

6380 if kwargs["member"]: 

6381 if kwargs["longitude"] or kwargs["latitude"]: 

6382 raise DataError( 

6383 "GEOSEARCH member and longitude or latitude cant be set together" 

6384 ) 

6385 pieces.extend([b"FROMMEMBER", kwargs["member"]]) 

6386 if kwargs["longitude"] is not None and kwargs["latitude"] is not None: 

6387 pieces.extend([b"FROMLONLAT", kwargs["longitude"], kwargs["latitude"]]) 

6388 

6389 # BYRADIUS or BYBOX 

6390 if kwargs["radius"] is None: 

6391 if kwargs["width"] is None or kwargs["height"] is None: 

6392 raise DataError("GEOSEARCH must have radius or width and height") 

6393 if kwargs["unit"] is None: 

6394 raise DataError("GEOSEARCH must have unit") 

6395 if kwargs["unit"].lower() not in ("m", "km", "mi", "ft"): 

6396 raise DataError("GEOSEARCH invalid unit") 

6397 if kwargs["radius"]: 

6398 if kwargs["width"] or kwargs["height"]: 

6399 raise DataError( 

6400 "GEOSEARCH radius and width or height cant be set together" 

6401 ) 

6402 pieces.extend([b"BYRADIUS", kwargs["radius"], kwargs["unit"]]) 

6403 if kwargs["width"] and kwargs["height"]: 

6404 pieces.extend([b"BYBOX", kwargs["width"], kwargs["height"], kwargs["unit"]]) 

6405 

6406 # sort 

6407 if kwargs["sort"]: 

6408 if kwargs["sort"].upper() == "ASC": 

6409 pieces.append(b"ASC") 

6410 elif kwargs["sort"].upper() == "DESC": 

6411 pieces.append(b"DESC") 

6412 else: 

6413 raise DataError("GEOSEARCH invalid sort") 

6414 

6415 # count any 

6416 if kwargs["count"]: 

6417 pieces.extend([b"COUNT", kwargs["count"]]) 

6418 if kwargs["any"]: 

6419 pieces.append(b"ANY") 

6420 elif kwargs["any"]: 

6421 raise DataError("GEOSEARCH ``any`` can't be provided without count") 

6422 

6423 # other properties 

6424 for arg_name, byte_repr in ( 

6425 ("withdist", b"WITHDIST"), 

6426 ("withcoord", b"WITHCOORD"), 

6427 ("withhash", b"WITHHASH"), 

6428 ("store_dist", b"STOREDIST"), 

6429 ): 

6430 if kwargs[arg_name]: 

6431 pieces.append(byte_repr) 

6432 

6433 kwargs["keys"] = [args[0] if command == "GEOSEARCH" else args[1]] 

6434 

6435 return self.execute_command(command, *pieces, **kwargs) 

6436 

6437 

6438AsyncGeoCommands = GeoCommands 

6439 

6440 

6441class ModuleCommands(CommandsProtocol): 

6442 """ 

6443 Redis Module commands. 

6444 see: https://redis.io/topics/modules-intro 

6445 """ 

6446 

6447 def module_load(self, path, *args) -> ResponseT: 

6448 """ 

6449 Loads the module from ``path``. 

6450 Passes all ``*args`` to the module, during loading. 

6451 Raises ``ModuleError`` if a module is not found at ``path``. 

6452 

6453 For more information, see https://redis.io/commands/module-load 

6454 """ 

6455 return self.execute_command("MODULE LOAD", path, *args) 

6456 

6457 def module_loadex( 

6458 self, 

6459 path: str, 

6460 options: Optional[List[str]] = None, 

6461 args: Optional[List[str]] = None, 

6462 ) -> ResponseT: 

6463 """ 

6464 Loads a module from a dynamic library at runtime with configuration directives. 

6465 

6466 For more information, see https://redis.io/commands/module-loadex 

6467 """ 

6468 pieces = [] 

6469 if options is not None: 

6470 pieces.append("CONFIG") 

6471 pieces.extend(options) 

6472 if args is not None: 

6473 pieces.append("ARGS") 

6474 pieces.extend(args) 

6475 

6476 return self.execute_command("MODULE LOADEX", path, *pieces) 

6477 

6478 def module_unload(self, name) -> ResponseT: 

6479 """ 

6480 Unloads the module ``name``. 

6481 Raises ``ModuleError`` if ``name`` is not in loaded modules. 

6482 

6483 For more information, see https://redis.io/commands/module-unload 

6484 """ 

6485 return self.execute_command("MODULE UNLOAD", name) 

6486 

6487 def module_list(self) -> ResponseT: 

6488 """ 

6489 Returns a list of dictionaries containing the name and version of 

6490 all loaded modules. 

6491 

6492 For more information, see https://redis.io/commands/module-list 

6493 """ 

6494 return self.execute_command("MODULE LIST") 

6495 

6496 def command_info(self) -> None: 

6497 raise NotImplementedError( 

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

6499 ) 

6500 

6501 def command_count(self) -> ResponseT: 

6502 return self.execute_command("COMMAND COUNT") 

6503 

6504 def command_getkeys(self, *args) -> ResponseT: 

6505 return self.execute_command("COMMAND GETKEYS", *args) 

6506 

6507 def command(self) -> ResponseT: 

6508 return self.execute_command("COMMAND") 

6509 

6510 

6511class AsyncModuleCommands(ModuleCommands): 

6512 async def command_info(self) -> None: 

6513 return super().command_info() 

6514 

6515 

6516class ClusterCommands(CommandsProtocol): 

6517 """ 

6518 Class for Redis Cluster commands 

6519 """ 

6520 

6521 def cluster(self, cluster_arg, *args, **kwargs) -> ResponseT: 

6522 return self.execute_command(f"CLUSTER {cluster_arg.upper()}", *args, **kwargs) 

6523 

6524 def readwrite(self, **kwargs) -> ResponseT: 

6525 """ 

6526 Disables read queries for a connection to a Redis Cluster slave node. 

6527 

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

6529 """ 

6530 return self.execute_command("READWRITE", **kwargs) 

6531 

6532 def readonly(self, **kwargs) -> ResponseT: 

6533 """ 

6534 Enables read queries for a connection to a Redis Cluster replica node. 

6535 

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

6537 """ 

6538 return self.execute_command("READONLY", **kwargs) 

6539 

6540 

6541AsyncClusterCommands = ClusterCommands 

6542 

6543 

6544class FunctionCommands: 

6545 """ 

6546 Redis Function commands 

6547 """ 

6548 

6549 def function_load( 

6550 self, code: str, replace: Optional[bool] = False 

6551 ) -> Union[Awaitable[str], str]: 

6552 """ 

6553 Load a library to Redis. 

6554 :param code: the source code (must start with 

6555 Shebang statement that provides a metadata about the library) 

6556 :param replace: changes the behavior to overwrite the existing library 

6557 with the new contents. 

6558 Return the library name that was loaded. 

6559 

6560 For more information, see https://redis.io/commands/function-load 

6561 """ 

6562 pieces = ["REPLACE"] if replace else [] 

6563 pieces.append(code) 

6564 return self.execute_command("FUNCTION LOAD", *pieces) 

6565 

6566 def function_delete(self, library: str) -> Union[Awaitable[str], str]: 

6567 """ 

6568 Delete the library called ``library`` and all its functions. 

6569 

6570 For more information, see https://redis.io/commands/function-delete 

6571 """ 

6572 return self.execute_command("FUNCTION DELETE", library) 

6573 

6574 def function_flush(self, mode: str = "SYNC") -> Union[Awaitable[str], str]: 

6575 """ 

6576 Deletes all the libraries. 

6577 

6578 For more information, see https://redis.io/commands/function-flush 

6579 """ 

6580 return self.execute_command("FUNCTION FLUSH", mode) 

6581 

6582 def function_list( 

6583 self, library: Optional[str] = "*", withcode: Optional[bool] = False 

6584 ) -> Union[Awaitable[List], List]: 

6585 """ 

6586 Return information about the functions and libraries. 

6587 

6588 Args: 

6589 

6590 library: specify a pattern for matching library names 

6591 withcode: cause the server to include the libraries source implementation 

6592 in the reply 

6593 """ 

6594 args = ["LIBRARYNAME", library] 

6595 if withcode: 

6596 args.append("WITHCODE") 

6597 return self.execute_command("FUNCTION LIST", *args) 

6598 

6599 def _fcall( 

6600 self, command: str, function, numkeys: int, *keys_and_args: Any 

6601 ) -> Union[Awaitable[str], str]: 

6602 return self.execute_command(command, function, numkeys, *keys_and_args) 

6603 

6604 def fcall( 

6605 self, function, numkeys: int, *keys_and_args: Any 

6606 ) -> Union[Awaitable[str], str]: 

6607 """ 

6608 Invoke a function. 

6609 

6610 For more information, see https://redis.io/commands/fcall 

6611 """ 

6612 return self._fcall("FCALL", function, numkeys, *keys_and_args) 

6613 

6614 def fcall_ro( 

6615 self, function, numkeys: int, *keys_and_args: Any 

6616 ) -> Union[Awaitable[str], str]: 

6617 """ 

6618 This is a read-only variant of the FCALL command that cannot 

6619 execute commands that modify data. 

6620 

6621 For more information, see https://redis.io/commands/fcall_ro 

6622 """ 

6623 return self._fcall("FCALL_RO", function, numkeys, *keys_and_args) 

6624 

6625 def function_dump(self) -> Union[Awaitable[str], str]: 

6626 """ 

6627 Return the serialized payload of loaded libraries. 

6628 

6629 For more information, see https://redis.io/commands/function-dump 

6630 """ 

6631 from redis.client import NEVER_DECODE 

6632 

6633 options = {} 

6634 options[NEVER_DECODE] = [] 

6635 

6636 return self.execute_command("FUNCTION DUMP", **options) 

6637 

6638 def function_restore( 

6639 self, payload: str, policy: Optional[str] = "APPEND" 

6640 ) -> Union[Awaitable[str], str]: 

6641 """ 

6642 Restore libraries from the serialized ``payload``. 

6643 You can use the optional policy argument to provide a policy 

6644 for handling existing libraries. 

6645 

6646 For more information, see https://redis.io/commands/function-restore 

6647 """ 

6648 return self.execute_command("FUNCTION RESTORE", payload, policy) 

6649 

6650 def function_kill(self) -> Union[Awaitable[str], str]: 

6651 """ 

6652 Kill a function that is currently executing. 

6653 

6654 For more information, see https://redis.io/commands/function-kill 

6655 """ 

6656 return self.execute_command("FUNCTION KILL") 

6657 

6658 def function_stats(self) -> Union[Awaitable[List], List]: 

6659 """ 

6660 Return information about the function that's currently running 

6661 and information about the available execution engines. 

6662 

6663 For more information, see https://redis.io/commands/function-stats 

6664 """ 

6665 return self.execute_command("FUNCTION STATS") 

6666 

6667 

6668AsyncFunctionCommands = FunctionCommands 

6669 

6670 

6671class DataAccessCommands( 

6672 BasicKeyCommands, 

6673 HyperlogCommands, 

6674 HashCommands, 

6675 GeoCommands, 

6676 ListCommands, 

6677 ScanCommands, 

6678 SetCommands, 

6679 StreamCommands, 

6680 SortedSetCommands, 

6681): 

6682 """ 

6683 A class containing all of the implemented data access redis commands. 

6684 This class is to be used as a mixin for synchronous Redis clients. 

6685 """ 

6686 

6687 

6688class AsyncDataAccessCommands( 

6689 AsyncBasicKeyCommands, 

6690 AsyncHyperlogCommands, 

6691 AsyncHashCommands, 

6692 AsyncGeoCommands, 

6693 AsyncListCommands, 

6694 AsyncScanCommands, 

6695 AsyncSetCommands, 

6696 AsyncStreamCommands, 

6697 AsyncSortedSetCommands, 

6698): 

6699 """ 

6700 A class containing all of the implemented data access redis commands. 

6701 This class is to be used as a mixin for asynchronous Redis clients. 

6702 """ 

6703 

6704 

6705class CoreCommands( 

6706 ACLCommands, 

6707 ClusterCommands, 

6708 DataAccessCommands, 

6709 ManagementCommands, 

6710 ModuleCommands, 

6711 PubSubCommands, 

6712 ScriptCommands, 

6713 FunctionCommands, 

6714): 

6715 """ 

6716 A class containing all of the implemented redis commands. This class is 

6717 to be used as a mixin for synchronous Redis clients. 

6718 """ 

6719 

6720 

6721class AsyncCoreCommands( 

6722 AsyncACLCommands, 

6723 AsyncClusterCommands, 

6724 AsyncDataAccessCommands, 

6725 AsyncManagementCommands, 

6726 AsyncModuleCommands, 

6727 AsyncPubSubCommands, 

6728 AsyncScriptCommands, 

6729 AsyncFunctionCommands, 

6730): 

6731 """ 

6732 A class containing all of the implemented redis commands. This class is 

6733 to be used as a mixin for asynchronous Redis clients. 

6734 """