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) -> Union[Awaitable[bool], bool]: 

1214 """ 

1215 Ping the Redis server to test connectivity. 

1216 

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

1218 responds with "PONG". 

1219 

1220 This command is useful for: 

1221 - Testing whether a connection is still alive 

1222 - Verifying the server's ability to serve data 

1223 

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

1225 """ 

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

1227 

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

1229 """ 

1230 Ask the server to close the connection. 

1231 

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

1233 """ 

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

1235 

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

1237 """ 

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

1239 

1240 Examples of valid arguments include: 

1241 

1242 NO ONE (set no replication) 

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

1244 

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

1246 """ 

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

1248 

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

1250 """ 

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

1252 blocking until the save is complete 

1253 

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

1255 """ 

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

1257 

1258 def shutdown( 

1259 self, 

1260 save: bool = False, 

1261 nosave: bool = False, 

1262 now: bool = False, 

1263 force: bool = False, 

1264 abort: bool = False, 

1265 **kwargs, 

1266 ) -> None: 

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

1268 data will be flushed before shutdown. 

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

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

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

1272 are configured. 

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

1274 the shutdown sequence. 

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

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

1277 

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

1279 """ 

1280 if save and nosave: 

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

1282 args = ["SHUTDOWN"] 

1283 if save: 

1284 args.append("SAVE") 

1285 if nosave: 

1286 args.append("NOSAVE") 

1287 if now: 

1288 args.append("NOW") 

1289 if force: 

1290 args.append("FORCE") 

1291 if abort: 

1292 args.append("ABORT") 

1293 try: 

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

1295 except ConnectionError: 

1296 # a ConnectionError here is expected 

1297 return 

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

1299 

1300 def slaveof( 

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

1302 ) -> ResponseT: 

1303 """ 

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

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

1306 instance is promoted to a master instead. 

1307 

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

1309 """ 

1310 if host is None and port is None: 

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

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

1313 

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

1315 """ 

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

1317 most recent ``num`` items. 

1318 

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

1320 """ 

1321 from redis.client import NEVER_DECODE 

1322 

1323 args = ["SLOWLOG GET"] 

1324 if num is not None: 

1325 args.append(num) 

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

1327 if decode_responses is True: 

1328 kwargs[NEVER_DECODE] = [] 

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

1330 

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

1332 """ 

1333 Get the number of items in the slowlog 

1334 

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

1336 """ 

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

1338 

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

1340 """ 

1341 Remove all items in the slowlog 

1342 

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

1344 """ 

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

1346 

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

1348 """ 

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

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

1351 

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

1353 """ 

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

1355 

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

1357 """ 

1358 Redis synchronous replication 

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

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

1361 reached. 

1362 

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

1364 """ 

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

1366 

1367 def waitaof( 

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

1369 ) -> ResponseT: 

1370 """ 

1371 This command blocks the current client until all previous write 

1372 commands by that client are acknowledged as having been fsynced 

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

1374 of replicas. 

1375 

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

1377 """ 

1378 return self.execute_command( 

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

1380 ) 

1381 

1382 def hello(self): 

1383 """ 

1384 This function throws a NotImplementedError since it is intentionally 

1385 not supported. 

1386 """ 

1387 raise NotImplementedError( 

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

1389 ) 

1390 

1391 def failover(self): 

1392 """ 

1393 This function throws a NotImplementedError since it is intentionally 

1394 not supported. 

1395 """ 

1396 raise NotImplementedError( 

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

1398 ) 

1399 

1400 

1401class AsyncManagementCommands(ManagementCommands): 

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

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

1404 

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

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

1407 

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

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

1410 

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

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

1413 

1414 async def shutdown( 

1415 self, 

1416 save: bool = False, 

1417 nosave: bool = False, 

1418 now: bool = False, 

1419 force: bool = False, 

1420 abort: bool = False, 

1421 **kwargs, 

1422 ) -> None: 

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

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

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

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

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

1428 

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

1430 """ 

1431 if save and nosave: 

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

1433 args = ["SHUTDOWN"] 

1434 if save: 

1435 args.append("SAVE") 

1436 if nosave: 

1437 args.append("NOSAVE") 

1438 if now: 

1439 args.append("NOW") 

1440 if force: 

1441 args.append("FORCE") 

1442 if abort: 

1443 args.append("ABORT") 

1444 try: 

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

1446 except ConnectionError: 

1447 # a ConnectionError here is expected 

1448 return 

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

1450 

1451 

1452class BitFieldOperation: 

1453 """ 

1454 Command builder for BITFIELD commands. 

1455 """ 

1456 

1457 def __init__( 

1458 self, 

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

1460 key: str, 

1461 default_overflow: Optional[str] = None, 

1462 ): 

1463 self.client = client 

1464 self.key = key 

1465 self._default_overflow = default_overflow 

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

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

1468 self._last_overflow = "WRAP" 

1469 self.reset() 

1470 

1471 def reset(self): 

1472 """ 

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

1474 """ 

1475 self.operations = [] 

1476 self._last_overflow = "WRAP" 

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

1478 

1479 def overflow(self, overflow: str): 

1480 """ 

1481 Update the overflow algorithm of successive INCRBY operations 

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

1483 Redis docs for descriptions of these algorithmsself. 

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

1485 """ 

1486 overflow = overflow.upper() 

1487 if overflow != self._last_overflow: 

1488 self._last_overflow = overflow 

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

1490 return self 

1491 

1492 def incrby( 

1493 self, 

1494 fmt: str, 

1495 offset: BitfieldOffsetT, 

1496 increment: int, 

1497 overflow: Optional[str] = None, 

1498 ): 

1499 """ 

1500 Increment a bitfield by a given amount. 

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

1502 for an unsigned 8-bit integer. 

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

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

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

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

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

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

1509 descriptions of these algorithms. 

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

1511 """ 

1512 if overflow is not None: 

1513 self.overflow(overflow) 

1514 

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

1516 return self 

1517 

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

1519 """ 

1520 Get the value of a given bitfield. 

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

1522 an unsigned 8-bit integer. 

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

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

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

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

1527 """ 

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

1529 return self 

1530 

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

1532 """ 

1533 Set the value of a given bitfield. 

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

1535 an unsigned 8-bit integer. 

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

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

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

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

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

1541 """ 

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

1543 return self 

1544 

1545 @property 

1546 def command(self): 

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

1548 for ops in self.operations: 

1549 cmd.extend(ops) 

1550 return cmd 

1551 

1552 def execute(self) -> ResponseT: 

1553 """ 

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

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

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

1557 will be present within the pipeline's execute. 

1558 """ 

1559 command = self.command 

1560 self.reset() 

1561 return self.client.execute_command(*command) 

1562 

1563 

1564class BasicKeyCommands(CommandsProtocol): 

1565 """ 

1566 Redis basic key-based commands 

1567 """ 

1568 

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

1570 """ 

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

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

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

1574 

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

1576 """ 

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

1578 

1579 def bitcount( 

1580 self, 

1581 key: KeyT, 

1582 start: Optional[int] = None, 

1583 end: Optional[int] = None, 

1584 mode: Optional[str] = None, 

1585 ) -> ResponseT: 

1586 """ 

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

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

1589 

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

1591 """ 

1592 params = [key] 

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

1594 params.append(start) 

1595 params.append(end) 

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

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

1598 if mode is not None: 

1599 params.append(mode) 

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

1601 

1602 def bitfield( 

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

1604 key: KeyT, 

1605 default_overflow: Optional[str] = None, 

1606 ) -> BitFieldOperation: 

1607 """ 

1608 Return a BitFieldOperation instance to conveniently construct one or 

1609 more bitfield operations on ``key``. 

1610 

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

1612 """ 

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

1614 

1615 def bitfield_ro( 

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

1617 key: KeyT, 

1618 encoding: str, 

1619 offset: BitfieldOffsetT, 

1620 items: Optional[list] = None, 

1621 ) -> ResponseT: 

1622 """ 

1623 Return an array of the specified bitfield values 

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

1625 parameters and remaining values are result of corresponding 

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

1627 Read-only variant of the BITFIELD command. 

1628 

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

1630 """ 

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

1632 

1633 items = items or [] 

1634 for encoding, offset in items: 

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

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

1637 

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

1639 """ 

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

1641 store the result in ``dest``. 

1642 

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

1644 """ 

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

1646 

1647 def bitpos( 

1648 self, 

1649 key: KeyT, 

1650 bit: int, 

1651 start: Optional[int] = None, 

1652 end: Optional[int] = None, 

1653 mode: Optional[str] = None, 

1654 ) -> ResponseT: 

1655 """ 

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

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

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

1659 means to look at the first three bytes. 

1660 

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

1662 """ 

1663 if bit not in (0, 1): 

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

1665 params = [key, bit] 

1666 

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

1668 

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

1670 params.append(end) 

1671 elif start is None and end is not None: 

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

1673 

1674 if mode is not None: 

1675 params.append(mode) 

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

1677 

1678 def copy( 

1679 self, 

1680 source: str, 

1681 destination: str, 

1682 destination_db: Optional[str] = None, 

1683 replace: bool = False, 

1684 ) -> ResponseT: 

1685 """ 

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

1687 

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

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

1690 

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

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

1693 the ``destination`` key already exists. 

1694 

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

1696 """ 

1697 params = [source, destination] 

1698 if destination_db is not None: 

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

1700 if replace: 

1701 params.append("REPLACE") 

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

1703 

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

1705 """ 

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

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

1708 

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

1710 """ 

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

1712 

1713 decr = decrby 

1714 

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

1716 """ 

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

1718 """ 

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

1720 

1721 def __delitem__(self, name: KeyT): 

1722 self.delete(name) 

1723 

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

1725 """ 

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

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

1728 

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

1730 """ 

1731 from redis.client import NEVER_DECODE 

1732 

1733 options = {} 

1734 options[NEVER_DECODE] = [] 

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

1736 

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

1738 """ 

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

1740 

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

1742 """ 

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

1744 

1745 __contains__ = exists 

1746 

1747 def expire( 

1748 self, 

1749 name: KeyT, 

1750 time: ExpiryT, 

1751 nx: bool = False, 

1752 xx: bool = False, 

1753 gt: bool = False, 

1754 lt: bool = False, 

1755 ) -> ResponseT: 

1756 """ 

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

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

1759 object. 

1760 

1761 Valid options are: 

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

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

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

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

1766 

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

1768 """ 

1769 if isinstance(time, datetime.timedelta): 

1770 time = int(time.total_seconds()) 

1771 

1772 exp_option = list() 

1773 if nx: 

1774 exp_option.append("NX") 

1775 if xx: 

1776 exp_option.append("XX") 

1777 if gt: 

1778 exp_option.append("GT") 

1779 if lt: 

1780 exp_option.append("LT") 

1781 

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

1783 

1784 def expireat( 

1785 self, 

1786 name: KeyT, 

1787 when: AbsExpiryT, 

1788 nx: bool = False, 

1789 xx: bool = False, 

1790 gt: bool = False, 

1791 lt: bool = False, 

1792 ) -> ResponseT: 

1793 """ 

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

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

1796 datetime object. 

1797 

1798 Valid options are: 

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

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

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

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

1803 

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

1805 """ 

1806 if isinstance(when, datetime.datetime): 

1807 when = int(when.timestamp()) 

1808 

1809 exp_option = list() 

1810 if nx: 

1811 exp_option.append("NX") 

1812 if xx: 

1813 exp_option.append("XX") 

1814 if gt: 

1815 exp_option.append("GT") 

1816 if lt: 

1817 exp_option.append("LT") 

1818 

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

1820 

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

1822 """ 

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

1824 at which the given key will expire. 

1825 

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

1827 """ 

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

1829 

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

1831 """ 

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

1833 

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

1835 """ 

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

1837 

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

1839 """ 

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

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

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

1843 is a string). 

1844 

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

1846 """ 

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

1848 

1849 def getex( 

1850 self, 

1851 name: KeyT, 

1852 ex: Optional[ExpiryT] = None, 

1853 px: Optional[ExpiryT] = None, 

1854 exat: Optional[AbsExpiryT] = None, 

1855 pxat: Optional[AbsExpiryT] = None, 

1856 persist: bool = False, 

1857 ) -> ResponseT: 

1858 """ 

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

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

1861 additional options. All time parameters can be given as 

1862 datetime.timedelta or integers. 

1863 

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

1865 

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

1867 

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

1869 specified in unix time. 

1870 

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

1872 specified in unix time. 

1873 

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

1875 

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

1877 """ 

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

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

1880 raise DataError( 

1881 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

1883 ) 

1884 

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

1886 

1887 if persist: 

1888 exp_options.append("PERSIST") 

1889 

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

1891 

1892 def __getitem__(self, name: KeyT): 

1893 """ 

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

1895 doesn't exist. 

1896 """ 

1897 value = self.get(name) 

1898 if value is not None: 

1899 return value 

1900 raise KeyError(name) 

1901 

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

1903 """ 

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

1905 

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

1907 """ 

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

1909 

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

1911 """ 

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

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

1914 

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

1916 """ 

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

1918 

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

1920 """ 

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

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

1923 

1924 As per Redis 6.2, GETSET is considered deprecated. 

1925 Please use SET with GET parameter in new code. 

1926 

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

1928 """ 

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

1930 

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

1932 """ 

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

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

1935 

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

1937 """ 

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

1939 

1940 incr = incrby 

1941 

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

1943 """ 

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

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

1946 

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

1948 """ 

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

1950 

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

1952 """ 

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

1954 

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

1956 """ 

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

1958 

1959 def lmove( 

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

1961 ) -> ResponseT: 

1962 """ 

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

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

1965 Returns the element being popped and pushed. 

1966 

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

1968 """ 

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

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

1971 

1972 def blmove( 

1973 self, 

1974 first_list: str, 

1975 second_list: str, 

1976 timeout: int, 

1977 src: str = "LEFT", 

1978 dest: str = "RIGHT", 

1979 ) -> ResponseT: 

1980 """ 

1981 Blocking version of lmove. 

1982 

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

1984 """ 

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

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

1987 

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

1989 """ 

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

1991 

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

1993 """ 

1994 from redis.client import EMPTY_RESPONSE 

1995 

1996 args = list_or_args(keys, args) 

1997 options = {} 

1998 if not args: 

1999 options[EMPTY_RESPONSE] = [] 

2000 options["keys"] = args 

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

2002 

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

2004 """ 

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

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

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

2008 

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

2010 """ 

2011 items = [] 

2012 for pair in mapping.items(): 

2013 items.extend(pair) 

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

2015 

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

2017 """ 

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

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

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

2021 Returns a boolean indicating if the operation was successful. 

2022 

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

2024 """ 

2025 items = [] 

2026 for pair in mapping.items(): 

2027 items.extend(pair) 

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

2029 

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

2031 """ 

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

2033 

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

2035 """ 

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

2037 

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

2039 """ 

2040 Removes an expiration on ``name`` 

2041 

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

2043 """ 

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

2045 

2046 def pexpire( 

2047 self, 

2048 name: KeyT, 

2049 time: ExpiryT, 

2050 nx: bool = False, 

2051 xx: bool = False, 

2052 gt: bool = False, 

2053 lt: bool = False, 

2054 ) -> ResponseT: 

2055 """ 

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

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

2058 integer or a Python timedelta object. 

2059 

2060 Valid options are: 

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

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

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

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

2065 

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

2067 """ 

2068 if isinstance(time, datetime.timedelta): 

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

2070 

2071 exp_option = list() 

2072 if nx: 

2073 exp_option.append("NX") 

2074 if xx: 

2075 exp_option.append("XX") 

2076 if gt: 

2077 exp_option.append("GT") 

2078 if lt: 

2079 exp_option.append("LT") 

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

2081 

2082 def pexpireat( 

2083 self, 

2084 name: KeyT, 

2085 when: AbsExpiryT, 

2086 nx: bool = False, 

2087 xx: bool = False, 

2088 gt: bool = False, 

2089 lt: bool = False, 

2090 ) -> ResponseT: 

2091 """ 

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

2093 can be represented as an integer representing unix time in 

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

2095 

2096 Valid options are: 

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

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

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

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

2101 

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

2103 """ 

2104 if isinstance(when, datetime.datetime): 

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

2106 exp_option = list() 

2107 if nx: 

2108 exp_option.append("NX") 

2109 if xx: 

2110 exp_option.append("XX") 

2111 if gt: 

2112 exp_option.append("GT") 

2113 if lt: 

2114 exp_option.append("LT") 

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

2116 

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

2118 """ 

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

2120 at which the given key will expire. 

2121 

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

2123 """ 

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

2125 

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

2127 """ 

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

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

2130 timedelta object 

2131 

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

2133 """ 

2134 if isinstance(time_ms, datetime.timedelta): 

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

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

2137 

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

2139 """ 

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

2141 

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

2143 """ 

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

2145 

2146 def hrandfield( 

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

2148 ) -> ResponseT: 

2149 """ 

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

2151 

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

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

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

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

2156 specified count. 

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

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

2159 

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

2161 """ 

2162 params = [] 

2163 if count is not None: 

2164 params.append(count) 

2165 if withvalues: 

2166 params.append("WITHVALUES") 

2167 

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

2169 

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

2171 """ 

2172 Returns the name of a random key 

2173 

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

2175 """ 

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

2177 

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

2179 """ 

2180 Rename key ``src`` to ``dst`` 

2181 

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

2183 """ 

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

2185 

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

2187 """ 

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

2189 

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

2191 """ 

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

2193 

2194 def restore( 

2195 self, 

2196 name: KeyT, 

2197 ttl: float, 

2198 value: EncodableT, 

2199 replace: bool = False, 

2200 absttl: bool = False, 

2201 idletime: Optional[int] = None, 

2202 frequency: Optional[int] = None, 

2203 ) -> ResponseT: 

2204 """ 

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

2206 using DUMP. 

2207 

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

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

2210 

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

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

2213 greater). 

2214 

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

2216 key must be idle, prior to execution. 

2217 

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

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

2220 

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

2222 """ 

2223 params = [name, ttl, value] 

2224 if replace: 

2225 params.append("REPLACE") 

2226 if absttl: 

2227 params.append("ABSTTL") 

2228 if idletime is not None: 

2229 params.append("IDLETIME") 

2230 try: 

2231 params.append(int(idletime)) 

2232 except ValueError: 

2233 raise DataError("idletimemust be an integer") 

2234 

2235 if frequency is not None: 

2236 params.append("FREQ") 

2237 try: 

2238 params.append(int(frequency)) 

2239 except ValueError: 

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

2241 

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

2243 

2244 def set( 

2245 self, 

2246 name: KeyT, 

2247 value: EncodableT, 

2248 ex: Optional[ExpiryT] = None, 

2249 px: Optional[ExpiryT] = None, 

2250 nx: bool = False, 

2251 xx: bool = False, 

2252 keepttl: bool = False, 

2253 get: bool = False, 

2254 exat: Optional[AbsExpiryT] = None, 

2255 pxat: Optional[AbsExpiryT] = None, 

2256 ) -> ResponseT: 

2257 """ 

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

2259 

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

2261 

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

2263 

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

2265 if it does not exist. 

2266 

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

2268 if it already exists. 

2269 

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

2271 (Available since Redis 6.0) 

2272 

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

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

2275 (Available since Redis 6.2) 

2276 

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

2278 specified in unix time. 

2279 

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

2281 specified in unix time. 

2282 

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

2284 """ 

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

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

2287 raise DataError( 

2288 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

2290 ) 

2291 

2292 if nx and xx: 

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

2294 

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

2296 options = {} 

2297 

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

2299 

2300 if keepttl: 

2301 pieces.append("KEEPTTL") 

2302 

2303 if nx: 

2304 pieces.append("NX") 

2305 if xx: 

2306 pieces.append("XX") 

2307 

2308 if get: 

2309 pieces.append("GET") 

2310 options["get"] = True 

2311 

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

2313 

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

2315 self.set(name, value) 

2316 

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

2318 """ 

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

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

2321 

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

2323 """ 

2324 value = value and 1 or 0 

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

2326 

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

2328 """ 

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

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

2331 timedelta object. 

2332 

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

2334 """ 

2335 if isinstance(time, datetime.timedelta): 

2336 time = int(time.total_seconds()) 

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

2338 

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

2340 """ 

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

2342 

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

2344 """ 

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

2346 

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

2348 """ 

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

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

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

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

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

2354 of what's being injected. 

2355 

2356 Returns the length of the new string. 

2357 

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

2359 """ 

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

2361 

2362 def stralgo( 

2363 self, 

2364 algo: Literal["LCS"], 

2365 value1: KeyT, 

2366 value2: KeyT, 

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

2368 len: bool = False, 

2369 idx: bool = False, 

2370 minmatchlen: Optional[int] = None, 

2371 withmatchlen: bool = False, 

2372 **kwargs, 

2373 ) -> ResponseT: 

2374 """ 

2375 Implements complex algorithms that operate on strings. 

2376 Right now the only algorithm implemented is the LCS algorithm 

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

2378 implemented in the future. 

2379 

2380 ``algo`` Right now must be LCS 

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

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

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

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

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

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

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

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

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

2390 

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

2392 """ 

2393 # check validity 

2394 supported_algo = ["LCS"] 

2395 if algo not in supported_algo: 

2396 supported_algos_str = ", ".join(supported_algo) 

2397 raise DataError(f"The supported algorithms are: {supported_algos_str}") 

2398 if specific_argument not in ["keys", "strings"]: 

2399 raise DataError("specific_argument can be only keys or strings") 

2400 if len and idx: 

2401 raise DataError("len and idx cannot be provided together.") 

2402 

2403 pieces: list[EncodableT] = [algo, specific_argument.upper(), value1, value2] 

2404 if len: 

2405 pieces.append(b"LEN") 

2406 if idx: 

2407 pieces.append(b"IDX") 

2408 try: 

2409 int(minmatchlen) 

2410 pieces.extend([b"MINMATCHLEN", minmatchlen]) 

2411 except TypeError: 

2412 pass 

2413 if withmatchlen: 

2414 pieces.append(b"WITHMATCHLEN") 

2415 

2416 return self.execute_command( 

2417 "STRALGO", 

2418 *pieces, 

2419 len=len, 

2420 idx=idx, 

2421 minmatchlen=minmatchlen, 

2422 withmatchlen=withmatchlen, 

2423 **kwargs, 

2424 ) 

2425 

2426 def strlen(self, name: KeyT) -> ResponseT: 

2427 """ 

2428 Return the number of bytes stored in the value of ``name`` 

2429 

2430 For more information, see https://redis.io/commands/strlen 

2431 """ 

2432 return self.execute_command("STRLEN", name, keys=[name]) 

2433 

2434 def substr(self, name: KeyT, start: int, end: int = -1) -> ResponseT: 

2435 """ 

2436 Return a substring of the string at key ``name``. ``start`` and ``end`` 

2437 are 0-based integers specifying the portion of the string to return. 

2438 """ 

2439 return self.execute_command("SUBSTR", name, start, end, keys=[name]) 

2440 

2441 def touch(self, *args: KeyT) -> ResponseT: 

2442 """ 

2443 Alters the last access time of a key(s) ``*args``. A key is ignored 

2444 if it does not exist. 

2445 

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

2447 """ 

2448 return self.execute_command("TOUCH", *args) 

2449 

2450 def ttl(self, name: KeyT) -> ResponseT: 

2451 """ 

2452 Returns the number of seconds until the key ``name`` will expire 

2453 

2454 For more information, see https://redis.io/commands/ttl 

2455 """ 

2456 return self.execute_command("TTL", name) 

2457 

2458 def type(self, name: KeyT) -> ResponseT: 

2459 """ 

2460 Returns the type of key ``name`` 

2461 

2462 For more information, see https://redis.io/commands/type 

2463 """ 

2464 return self.execute_command("TYPE", name, keys=[name]) 

2465 

2466 def watch(self, *names: KeyT) -> None: 

2467 """ 

2468 Watches the values at keys ``names``, or None if the key doesn't exist 

2469 

2470 For more information, see https://redis.io/commands/watch 

2471 """ 

2472 warnings.warn(DeprecationWarning("Call WATCH from a Pipeline object")) 

2473 

2474 def unwatch(self) -> None: 

2475 """ 

2476 Unwatches all previously watched keys for a transaction 

2477 

2478 For more information, see https://redis.io/commands/unwatch 

2479 """ 

2480 warnings.warn(DeprecationWarning("Call UNWATCH from a Pipeline object")) 

2481 

2482 def unlink(self, *names: KeyT) -> ResponseT: 

2483 """ 

2484 Unlink one or more keys specified by ``names`` 

2485 

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

2487 """ 

2488 return self.execute_command("UNLINK", *names) 

2489 

2490 def lcs( 

2491 self, 

2492 key1: str, 

2493 key2: str, 

2494 len: Optional[bool] = False, 

2495 idx: Optional[bool] = False, 

2496 minmatchlen: Optional[int] = 0, 

2497 withmatchlen: Optional[bool] = False, 

2498 ) -> Union[str, int, list]: 

2499 """ 

2500 Find the longest common subsequence between ``key1`` and ``key2``. 

2501 If ``len`` is true the length of the match will will be returned. 

2502 If ``idx`` is true the match position in each strings will be returned. 

2503 ``minmatchlen`` restrict the list of matches to the ones of 

2504 the given ``minmatchlen``. 

2505 If ``withmatchlen`` the length of the match also will be returned. 

2506 For more information, see https://redis.io/commands/lcs 

2507 """ 

2508 pieces = [key1, key2] 

2509 if len: 

2510 pieces.append("LEN") 

2511 if idx: 

2512 pieces.append("IDX") 

2513 if minmatchlen != 0: 

2514 pieces.extend(["MINMATCHLEN", minmatchlen]) 

2515 if withmatchlen: 

2516 pieces.append("WITHMATCHLEN") 

2517 return self.execute_command("LCS", *pieces, keys=[key1, key2]) 

2518 

2519 

2520class AsyncBasicKeyCommands(BasicKeyCommands): 

2521 def __delitem__(self, name: KeyT): 

2522 raise TypeError("Async Redis client does not support class deletion") 

2523 

2524 def __contains__(self, name: KeyT): 

2525 raise TypeError("Async Redis client does not support class inclusion") 

2526 

2527 def __getitem__(self, name: KeyT): 

2528 raise TypeError("Async Redis client does not support class retrieval") 

2529 

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

2531 raise TypeError("Async Redis client does not support class assignment") 

2532 

2533 async def watch(self, *names: KeyT) -> None: 

2534 return super().watch(*names) 

2535 

2536 async def unwatch(self) -> None: 

2537 return super().unwatch() 

2538 

2539 

2540class ListCommands(CommandsProtocol): 

2541 """ 

2542 Redis commands for List data type. 

2543 see: https://redis.io/topics/data-types#lists 

2544 """ 

2545 

2546 def blpop( 

2547 self, keys: List, timeout: Optional[Number] = 0 

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

2549 """ 

2550 LPOP a value off of the first non-empty list 

2551 named in the ``keys`` list. 

2552 

2553 If none of the lists in ``keys`` has a value to LPOP, then block 

2554 for ``timeout`` seconds, or until a value gets pushed on to one 

2555 of the lists. 

2556 

2557 If timeout is 0, then block indefinitely. 

2558 

2559 For more information, see https://redis.io/commands/blpop 

2560 """ 

2561 if timeout is None: 

2562 timeout = 0 

2563 keys = list_or_args(keys, None) 

2564 keys.append(timeout) 

2565 return self.execute_command("BLPOP", *keys) 

2566 

2567 def brpop( 

2568 self, keys: List, timeout: Optional[Number] = 0 

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

2570 """ 

2571 RPOP a value off of the first non-empty list 

2572 named in the ``keys`` list. 

2573 

2574 If none of the lists in ``keys`` has a value to RPOP, then block 

2575 for ``timeout`` seconds, or until a value gets pushed on to one 

2576 of the lists. 

2577 

2578 If timeout is 0, then block indefinitely. 

2579 

2580 For more information, see https://redis.io/commands/brpop 

2581 """ 

2582 if timeout is None: 

2583 timeout = 0 

2584 keys = list_or_args(keys, None) 

2585 keys.append(timeout) 

2586 return self.execute_command("BRPOP", *keys) 

2587 

2588 def brpoplpush( 

2589 self, src: str, dst: str, timeout: Optional[Number] = 0 

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

2591 """ 

2592 Pop a value off the tail of ``src``, push it on the head of ``dst`` 

2593 and then return it. 

2594 

2595 This command blocks until a value is in ``src`` or until ``timeout`` 

2596 seconds elapse, whichever is first. A ``timeout`` value of 0 blocks 

2597 forever. 

2598 

2599 For more information, see https://redis.io/commands/brpoplpush 

2600 """ 

2601 if timeout is None: 

2602 timeout = 0 

2603 return self.execute_command("BRPOPLPUSH", src, dst, timeout) 

2604 

2605 def blmpop( 

2606 self, 

2607 timeout: float, 

2608 numkeys: int, 

2609 *args: List[str], 

2610 direction: str, 

2611 count: Optional[int] = 1, 

2612 ) -> Optional[list]: 

2613 """ 

2614 Pop ``count`` values (default 1) from first non-empty in the list 

2615 of provided key names. 

2616 

2617 When all lists are empty this command blocks the connection until another 

2618 client pushes to it or until the timeout, timeout of 0 blocks indefinitely 

2619 

2620 For more information, see https://redis.io/commands/blmpop 

2621 """ 

2622 args = [timeout, numkeys, *args, direction, "COUNT", count] 

2623 

2624 return self.execute_command("BLMPOP", *args) 

2625 

2626 def lmpop( 

2627 self, 

2628 num_keys: int, 

2629 *args: List[str], 

2630 direction: str, 

2631 count: Optional[int] = 1, 

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

2633 """ 

2634 Pop ``count`` values (default 1) first non-empty list key from the list 

2635 of args provided key names. 

2636 

2637 For more information, see https://redis.io/commands/lmpop 

2638 """ 

2639 args = [num_keys] + list(args) + [direction] 

2640 if count != 1: 

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

2642 

2643 return self.execute_command("LMPOP", *args) 

2644 

2645 def lindex( 

2646 self, name: str, index: int 

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

2648 """ 

2649 Return the item from list ``name`` at position ``index`` 

2650 

2651 Negative indexes are supported and will return an item at the 

2652 end of the list 

2653 

2654 For more information, see https://redis.io/commands/lindex 

2655 """ 

2656 return self.execute_command("LINDEX", name, index, keys=[name]) 

2657 

2658 def linsert( 

2659 self, name: str, where: str, refvalue: str, value: str 

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

2661 """ 

2662 Insert ``value`` in list ``name`` either immediately before or after 

2663 [``where``] ``refvalue`` 

2664 

2665 Returns the new length of the list on success or -1 if ``refvalue`` 

2666 is not in the list. 

2667 

2668 For more information, see https://redis.io/commands/linsert 

2669 """ 

2670 return self.execute_command("LINSERT", name, where, refvalue, value) 

2671 

2672 def llen(self, name: str) -> Union[Awaitable[int], int]: 

2673 """ 

2674 Return the length of the list ``name`` 

2675 

2676 For more information, see https://redis.io/commands/llen 

2677 """ 

2678 return self.execute_command("LLEN", name, keys=[name]) 

2679 

2680 def lpop( 

2681 self, 

2682 name: str, 

2683 count: Optional[int] = None, 

2684 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]: 

2685 """ 

2686 Removes and returns the first elements of the list ``name``. 

2687 

2688 By default, the command pops a single element from the beginning of 

2689 the list. When provided with the optional ``count`` argument, the reply 

2690 will consist of up to count elements, depending on the list's length. 

2691 

2692 For more information, see https://redis.io/commands/lpop 

2693 """ 

2694 if count is not None: 

2695 return self.execute_command("LPOP", name, count) 

2696 else: 

2697 return self.execute_command("LPOP", name) 

2698 

2699 def lpush(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2700 """ 

2701 Push ``values`` onto the head of the list ``name`` 

2702 

2703 For more information, see https://redis.io/commands/lpush 

2704 """ 

2705 return self.execute_command("LPUSH", name, *values) 

2706 

2707 def lpushx(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2708 """ 

2709 Push ``value`` onto the head of the list ``name`` if ``name`` exists 

2710 

2711 For more information, see https://redis.io/commands/lpushx 

2712 """ 

2713 return self.execute_command("LPUSHX", name, *values) 

2714 

2715 def lrange(self, name: str, start: int, end: int) -> Union[Awaitable[list], list]: 

2716 """ 

2717 Return a slice of the list ``name`` between 

2718 position ``start`` and ``end`` 

2719 

2720 ``start`` and ``end`` can be negative numbers just like 

2721 Python slicing notation 

2722 

2723 For more information, see https://redis.io/commands/lrange 

2724 """ 

2725 return self.execute_command("LRANGE", name, start, end, keys=[name]) 

2726 

2727 def lrem(self, name: str, count: int, value: str) -> Union[Awaitable[int], int]: 

2728 """ 

2729 Remove the first ``count`` occurrences of elements equal to ``value`` 

2730 from the list stored at ``name``. 

2731 

2732 The count argument influences the operation in the following ways: 

2733 count > 0: Remove elements equal to value moving from head to tail. 

2734 count < 0: Remove elements equal to value moving from tail to head. 

2735 count = 0: Remove all elements equal to value. 

2736 

2737 For more information, see https://redis.io/commands/lrem 

2738 """ 

2739 return self.execute_command("LREM", name, count, value) 

2740 

2741 def lset(self, name: str, index: int, value: str) -> Union[Awaitable[str], str]: 

2742 """ 

2743 Set element at ``index`` of list ``name`` to ``value`` 

2744 

2745 For more information, see https://redis.io/commands/lset 

2746 """ 

2747 return self.execute_command("LSET", name, index, value) 

2748 

2749 def ltrim(self, name: str, start: int, end: int) -> Union[Awaitable[str], str]: 

2750 """ 

2751 Trim the list ``name``, removing all values not within the slice 

2752 between ``start`` and ``end`` 

2753 

2754 ``start`` and ``end`` can be negative numbers just like 

2755 Python slicing notation 

2756 

2757 For more information, see https://redis.io/commands/ltrim 

2758 """ 

2759 return self.execute_command("LTRIM", name, start, end) 

2760 

2761 def rpop( 

2762 self, 

2763 name: str, 

2764 count: Optional[int] = None, 

2765 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]: 

2766 """ 

2767 Removes and returns the last elements of the list ``name``. 

2768 

2769 By default, the command pops a single element from the end of the list. 

2770 When provided with the optional ``count`` argument, the reply will 

2771 consist of up to count elements, depending on the list's length. 

2772 

2773 For more information, see https://redis.io/commands/rpop 

2774 """ 

2775 if count is not None: 

2776 return self.execute_command("RPOP", name, count) 

2777 else: 

2778 return self.execute_command("RPOP", name) 

2779 

2780 def rpoplpush(self, src: str, dst: str) -> Union[Awaitable[str], str]: 

2781 """ 

2782 RPOP a value off of the ``src`` list and atomically LPUSH it 

2783 on to the ``dst`` list. Returns the value. 

2784 

2785 For more information, see https://redis.io/commands/rpoplpush 

2786 """ 

2787 return self.execute_command("RPOPLPUSH", src, dst) 

2788 

2789 def rpush(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2790 """ 

2791 Push ``values`` onto the tail of the list ``name`` 

2792 

2793 For more information, see https://redis.io/commands/rpush 

2794 """ 

2795 return self.execute_command("RPUSH", name, *values) 

2796 

2797 def rpushx(self, name: str, *values: str) -> Union[Awaitable[int], int]: 

2798 """ 

2799 Push ``value`` onto the tail of the list ``name`` if ``name`` exists 

2800 

2801 For more information, see https://redis.io/commands/rpushx 

2802 """ 

2803 return self.execute_command("RPUSHX", name, *values) 

2804 

2805 def lpos( 

2806 self, 

2807 name: str, 

2808 value: str, 

2809 rank: Optional[int] = None, 

2810 count: Optional[int] = None, 

2811 maxlen: Optional[int] = None, 

2812 ) -> Union[str, List, None]: 

2813 """ 

2814 Get position of ``value`` within the list ``name`` 

2815 

2816 If specified, ``rank`` indicates the "rank" of the first element to 

2817 return in case there are multiple copies of ``value`` in the list. 

2818 By default, LPOS returns the position of the first occurrence of 

2819 ``value`` in the list. When ``rank`` 2, LPOS returns the position of 

2820 the second ``value`` in the list. If ``rank`` is negative, LPOS 

2821 searches the list in reverse. For example, -1 would return the 

2822 position of the last occurrence of ``value`` and -2 would return the 

2823 position of the next to last occurrence of ``value``. 

2824 

2825 If specified, ``count`` indicates that LPOS should return a list of 

2826 up to ``count`` positions. A ``count`` of 2 would return a list of 

2827 up to 2 positions. A ``count`` of 0 returns a list of all positions 

2828 matching ``value``. When ``count`` is specified and but ``value`` 

2829 does not exist in the list, an empty list is returned. 

2830 

2831 If specified, ``maxlen`` indicates the maximum number of list 

2832 elements to scan. A ``maxlen`` of 1000 will only return the 

2833 position(s) of items within the first 1000 entries in the list. 

2834 A ``maxlen`` of 0 (the default) will scan the entire list. 

2835 

2836 For more information, see https://redis.io/commands/lpos 

2837 """ 

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

2839 if rank is not None: 

2840 pieces.extend(["RANK", rank]) 

2841 

2842 if count is not None: 

2843 pieces.extend(["COUNT", count]) 

2844 

2845 if maxlen is not None: 

2846 pieces.extend(["MAXLEN", maxlen]) 

2847 

2848 return self.execute_command("LPOS", *pieces, keys=[name]) 

2849 

2850 def sort( 

2851 self, 

2852 name: str, 

2853 start: Optional[int] = None, 

2854 num: Optional[int] = None, 

2855 by: Optional[str] = None, 

2856 get: Optional[List[str]] = None, 

2857 desc: bool = False, 

2858 alpha: bool = False, 

2859 store: Optional[str] = None, 

2860 groups: Optional[bool] = False, 

2861 ) -> Union[List, int]: 

2862 """ 

2863 Sort and return the list, set or sorted set at ``name``. 

2864 

2865 ``start`` and ``num`` allow for paging through the sorted data 

2866 

2867 ``by`` allows using an external key to weight and sort the items. 

2868 Use an "*" to indicate where in the key the item value is located 

2869 

2870 ``get`` allows for returning items from external keys rather than the 

2871 sorted data itself. Use an "*" to indicate where in the key 

2872 the item value is located 

2873 

2874 ``desc`` allows for reversing the sort 

2875 

2876 ``alpha`` allows for sorting lexicographically rather than numerically 

2877 

2878 ``store`` allows for storing the result of the sort into 

2879 the key ``store`` 

2880 

2881 ``groups`` if set to True and if ``get`` contains at least two 

2882 elements, sort will return a list of tuples, each containing the 

2883 values fetched from the arguments to ``get``. 

2884 

2885 For more information, see https://redis.io/commands/sort 

2886 """ 

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

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

2889 

2890 pieces: list[EncodableT] = [name] 

2891 if by is not None: 

2892 pieces.extend([b"BY", by]) 

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

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

2895 if get is not None: 

2896 # If get is a string assume we want to get a single value. 

2897 # Otherwise assume it's an interable and we want to get multiple 

2898 # values. We can't just iterate blindly because strings are 

2899 # iterable. 

2900 if isinstance(get, (bytes, str)): 

2901 pieces.extend([b"GET", get]) 

2902 else: 

2903 for g in get: 

2904 pieces.extend([b"GET", g]) 

2905 if desc: 

2906 pieces.append(b"DESC") 

2907 if alpha: 

2908 pieces.append(b"ALPHA") 

2909 if store is not None: 

2910 pieces.extend([b"STORE", store]) 

2911 if groups: 

2912 if not get or isinstance(get, (bytes, str)) or len(get) < 2: 

2913 raise DataError( 

2914 'when using "groups" the "get" argument ' 

2915 "must be specified and contain at least " 

2916 "two keys" 

2917 ) 

2918 

2919 options = {"groups": len(get) if groups else None} 

2920 options["keys"] = [name] 

2921 return self.execute_command("SORT", *pieces, **options) 

2922 

2923 def sort_ro( 

2924 self, 

2925 key: str, 

2926 start: Optional[int] = None, 

2927 num: Optional[int] = None, 

2928 by: Optional[str] = None, 

2929 get: Optional[List[str]] = None, 

2930 desc: bool = False, 

2931 alpha: bool = False, 

2932 ) -> list: 

2933 """ 

2934 Returns the elements contained in the list, set or sorted set at key. 

2935 (read-only variant of the SORT command) 

2936 

2937 ``start`` and ``num`` allow for paging through the sorted data 

2938 

2939 ``by`` allows using an external key to weight and sort the items. 

2940 Use an "*" to indicate where in the key the item value is located 

2941 

2942 ``get`` allows for returning items from external keys rather than the 

2943 sorted data itself. Use an "*" to indicate where in the key 

2944 the item value is located 

2945 

2946 ``desc`` allows for reversing the sort 

2947 

2948 ``alpha`` allows for sorting lexicographically rather than numerically 

2949 

2950 For more information, see https://redis.io/commands/sort_ro 

2951 """ 

2952 return self.sort( 

2953 key, start=start, num=num, by=by, get=get, desc=desc, alpha=alpha 

2954 ) 

2955 

2956 

2957AsyncListCommands = ListCommands 

2958 

2959 

2960class ScanCommands(CommandsProtocol): 

2961 """ 

2962 Redis SCAN commands. 

2963 see: https://redis.io/commands/scan 

2964 """ 

2965 

2966 def scan( 

2967 self, 

2968 cursor: int = 0, 

2969 match: Union[PatternT, None] = None, 

2970 count: Optional[int] = None, 

2971 _type: Optional[str] = None, 

2972 **kwargs, 

2973 ) -> ResponseT: 

2974 """ 

2975 Incrementally return lists of key names. Also return a cursor 

2976 indicating the scan position. 

2977 

2978 ``match`` allows for filtering the keys by pattern 

2979 

2980 ``count`` provides a hint to Redis about the number of keys to 

2981 return per batch. 

2982 

2983 ``_type`` filters the returned values by a particular Redis type. 

2984 Stock Redis instances allow for the following types: 

2985 HASH, LIST, SET, STREAM, STRING, ZSET 

2986 Additionally, Redis modules can expose other types as well. 

2987 

2988 For more information, see https://redis.io/commands/scan 

2989 """ 

2990 pieces: list[EncodableT] = [cursor] 

2991 if match is not None: 

2992 pieces.extend([b"MATCH", match]) 

2993 if count is not None: 

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

2995 if _type is not None: 

2996 pieces.extend([b"TYPE", _type]) 

2997 return self.execute_command("SCAN", *pieces, **kwargs) 

2998 

2999 def scan_iter( 

3000 self, 

3001 match: Union[PatternT, None] = None, 

3002 count: Optional[int] = None, 

3003 _type: Optional[str] = None, 

3004 **kwargs, 

3005 ) -> Iterator: 

3006 """ 

3007 Make an iterator using the SCAN command so that the client doesn't 

3008 need to remember the cursor position. 

3009 

3010 ``match`` allows for filtering the keys by pattern 

3011 

3012 ``count`` provides a hint to Redis about the number of keys to 

3013 return per batch. 

3014 

3015 ``_type`` filters the returned values by a particular Redis type. 

3016 Stock Redis instances allow for the following types: 

3017 HASH, LIST, SET, STREAM, STRING, ZSET 

3018 Additionally, Redis modules can expose other types as well. 

3019 """ 

3020 cursor = "0" 

3021 while cursor != 0: 

3022 cursor, data = self.scan( 

3023 cursor=cursor, match=match, count=count, _type=_type, **kwargs 

3024 ) 

3025 yield from data 

3026 

3027 def sscan( 

3028 self, 

3029 name: KeyT, 

3030 cursor: int = 0, 

3031 match: Union[PatternT, None] = None, 

3032 count: Optional[int] = None, 

3033 ) -> ResponseT: 

3034 """ 

3035 Incrementally return lists of elements in a set. Also return a cursor 

3036 indicating the scan position. 

3037 

3038 ``match`` allows for filtering the keys by pattern 

3039 

3040 ``count`` allows for hint the minimum number of returns 

3041 

3042 For more information, see https://redis.io/commands/sscan 

3043 """ 

3044 pieces: list[EncodableT] = [name, cursor] 

3045 if match is not None: 

3046 pieces.extend([b"MATCH", match]) 

3047 if count is not None: 

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

3049 return self.execute_command("SSCAN", *pieces) 

3050 

3051 def sscan_iter( 

3052 self, 

3053 name: KeyT, 

3054 match: Union[PatternT, None] = None, 

3055 count: Optional[int] = None, 

3056 ) -> Iterator: 

3057 """ 

3058 Make an iterator using the SSCAN command so that the client doesn't 

3059 need to remember the cursor position. 

3060 

3061 ``match`` allows for filtering the keys by pattern 

3062 

3063 ``count`` allows for hint the minimum number of returns 

3064 """ 

3065 cursor = "0" 

3066 while cursor != 0: 

3067 cursor, data = self.sscan(name, cursor=cursor, match=match, count=count) 

3068 yield from data 

3069 

3070 def hscan( 

3071 self, 

3072 name: KeyT, 

3073 cursor: int = 0, 

3074 match: Union[PatternT, None] = None, 

3075 count: Optional[int] = None, 

3076 no_values: Union[bool, None] = None, 

3077 ) -> ResponseT: 

3078 """ 

3079 Incrementally return key/value slices in a hash. Also return a cursor 

3080 indicating the scan position. 

3081 

3082 ``match`` allows for filtering the keys by pattern 

3083 

3084 ``count`` allows for hint the minimum number of returns 

3085 

3086 ``no_values`` indicates to return only the keys, without values. 

3087 

3088 For more information, see https://redis.io/commands/hscan 

3089 """ 

3090 pieces: list[EncodableT] = [name, cursor] 

3091 if match is not None: 

3092 pieces.extend([b"MATCH", match]) 

3093 if count is not None: 

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

3095 if no_values is not None: 

3096 pieces.extend([b"NOVALUES"]) 

3097 return self.execute_command("HSCAN", *pieces, no_values=no_values) 

3098 

3099 def hscan_iter( 

3100 self, 

3101 name: str, 

3102 match: Union[PatternT, None] = None, 

3103 count: Optional[int] = None, 

3104 no_values: Union[bool, None] = None, 

3105 ) -> Iterator: 

3106 """ 

3107 Make an iterator using the HSCAN command so that the client doesn't 

3108 need to remember the cursor position. 

3109 

3110 ``match`` allows for filtering the keys by pattern 

3111 

3112 ``count`` allows for hint the minimum number of returns 

3113 

3114 ``no_values`` indicates to return only the keys, without values 

3115 """ 

3116 cursor = "0" 

3117 while cursor != 0: 

3118 cursor, data = self.hscan( 

3119 name, cursor=cursor, match=match, count=count, no_values=no_values 

3120 ) 

3121 if no_values: 

3122 yield from data 

3123 else: 

3124 yield from data.items() 

3125 

3126 def zscan( 

3127 self, 

3128 name: KeyT, 

3129 cursor: int = 0, 

3130 match: Union[PatternT, None] = None, 

3131 count: Optional[int] = None, 

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

3133 ) -> ResponseT: 

3134 """ 

3135 Incrementally return lists of elements in a sorted set. Also return a 

3136 cursor indicating the scan position. 

3137 

3138 ``match`` allows for filtering the keys by pattern 

3139 

3140 ``count`` allows for hint the minimum number of returns 

3141 

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

3143 

3144 For more information, see https://redis.io/commands/zscan 

3145 """ 

3146 pieces = [name, cursor] 

3147 if match is not None: 

3148 pieces.extend([b"MATCH", match]) 

3149 if count is not None: 

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

3151 options = {"score_cast_func": score_cast_func} 

3152 return self.execute_command("ZSCAN", *pieces, **options) 

3153 

3154 def zscan_iter( 

3155 self, 

3156 name: KeyT, 

3157 match: Union[PatternT, None] = None, 

3158 count: Optional[int] = None, 

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

3160 ) -> Iterator: 

3161 """ 

3162 Make an iterator using the ZSCAN command so that the client doesn't 

3163 need to remember the cursor position. 

3164 

3165 ``match`` allows for filtering the keys by pattern 

3166 

3167 ``count`` allows for hint the minimum number of returns 

3168 

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

3170 """ 

3171 cursor = "0" 

3172 while cursor != 0: 

3173 cursor, data = self.zscan( 

3174 name, 

3175 cursor=cursor, 

3176 match=match, 

3177 count=count, 

3178 score_cast_func=score_cast_func, 

3179 ) 

3180 yield from data 

3181 

3182 

3183class AsyncScanCommands(ScanCommands): 

3184 async def scan_iter( 

3185 self, 

3186 match: Union[PatternT, None] = None, 

3187 count: Optional[int] = None, 

3188 _type: Optional[str] = None, 

3189 **kwargs, 

3190 ) -> AsyncIterator: 

3191 """ 

3192 Make an iterator using the SCAN command so that the client doesn't 

3193 need to remember the cursor position. 

3194 

3195 ``match`` allows for filtering the keys by pattern 

3196 

3197 ``count`` provides a hint to Redis about the number of keys to 

3198 return per batch. 

3199 

3200 ``_type`` filters the returned values by a particular Redis type. 

3201 Stock Redis instances allow for the following types: 

3202 HASH, LIST, SET, STREAM, STRING, ZSET 

3203 Additionally, Redis modules can expose other types as well. 

3204 """ 

3205 cursor = "0" 

3206 while cursor != 0: 

3207 cursor, data = await self.scan( 

3208 cursor=cursor, match=match, count=count, _type=_type, **kwargs 

3209 ) 

3210 for d in data: 

3211 yield d 

3212 

3213 async def sscan_iter( 

3214 self, 

3215 name: KeyT, 

3216 match: Union[PatternT, None] = None, 

3217 count: Optional[int] = None, 

3218 ) -> AsyncIterator: 

3219 """ 

3220 Make an iterator using the SSCAN command so that the client doesn't 

3221 need to remember the cursor position. 

3222 

3223 ``match`` allows for filtering the keys by pattern 

3224 

3225 ``count`` allows for hint the minimum number of returns 

3226 """ 

3227 cursor = "0" 

3228 while cursor != 0: 

3229 cursor, data = await self.sscan( 

3230 name, cursor=cursor, match=match, count=count 

3231 ) 

3232 for d in data: 

3233 yield d 

3234 

3235 async def hscan_iter( 

3236 self, 

3237 name: str, 

3238 match: Union[PatternT, None] = None, 

3239 count: Optional[int] = None, 

3240 no_values: Union[bool, None] = None, 

3241 ) -> AsyncIterator: 

3242 """ 

3243 Make an iterator using the HSCAN command so that the client doesn't 

3244 need to remember the cursor position. 

3245 

3246 ``match`` allows for filtering the keys by pattern 

3247 

3248 ``count`` allows for hint the minimum number of returns 

3249 

3250 ``no_values`` indicates to return only the keys, without values 

3251 """ 

3252 cursor = "0" 

3253 while cursor != 0: 

3254 cursor, data = await self.hscan( 

3255 name, cursor=cursor, match=match, count=count, no_values=no_values 

3256 ) 

3257 if no_values: 

3258 for it in data: 

3259 yield it 

3260 else: 

3261 for it in data.items(): 

3262 yield it 

3263 

3264 async def zscan_iter( 

3265 self, 

3266 name: KeyT, 

3267 match: Union[PatternT, None] = None, 

3268 count: Optional[int] = None, 

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

3270 ) -> AsyncIterator: 

3271 """ 

3272 Make an iterator using the ZSCAN command so that the client doesn't 

3273 need to remember the cursor position. 

3274 

3275 ``match`` allows for filtering the keys by pattern 

3276 

3277 ``count`` allows for hint the minimum number of returns 

3278 

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

3280 """ 

3281 cursor = "0" 

3282 while cursor != 0: 

3283 cursor, data = await self.zscan( 

3284 name, 

3285 cursor=cursor, 

3286 match=match, 

3287 count=count, 

3288 score_cast_func=score_cast_func, 

3289 ) 

3290 for d in data: 

3291 yield d 

3292 

3293 

3294class SetCommands(CommandsProtocol): 

3295 """ 

3296 Redis commands for Set data type. 

3297 see: https://redis.io/topics/data-types#sets 

3298 """ 

3299 

3300 def sadd(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]: 

3301 """ 

3302 Add ``value(s)`` to set ``name`` 

3303 

3304 For more information, see https://redis.io/commands/sadd 

3305 """ 

3306 return self.execute_command("SADD", name, *values) 

3307 

3308 def scard(self, name: KeyT) -> Union[Awaitable[int], int]: 

3309 """ 

3310 Return the number of elements in set ``name`` 

3311 

3312 For more information, see https://redis.io/commands/scard 

3313 """ 

3314 return self.execute_command("SCARD", name, keys=[name]) 

3315 

3316 def sdiff(self, keys: List, *args: List) -> Union[Awaitable[list], list]: 

3317 """ 

3318 Return the difference of sets specified by ``keys`` 

3319 

3320 For more information, see https://redis.io/commands/sdiff 

3321 """ 

3322 args = list_or_args(keys, args) 

3323 return self.execute_command("SDIFF", *args, keys=args) 

3324 

3325 def sdiffstore( 

3326 self, dest: str, keys: List, *args: List 

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

3328 """ 

3329 Store the difference of sets specified by ``keys`` into a new 

3330 set named ``dest``. Returns the number of keys in the new set. 

3331 

3332 For more information, see https://redis.io/commands/sdiffstore 

3333 """ 

3334 args = list_or_args(keys, args) 

3335 return self.execute_command("SDIFFSTORE", dest, *args) 

3336 

3337 def sinter(self, keys: List, *args: List) -> Union[Awaitable[list], list]: 

3338 """ 

3339 Return the intersection of sets specified by ``keys`` 

3340 

3341 For more information, see https://redis.io/commands/sinter 

3342 """ 

3343 args = list_or_args(keys, args) 

3344 return self.execute_command("SINTER", *args, keys=args) 

3345 

3346 def sintercard( 

3347 self, numkeys: int, keys: List[KeyT], limit: int = 0 

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

3349 """ 

3350 Return the cardinality of the intersect of multiple sets specified by ``keys``. 

3351 

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

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

3354 exit and yield limit as the cardinality 

3355 

3356 For more information, see https://redis.io/commands/sintercard 

3357 """ 

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

3359 return self.execute_command("SINTERCARD", *args, keys=keys) 

3360 

3361 def sinterstore( 

3362 self, dest: KeyT, keys: List, *args: List 

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

3364 """ 

3365 Store the intersection of sets specified by ``keys`` into a new 

3366 set named ``dest``. Returns the number of keys in the new set. 

3367 

3368 For more information, see https://redis.io/commands/sinterstore 

3369 """ 

3370 args = list_or_args(keys, args) 

3371 return self.execute_command("SINTERSTORE", dest, *args) 

3372 

3373 def sismember( 

3374 self, name: KeyT, value: str 

3375 ) -> Union[Awaitable[Union[Literal[0], Literal[1]]], Union[Literal[0], Literal[1]]]: 

3376 """ 

3377 Return whether ``value`` is a member of set ``name``: 

3378 - 1 if the value is a member of the set. 

3379 - 0 if the value is not a member of the set or if key does not exist. 

3380 

3381 For more information, see https://redis.io/commands/sismember 

3382 """ 

3383 return self.execute_command("SISMEMBER", name, value, keys=[name]) 

3384 

3385 def smembers(self, name: KeyT) -> Union[Awaitable[Set], Set]: 

3386 """ 

3387 Return all members of the set ``name`` 

3388 

3389 For more information, see https://redis.io/commands/smembers 

3390 """ 

3391 return self.execute_command("SMEMBERS", name, keys=[name]) 

3392 

3393 def smismember( 

3394 self, name: KeyT, values: List, *args: List 

3395 ) -> Union[ 

3396 Awaitable[List[Union[Literal[0], Literal[1]]]], 

3397 List[Union[Literal[0], Literal[1]]], 

3398 ]: 

3399 """ 

3400 Return whether each value in ``values`` is a member of the set ``name`` 

3401 as a list of ``int`` in the order of ``values``: 

3402 - 1 if the value is a member of the set. 

3403 - 0 if the value is not a member of the set or if key does not exist. 

3404 

3405 For more information, see https://redis.io/commands/smismember 

3406 """ 

3407 args = list_or_args(values, args) 

3408 return self.execute_command("SMISMEMBER", name, *args, keys=[name]) 

3409 

3410 def smove(self, src: KeyT, dst: KeyT, value: str) -> Union[Awaitable[bool], bool]: 

3411 """ 

3412 Move ``value`` from set ``src`` to set ``dst`` atomically 

3413 

3414 For more information, see https://redis.io/commands/smove 

3415 """ 

3416 return self.execute_command("SMOVE", src, dst, value) 

3417 

3418 def spop(self, name: KeyT, count: Optional[int] = None) -> Union[str, List, None]: 

3419 """ 

3420 Remove and return a random member of set ``name`` 

3421 

3422 For more information, see https://redis.io/commands/spop 

3423 """ 

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

3425 return self.execute_command("SPOP", name, *args) 

3426 

3427 def srandmember( 

3428 self, name: KeyT, number: Optional[int] = None 

3429 ) -> Union[str, List, None]: 

3430 """ 

3431 If ``number`` is None, returns a random member of set ``name``. 

3432 

3433 If ``number`` is supplied, returns a list of ``number`` random 

3434 members of set ``name``. Note this is only available when running 

3435 Redis 2.6+. 

3436 

3437 For more information, see https://redis.io/commands/srandmember 

3438 """ 

3439 args = (number is not None) and [number] or [] 

3440 return self.execute_command("SRANDMEMBER", name, *args) 

3441 

3442 def srem(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]: 

3443 """ 

3444 Remove ``values`` from set ``name`` 

3445 

3446 For more information, see https://redis.io/commands/srem 

3447 """ 

3448 return self.execute_command("SREM", name, *values) 

3449 

3450 def sunion(self, keys: List, *args: List) -> Union[Awaitable[List], List]: 

3451 """ 

3452 Return the union of sets specified by ``keys`` 

3453 

3454 For more information, see https://redis.io/commands/sunion 

3455 """ 

3456 args = list_or_args(keys, args) 

3457 return self.execute_command("SUNION", *args, keys=args) 

3458 

3459 def sunionstore( 

3460 self, dest: KeyT, keys: List, *args: List 

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

3462 """ 

3463 Store the union of sets specified by ``keys`` into a new 

3464 set named ``dest``. Returns the number of keys in the new set. 

3465 

3466 For more information, see https://redis.io/commands/sunionstore 

3467 """ 

3468 args = list_or_args(keys, args) 

3469 return self.execute_command("SUNIONSTORE", dest, *args) 

3470 

3471 

3472AsyncSetCommands = SetCommands 

3473 

3474 

3475class StreamCommands(CommandsProtocol): 

3476 """ 

3477 Redis commands for Stream data type. 

3478 see: https://redis.io/topics/streams-intro 

3479 """ 

3480 

3481 def xack(self, name: KeyT, groupname: GroupT, *ids: StreamIdT) -> ResponseT: 

3482 """ 

3483 Acknowledges the successful processing of one or more messages. 

3484 

3485 Args: 

3486 name: name of the stream. 

3487 groupname: name of the consumer group. 

3488 *ids: message ids to acknowledge. 

3489 

3490 For more information, see https://redis.io/commands/xack 

3491 """ 

3492 return self.execute_command("XACK", name, groupname, *ids) 

3493 

3494 def xackdel( 

3495 self, 

3496 name: KeyT, 

3497 groupname: GroupT, 

3498 *ids: StreamIdT, 

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

3500 ) -> ResponseT: 

3501 """ 

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

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

3504 delete the corresponding entries from the stream. 

3505 """ 

3506 if not ids: 

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

3508 

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

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

3511 

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

3513 pieces.extend(ids) 

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

3515 

3516 def xadd( 

3517 self, 

3518 name: KeyT, 

3519 fields: Dict[FieldT, EncodableT], 

3520 id: StreamIdT = "*", 

3521 maxlen: Optional[int] = None, 

3522 approximate: bool = True, 

3523 nomkstream: bool = False, 

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

3525 limit: Optional[int] = None, 

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

3527 ) -> ResponseT: 

3528 """ 

3529 Add to a stream. 

3530 name: name of the stream 

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

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

3533 maxlen: truncate old stream members beyond this size. 

3534 Can't be specified with minid. 

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

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

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

3538 Can't be specified with maxlen. 

3539 limit: specifies the maximum number of entries to retrieve 

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

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

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

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

3544 

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

3546 """ 

3547 pieces: list[EncodableT] = [] 

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

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

3550 

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

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

3553 

3554 if maxlen is not None: 

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

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

3557 pieces.append(b"MAXLEN") 

3558 if approximate: 

3559 pieces.append(b"~") 

3560 pieces.append(str(maxlen)) 

3561 if minid is not None: 

3562 pieces.append(b"MINID") 

3563 if approximate: 

3564 pieces.append(b"~") 

3565 pieces.append(minid) 

3566 if limit is not None: 

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

3568 if nomkstream: 

3569 pieces.append(b"NOMKSTREAM") 

3570 if ref_policy is not None: 

3571 pieces.append(ref_policy) 

3572 pieces.append(id) 

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

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

3575 for pair in fields.items(): 

3576 pieces.extend(pair) 

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

3578 

3579 def xautoclaim( 

3580 self, 

3581 name: KeyT, 

3582 groupname: GroupT, 

3583 consumername: ConsumerT, 

3584 min_idle_time: int, 

3585 start_id: StreamIdT = "0-0", 

3586 count: Optional[int] = None, 

3587 justid: bool = False, 

3588 ) -> ResponseT: 

3589 """ 

3590 Transfers ownership of pending stream entries that match the specified 

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

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

3593 failures via SCAN-like semantics. 

3594 name: name of the stream. 

3595 groupname: name of the consumer group. 

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

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

3598 milliseconds. 

3599 start_id: filter messages with equal or greater ID. 

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

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

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

3603 of messages successfully claimed, without returning the actual message 

3604 

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

3606 """ 

3607 try: 

3608 if int(min_idle_time) < 0: 

3609 raise DataError( 

3610 "XAUTOCLAIM min_idle_time must be a nonnegative integer" 

3611 ) 

3612 except TypeError: 

3613 pass 

3614 

3615 kwargs = {} 

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

3617 

3618 try: 

3619 if int(count) < 0: 

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

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

3622 except TypeError: 

3623 pass 

3624 if justid: 

3625 pieces.append(b"JUSTID") 

3626 kwargs["parse_justid"] = True 

3627 

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

3629 

3630 def xclaim( 

3631 self, 

3632 name: KeyT, 

3633 groupname: GroupT, 

3634 consumername: ConsumerT, 

3635 min_idle_time: int, 

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

3637 idle: Optional[int] = None, 

3638 time: Optional[int] = None, 

3639 retrycount: Optional[int] = None, 

3640 force: bool = False, 

3641 justid: bool = False, 

3642 ) -> ResponseT: 

3643 """ 

3644 Changes the ownership of a pending message. 

3645 

3646 name: name of the stream. 

3647 

3648 groupname: name of the consumer group. 

3649 

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

3651 

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

3653 milliseconds 

3654 

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

3656 

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

3658 message in ms 

3659 

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

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

3662 Unix time (in milliseconds). 

3663 

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

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

3666 again. 

3667 

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

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

3670 PEL assigned to a different client. 

3671 

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

3673 of messages successfully claimed, without returning the actual message 

3674 

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

3676 """ 

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

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

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

3680 raise DataError( 

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

3682 "tuple of message IDs to claim" 

3683 ) 

3684 

3685 kwargs = {} 

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

3687 pieces.extend(list(message_ids)) 

3688 

3689 if idle is not None: 

3690 if not isinstance(idle, int): 

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

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

3693 if time is not None: 

3694 if not isinstance(time, int): 

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

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

3697 if retrycount is not None: 

3698 if not isinstance(retrycount, int): 

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

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

3701 

3702 if force: 

3703 if not isinstance(force, bool): 

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

3705 pieces.append(b"FORCE") 

3706 if justid: 

3707 if not isinstance(justid, bool): 

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

3709 pieces.append(b"JUSTID") 

3710 kwargs["parse_justid"] = True 

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

3712 

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

3714 """ 

3715 Deletes one or more messages from a stream. 

3716 

3717 Args: 

3718 name: name of the stream. 

3719 *ids: message ids to delete. 

3720 

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

3722 """ 

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

3724 

3725 def xdelex( 

3726 self, 

3727 name: KeyT, 

3728 *ids: StreamIdT, 

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

3730 ) -> ResponseT: 

3731 """ 

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

3733 are deleted concerning consumer groups. 

3734 """ 

3735 if not ids: 

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

3737 

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

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

3740 

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

3742 pieces.extend(ids) 

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

3744 

3745 def xgroup_create( 

3746 self, 

3747 name: KeyT, 

3748 groupname: GroupT, 

3749 id: StreamIdT = "$", 

3750 mkstream: bool = False, 

3751 entries_read: Optional[int] = None, 

3752 ) -> ResponseT: 

3753 """ 

3754 Create a new consumer group associated with a stream. 

3755 name: name of the stream. 

3756 groupname: name of the consumer group. 

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

3758 

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

3760 """ 

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

3762 if mkstream: 

3763 pieces.append(b"MKSTREAM") 

3764 if entries_read is not None: 

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

3766 

3767 return self.execute_command(*pieces) 

3768 

3769 def xgroup_delconsumer( 

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

3771 ) -> ResponseT: 

3772 """ 

3773 Remove a specific consumer from a consumer group. 

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

3775 was deleted. 

3776 name: name of the stream. 

3777 groupname: name of the consumer group. 

3778 consumername: name of consumer to delete 

3779 

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

3781 """ 

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

3783 

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

3785 """ 

3786 Destroy a consumer group. 

3787 name: name of the stream. 

3788 groupname: name of the consumer group. 

3789 

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

3791 """ 

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

3793 

3794 def xgroup_createconsumer( 

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

3796 ) -> ResponseT: 

3797 """ 

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

3799 consumer name is mentioned by some command. 

3800 They can be explicitly created by using this command. 

3801 name: name of the stream. 

3802 groupname: name of the consumer group. 

3803 consumername: name of consumer to create. 

3804 

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

3806 """ 

3807 return self.execute_command( 

3808 "XGROUP CREATECONSUMER", name, groupname, consumername 

3809 ) 

3810 

3811 def xgroup_setid( 

3812 self, 

3813 name: KeyT, 

3814 groupname: GroupT, 

3815 id: StreamIdT, 

3816 entries_read: Optional[int] = None, 

3817 ) -> ResponseT: 

3818 """ 

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

3820 name: name of the stream. 

3821 groupname: name of the consumer group. 

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

3823 

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

3825 """ 

3826 pieces = [name, groupname, id] 

3827 if entries_read is not None: 

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

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

3830 

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

3832 """ 

3833 Returns general information about the consumers in the group. 

3834 name: name of the stream. 

3835 groupname: name of the consumer group. 

3836 

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

3838 """ 

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

3840 

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

3842 """ 

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

3844 name: name of the stream. 

3845 

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

3847 """ 

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

3849 

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

3851 """ 

3852 Returns general information about the stream. 

3853 name: name of the stream. 

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

3855 

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

3857 """ 

3858 pieces = [name] 

3859 options = {} 

3860 if full: 

3861 pieces.append(b"FULL") 

3862 options = {"full": full} 

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

3864 

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

3866 """ 

3867 Returns the number of elements in a given stream. 

3868 

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

3870 """ 

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

3872 

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

3874 """ 

3875 Returns information about pending messages of a group. 

3876 name: name of the stream. 

3877 groupname: name of the consumer group. 

3878 

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

3880 """ 

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

3882 

3883 def xpending_range( 

3884 self, 

3885 name: KeyT, 

3886 groupname: GroupT, 

3887 min: StreamIdT, 

3888 max: StreamIdT, 

3889 count: int, 

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

3891 idle: Optional[int] = None, 

3892 ) -> ResponseT: 

3893 """ 

3894 Returns information about pending messages, in a range. 

3895 

3896 name: name of the stream. 

3897 groupname: name of the consumer group. 

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

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

3900 min: minimum stream ID. 

3901 max: maximum stream ID. 

3902 count: number of messages to return 

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

3904 """ 

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

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

3907 raise DataError( 

3908 "if XPENDING is provided with idle time" 

3909 " or consumername, it must be provided" 

3910 " with min, max and count parameters" 

3911 ) 

3912 return self.xpending(name, groupname) 

3913 

3914 pieces = [name, groupname] 

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

3916 raise DataError( 

3917 "XPENDING must be provided with min, max " 

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

3919 ) 

3920 # idle 

3921 try: 

3922 if int(idle) < 0: 

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

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

3925 except TypeError: 

3926 pass 

3927 # count 

3928 try: 

3929 if int(count) < 0: 

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

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

3932 except TypeError: 

3933 pass 

3934 # consumername 

3935 if consumername: 

3936 pieces.append(consumername) 

3937 

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

3939 

3940 def xrange( 

3941 self, 

3942 name: KeyT, 

3943 min: StreamIdT = "-", 

3944 max: StreamIdT = "+", 

3945 count: Optional[int] = None, 

3946 ) -> ResponseT: 

3947 """ 

3948 Read stream values within an interval. 

3949 

3950 name: name of the stream. 

3951 

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

3953 meaning the earliest available. 

3954 

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

3956 meaning the latest available. 

3957 

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

3959 earliest available. 

3960 

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

3962 """ 

3963 pieces = [min, max] 

3964 if count is not None: 

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

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

3967 pieces.append(b"COUNT") 

3968 pieces.append(str(count)) 

3969 

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

3971 

3972 def xread( 

3973 self, 

3974 streams: Dict[KeyT, StreamIdT], 

3975 count: Optional[int] = None, 

3976 block: Optional[int] = None, 

3977 ) -> ResponseT: 

3978 """ 

3979 Block and monitor multiple streams for new data. 

3980 

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

3982 IDs indicate the last ID already seen. 

3983 

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

3985 earliest available. 

3986 

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

3988 

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

3990 """ 

3991 pieces = [] 

3992 if block is not None: 

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

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

3995 pieces.append(b"BLOCK") 

3996 pieces.append(str(block)) 

3997 if count is not None: 

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

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

4000 pieces.append(b"COUNT") 

4001 pieces.append(str(count)) 

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

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

4004 pieces.append(b"STREAMS") 

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

4006 pieces.extend(keys) 

4007 pieces.extend(values) 

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

4009 

4010 def xreadgroup( 

4011 self, 

4012 groupname: str, 

4013 consumername: str, 

4014 streams: Dict[KeyT, StreamIdT], 

4015 count: Optional[int] = None, 

4016 block: Optional[int] = None, 

4017 noack: bool = False, 

4018 ) -> ResponseT: 

4019 """ 

4020 Read from a stream via a consumer group. 

4021 

4022 groupname: name of the consumer group. 

4023 

4024 consumername: name of the requesting consumer. 

4025 

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

4027 IDs indicate the last ID already seen. 

4028 

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

4030 earliest available. 

4031 

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

4033 noack: do not add messages to the PEL 

4034 

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

4036 """ 

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

4038 if count is not None: 

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

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

4041 pieces.append(b"COUNT") 

4042 pieces.append(str(count)) 

4043 if block is not None: 

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

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

4046 pieces.append(b"BLOCK") 

4047 pieces.append(str(block)) 

4048 if noack: 

4049 pieces.append(b"NOACK") 

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

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

4052 pieces.append(b"STREAMS") 

4053 pieces.extend(streams.keys()) 

4054 pieces.extend(streams.values()) 

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

4056 

4057 def xrevrange( 

4058 self, 

4059 name: KeyT, 

4060 max: StreamIdT = "+", 

4061 min: StreamIdT = "-", 

4062 count: Optional[int] = None, 

4063 ) -> ResponseT: 

4064 """ 

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

4066 

4067 name: name of the stream 

4068 

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

4070 meaning the latest available. 

4071 

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

4073 meaning the earliest available. 

4074 

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

4076 latest available. 

4077 

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

4079 """ 

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

4081 if count is not None: 

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

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

4084 pieces.append(b"COUNT") 

4085 pieces.append(str(count)) 

4086 

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

4088 

4089 def xtrim( 

4090 self, 

4091 name: KeyT, 

4092 maxlen: Optional[int] = None, 

4093 approximate: bool = True, 

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

4095 limit: Optional[int] = None, 

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

4097 ) -> ResponseT: 

4098 """ 

4099 Trims old messages from a stream. 

4100 name: name of the stream. 

4101 maxlen: truncate old stream messages beyond this size 

4102 Can't be specified with minid. 

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

4104 minid: the minimum id in the stream to query 

4105 Can't be specified with maxlen. 

4106 limit: specifies the maximum number of entries to retrieve 

4107 ref_policy: optional reference policy for consumer groups: 

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

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

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

4111 

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

4113 """ 

4114 pieces: list[EncodableT] = [] 

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

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

4117 

4118 if maxlen is None and minid is None: 

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

4120 

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

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

4123 

4124 if maxlen is not None: 

4125 pieces.append(b"MAXLEN") 

4126 if minid is not None: 

4127 pieces.append(b"MINID") 

4128 if approximate: 

4129 pieces.append(b"~") 

4130 if maxlen is not None: 

4131 pieces.append(maxlen) 

4132 if minid is not None: 

4133 pieces.append(minid) 

4134 if limit is not None: 

4135 pieces.append(b"LIMIT") 

4136 pieces.append(limit) 

4137 if ref_policy is not None: 

4138 pieces.append(ref_policy) 

4139 

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

4141 

4142 

4143AsyncStreamCommands = StreamCommands 

4144 

4145 

4146class SortedSetCommands(CommandsProtocol): 

4147 """ 

4148 Redis commands for Sorted Sets data type. 

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

4150 """ 

4151 

4152 def zadd( 

4153 self, 

4154 name: KeyT, 

4155 mapping: Mapping[AnyKeyT, EncodableT], 

4156 nx: bool = False, 

4157 xx: bool = False, 

4158 ch: bool = False, 

4159 incr: bool = False, 

4160 gt: bool = False, 

4161 lt: bool = False, 

4162 ) -> ResponseT: 

4163 """ 

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

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

4166 

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

4168 scores for elements that already exist. 

4169 

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

4171 exist. New elements will not be added. 

4172 

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

4174 Changed elements include new elements that were added and elements 

4175 whose scores changed. 

4176 

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

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

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

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

4181 

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

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

4184 

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

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

4187 

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

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

4190 set. 

4191 

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

4193 

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

4195 """ 

4196 if not mapping: 

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

4198 if nx and xx: 

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

4200 if gt and lt: 

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

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

4203 raise DataError( 

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

4205 ) 

4206 if nx and (gt or lt): 

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

4208 

4209 pieces: list[EncodableT] = [] 

4210 options = {} 

4211 if nx: 

4212 pieces.append(b"NX") 

4213 if xx: 

4214 pieces.append(b"XX") 

4215 if ch: 

4216 pieces.append(b"CH") 

4217 if incr: 

4218 pieces.append(b"INCR") 

4219 options["as_score"] = True 

4220 if gt: 

4221 pieces.append(b"GT") 

4222 if lt: 

4223 pieces.append(b"LT") 

4224 for pair in mapping.items(): 

4225 pieces.append(pair[1]) 

4226 pieces.append(pair[0]) 

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

4228 

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

4230 """ 

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

4232 

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

4234 """ 

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

4236 

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

4238 """ 

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

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

4241 

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

4243 """ 

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

4245 

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

4247 """ 

4248 Returns the difference between the first and all successive input 

4249 sorted sets provided in ``keys``. 

4250 

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

4252 """ 

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

4254 if withscores: 

4255 pieces.append("WITHSCORES") 

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

4257 

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

4259 """ 

4260 Computes the difference between the first and all successive input 

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

4262 

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

4264 """ 

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

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

4267 

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

4269 """ 

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

4271 

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

4273 """ 

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

4275 

4276 def zinter( 

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

4278 ) -> ResponseT: 

4279 """ 

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

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

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

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

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

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

4286 the inputs where it exists. 

4287 

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

4289 """ 

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

4291 

4292 def zinterstore( 

4293 self, 

4294 dest: KeyT, 

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

4296 aggregate: Optional[str] = None, 

4297 ) -> ResponseT: 

4298 """ 

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

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

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

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

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

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

4305 where it exists. 

4306 

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

4308 """ 

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

4310 

4311 def zintercard( 

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

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

4314 """ 

4315 Return the cardinality of the intersect of multiple sorted sets 

4316 specified by ``keys``. 

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

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

4319 exit and yield limit as the cardinality 

4320 

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

4322 """ 

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

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

4325 

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

4327 """ 

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

4329 lexicographical range ``min`` and ``max``. 

4330 

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

4332 """ 

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

4334 

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

4336 """ 

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

4338 from the sorted set ``name``. 

4339 

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

4341 """ 

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

4343 options = {"withscores": True} 

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

4345 

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

4347 """ 

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

4349 from the sorted set ``name``. 

4350 

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

4352 """ 

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

4354 options = {"withscores": True} 

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

4356 

4357 def zrandmember( 

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

4359 ) -> ResponseT: 

4360 """ 

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

4362 

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

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

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

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

4367 of the specified count. 

4368 

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

4370 includes the respective scores of the randomly selected elements from 

4371 the sorted set. 

4372 

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

4374 """ 

4375 params = [] 

4376 if count is not None: 

4377 params.append(count) 

4378 if withscores: 

4379 params.append("WITHSCORES") 

4380 

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

4382 

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

4384 """ 

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

4386 named in the ``keys`` list. 

4387 

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

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

4390 to one of the sorted sets. 

4391 

4392 If timeout is 0, then block indefinitely. 

4393 

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

4395 """ 

4396 if timeout is None: 

4397 timeout = 0 

4398 keys = list_or_args(keys, None) 

4399 keys.append(timeout) 

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

4401 

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

4403 """ 

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

4405 named in the ``keys`` list. 

4406 

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

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

4409 to one of the sorted sets. 

4410 

4411 If timeout is 0, then block indefinitely. 

4412 

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

4414 """ 

4415 if timeout is None: 

4416 timeout = 0 

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

4418 keys.append(timeout) 

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

4420 

4421 def zmpop( 

4422 self, 

4423 num_keys: int, 

4424 keys: List[str], 

4425 min: Optional[bool] = False, 

4426 max: Optional[bool] = False, 

4427 count: Optional[int] = 1, 

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

4429 """ 

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

4431 named in the ``keys`` list. 

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

4433 """ 

4434 args = [num_keys] + keys 

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

4436 raise DataError 

4437 elif min: 

4438 args.append("MIN") 

4439 else: 

4440 args.append("MAX") 

4441 if count != 1: 

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

4443 

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

4445 

4446 def bzmpop( 

4447 self, 

4448 timeout: float, 

4449 numkeys: int, 

4450 keys: List[str], 

4451 min: Optional[bool] = False, 

4452 max: Optional[bool] = False, 

4453 count: Optional[int] = 1, 

4454 ) -> Optional[list]: 

4455 """ 

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

4457 named in the ``keys`` list. 

4458 

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

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

4461 to one of the sorted sets. 

4462 

4463 If timeout is 0, then block indefinitely. 

4464 

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

4466 """ 

4467 args = [timeout, numkeys, *keys] 

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

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

4470 elif min: 

4471 args.append("MIN") 

4472 else: 

4473 args.append("MAX") 

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

4475 

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

4477 

4478 def _zrange( 

4479 self, 

4480 command, 

4481 dest: Union[KeyT, None], 

4482 name: KeyT, 

4483 start: int, 

4484 end: int, 

4485 desc: bool = False, 

4486 byscore: bool = False, 

4487 bylex: bool = False, 

4488 withscores: bool = False, 

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

4490 offset: Optional[int] = None, 

4491 num: Optional[int] = None, 

4492 ) -> ResponseT: 

4493 if byscore and bylex: 

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

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

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

4497 if bylex and withscores: 

4498 raise DataError( 

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

4500 ) 

4501 pieces = [command] 

4502 if dest: 

4503 pieces.append(dest) 

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

4505 if byscore: 

4506 pieces.append("BYSCORE") 

4507 if bylex: 

4508 pieces.append("BYLEX") 

4509 if desc: 

4510 pieces.append("REV") 

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

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

4513 if withscores: 

4514 pieces.append("WITHSCORES") 

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

4516 options["keys"] = [name] 

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

4518 

4519 def zrange( 

4520 self, 

4521 name: KeyT, 

4522 start: int, 

4523 end: int, 

4524 desc: bool = False, 

4525 withscores: bool = False, 

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

4527 byscore: bool = False, 

4528 bylex: bool = False, 

4529 offset: Optional[int] = None, 

4530 num: Optional[int] = None, 

4531 ) -> ResponseT: 

4532 """ 

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

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

4535 

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

4537 

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

4539 order. 

4540 

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

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

4543 

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

4545 

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

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

4548 

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

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

4551 range intervals. 

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

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

4554 

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

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

4557 

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

4559 """ 

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

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

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

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

4564 

4565 return self._zrange( 

4566 "ZRANGE", 

4567 None, 

4568 name, 

4569 start, 

4570 end, 

4571 desc, 

4572 byscore, 

4573 bylex, 

4574 withscores, 

4575 score_cast_func, 

4576 offset, 

4577 num, 

4578 ) 

4579 

4580 def zrevrange( 

4581 self, 

4582 name: KeyT, 

4583 start: int, 

4584 end: int, 

4585 withscores: bool = False, 

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

4587 ) -> ResponseT: 

4588 """ 

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

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

4591 

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

4593 

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

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

4596 

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

4598 

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

4600 """ 

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

4602 if withscores: 

4603 pieces.append(b"WITHSCORES") 

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

4605 options["keys"] = name 

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

4607 

4608 def zrangestore( 

4609 self, 

4610 dest: KeyT, 

4611 name: KeyT, 

4612 start: int, 

4613 end: int, 

4614 byscore: bool = False, 

4615 bylex: bool = False, 

4616 desc: bool = False, 

4617 offset: Optional[int] = None, 

4618 num: Optional[int] = None, 

4619 ) -> ResponseT: 

4620 """ 

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

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

4623 

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

4625 

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

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

4628 

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

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

4631 range intervals. 

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

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

4634 

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

4636 order. 

4637 

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

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

4640 

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

4642 """ 

4643 return self._zrange( 

4644 "ZRANGESTORE", 

4645 dest, 

4646 name, 

4647 start, 

4648 end, 

4649 desc, 

4650 byscore, 

4651 bylex, 

4652 False, 

4653 None, 

4654 offset, 

4655 num, 

4656 ) 

4657 

4658 def zrangebylex( 

4659 self, 

4660 name: KeyT, 

4661 min: EncodableT, 

4662 max: EncodableT, 

4663 start: Optional[int] = None, 

4664 num: Optional[int] = None, 

4665 ) -> ResponseT: 

4666 """ 

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

4668 between ``min`` and ``max``. 

4669 

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

4671 range. 

4672 

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

4674 """ 

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

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

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

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

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

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

4681 

4682 def zrevrangebylex( 

4683 self, 

4684 name: KeyT, 

4685 max: EncodableT, 

4686 min: EncodableT, 

4687 start: Optional[int] = None, 

4688 num: Optional[int] = None, 

4689 ) -> ResponseT: 

4690 """ 

4691 Return the reversed lexicographical range of values from sorted set 

4692 ``name`` between ``max`` and ``min``. 

4693 

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

4695 range. 

4696 

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

4698 """ 

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

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

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

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

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

4704 return self.execute_command(*pieces, keys=[name]) 

4705 

4706 def zrangebyscore( 

4707 self, 

4708 name: KeyT, 

4709 min: ZScoreBoundT, 

4710 max: ZScoreBoundT, 

4711 start: Optional[int] = None, 

4712 num: Optional[int] = None, 

4713 withscores: bool = False, 

4714 score_cast_func: Union[type, Callable] = float, 

4715 ) -> ResponseT: 

4716 """ 

4717 Return a range of values from the sorted set ``name`` with scores 

4718 between ``min`` and ``max``. 

4719 

4720 If ``start`` and ``num`` are specified, then return a slice 

4721 of the range. 

4722 

4723 ``withscores`` indicates to return the scores along with the values. 

4724 The return type is a list of (value, score) pairs 

4725 

4726 `score_cast_func`` a callable used to cast the score return value 

4727 

4728 For more information, see https://redis.io/commands/zrangebyscore 

4729 """ 

4730 if (start is not None and num is None) or (num is not None and start is None): 

4731 raise DataError("``start`` and ``num`` must both be specified") 

4732 pieces = ["ZRANGEBYSCORE", name, min, max] 

4733 if start is not None and num is not None: 

4734 pieces.extend(["LIMIT", start, num]) 

4735 if withscores: 

4736 pieces.append("WITHSCORES") 

4737 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4738 options["keys"] = [name] 

4739 return self.execute_command(*pieces, **options) 

4740 

4741 def zrevrangebyscore( 

4742 self, 

4743 name: KeyT, 

4744 max: ZScoreBoundT, 

4745 min: ZScoreBoundT, 

4746 start: Optional[int] = None, 

4747 num: Optional[int] = None, 

4748 withscores: bool = False, 

4749 score_cast_func: Union[type, Callable] = float, 

4750 ): 

4751 """ 

4752 Return a range of values from the sorted set ``name`` with scores 

4753 between ``min`` and ``max`` in descending order. 

4754 

4755 If ``start`` and ``num`` are specified, then return a slice 

4756 of the range. 

4757 

4758 ``withscores`` indicates to return the scores along with the values. 

4759 The return type is a list of (value, score) pairs 

4760 

4761 ``score_cast_func`` a callable used to cast the score return value 

4762 

4763 For more information, see https://redis.io/commands/zrevrangebyscore 

4764 """ 

4765 if (start is not None and num is None) or (num is not None and start is None): 

4766 raise DataError("``start`` and ``num`` must both be specified") 

4767 pieces = ["ZREVRANGEBYSCORE", name, max, min] 

4768 if start is not None and num is not None: 

4769 pieces.extend(["LIMIT", start, num]) 

4770 if withscores: 

4771 pieces.append("WITHSCORES") 

4772 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4773 options["keys"] = [name] 

4774 return self.execute_command(*pieces, **options) 

4775 

4776 def zrank( 

4777 self, 

4778 name: KeyT, 

4779 value: EncodableT, 

4780 withscore: bool = False, 

4781 ) -> ResponseT: 

4782 """ 

4783 Returns a 0-based value indicating the rank of ``value`` in sorted set 

4784 ``name``. 

4785 The optional WITHSCORE argument supplements the command's 

4786 reply with the score of the element returned. 

4787 

4788 For more information, see https://redis.io/commands/zrank 

4789 """ 

4790 if withscore: 

4791 return self.execute_command("ZRANK", name, value, "WITHSCORE", keys=[name]) 

4792 return self.execute_command("ZRANK", name, value, keys=[name]) 

4793 

4794 def zrem(self, name: KeyT, *values: FieldT) -> ResponseT: 

4795 """ 

4796 Remove member ``values`` from sorted set ``name`` 

4797 

4798 For more information, see https://redis.io/commands/zrem 

4799 """ 

4800 return self.execute_command("ZREM", name, *values) 

4801 

4802 def zremrangebylex(self, name: KeyT, min: EncodableT, max: EncodableT) -> ResponseT: 

4803 """ 

4804 Remove all elements in the sorted set ``name`` between the 

4805 lexicographical range specified by ``min`` and ``max``. 

4806 

4807 Returns the number of elements removed. 

4808 

4809 For more information, see https://redis.io/commands/zremrangebylex 

4810 """ 

4811 return self.execute_command("ZREMRANGEBYLEX", name, min, max) 

4812 

4813 def zremrangebyrank(self, name: KeyT, min: int, max: int) -> ResponseT: 

4814 """ 

4815 Remove all elements in the sorted set ``name`` with ranks between 

4816 ``min`` and ``max``. Values are 0-based, ordered from smallest score 

4817 to largest. Values can be negative indicating the highest scores. 

4818 Returns the number of elements removed 

4819 

4820 For more information, see https://redis.io/commands/zremrangebyrank 

4821 """ 

4822 return self.execute_command("ZREMRANGEBYRANK", name, min, max) 

4823 

4824 def zremrangebyscore( 

4825 self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT 

4826 ) -> ResponseT: 

4827 """ 

4828 Remove all elements in the sorted set ``name`` with scores 

4829 between ``min`` and ``max``. Returns the number of elements removed. 

4830 

4831 For more information, see https://redis.io/commands/zremrangebyscore 

4832 """ 

4833 return self.execute_command("ZREMRANGEBYSCORE", name, min, max) 

4834 

4835 def zrevrank( 

4836 self, 

4837 name: KeyT, 

4838 value: EncodableT, 

4839 withscore: bool = False, 

4840 ) -> ResponseT: 

4841 """ 

4842 Returns a 0-based value indicating the descending rank of 

4843 ``value`` in sorted set ``name``. 

4844 The optional ``withscore`` argument supplements the command's 

4845 reply with the score of the element returned. 

4846 

4847 For more information, see https://redis.io/commands/zrevrank 

4848 """ 

4849 if withscore: 

4850 return self.execute_command( 

4851 "ZREVRANK", name, value, "WITHSCORE", keys=[name] 

4852 ) 

4853 return self.execute_command("ZREVRANK", name, value, keys=[name]) 

4854 

4855 def zscore(self, name: KeyT, value: EncodableT) -> ResponseT: 

4856 """ 

4857 Return the score of element ``value`` in sorted set ``name`` 

4858 

4859 For more information, see https://redis.io/commands/zscore 

4860 """ 

4861 return self.execute_command("ZSCORE", name, value, keys=[name]) 

4862 

4863 def zunion( 

4864 self, 

4865 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4866 aggregate: Optional[str] = None, 

4867 withscores: bool = False, 

4868 ) -> ResponseT: 

4869 """ 

4870 Return the union of multiple sorted sets specified by ``keys``. 

4871 ``keys`` can be provided as dictionary of keys and their weights. 

4872 Scores will be aggregated based on the ``aggregate``, or SUM if 

4873 none is provided. 

4874 

4875 For more information, see https://redis.io/commands/zunion 

4876 """ 

4877 return self._zaggregate("ZUNION", None, keys, aggregate, withscores=withscores) 

4878 

4879 def zunionstore( 

4880 self, 

4881 dest: KeyT, 

4882 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4883 aggregate: Optional[str] = None, 

4884 ) -> ResponseT: 

4885 """ 

4886 Union multiple sorted sets specified by ``keys`` into 

4887 a new sorted set, ``dest``. Scores in the destination will be 

4888 aggregated based on the ``aggregate``, or SUM if none is provided. 

4889 

4890 For more information, see https://redis.io/commands/zunionstore 

4891 """ 

4892 return self._zaggregate("ZUNIONSTORE", dest, keys, aggregate) 

4893 

4894 def zmscore(self, key: KeyT, members: List[str]) -> ResponseT: 

4895 """ 

4896 Returns the scores associated with the specified members 

4897 in the sorted set stored at key. 

4898 ``members`` should be a list of the member name. 

4899 Return type is a list of score. 

4900 If the member does not exist, a None will be returned 

4901 in corresponding position. 

4902 

4903 For more information, see https://redis.io/commands/zmscore 

4904 """ 

4905 if not members: 

4906 raise DataError("ZMSCORE members must be a non-empty list") 

4907 pieces = [key] + members 

4908 return self.execute_command("ZMSCORE", *pieces, keys=[key]) 

4909 

4910 def _zaggregate( 

4911 self, 

4912 command: str, 

4913 dest: Union[KeyT, None], 

4914 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4915 aggregate: Optional[str] = None, 

4916 **options, 

4917 ) -> ResponseT: 

4918 pieces: list[EncodableT] = [command] 

4919 if dest is not None: 

4920 pieces.append(dest) 

4921 pieces.append(len(keys)) 

4922 if isinstance(keys, dict): 

4923 keys, weights = keys.keys(), keys.values() 

4924 else: 

4925 weights = None 

4926 pieces.extend(keys) 

4927 if weights: 

4928 pieces.append(b"WEIGHTS") 

4929 pieces.extend(weights) 

4930 if aggregate: 

4931 if aggregate.upper() in ["SUM", "MIN", "MAX"]: 

4932 pieces.append(b"AGGREGATE") 

4933 pieces.append(aggregate) 

4934 else: 

4935 raise DataError("aggregate can be sum, min or max.") 

4936 if options.get("withscores", False): 

4937 pieces.append(b"WITHSCORES") 

4938 options["keys"] = keys 

4939 return self.execute_command(*pieces, **options) 

4940 

4941 

4942AsyncSortedSetCommands = SortedSetCommands 

4943 

4944 

4945class HyperlogCommands(CommandsProtocol): 

4946 """ 

4947 Redis commands of HyperLogLogs data type. 

4948 see: https://redis.io/topics/data-types-intro#hyperloglogs 

4949 """ 

4950 

4951 def pfadd(self, name: KeyT, *values: FieldT) -> ResponseT: 

4952 """ 

4953 Adds the specified elements to the specified HyperLogLog. 

4954 

4955 For more information, see https://redis.io/commands/pfadd 

4956 """ 

4957 return self.execute_command("PFADD", name, *values) 

4958 

4959 def pfcount(self, *sources: KeyT) -> ResponseT: 

4960 """ 

4961 Return the approximated cardinality of 

4962 the set observed by the HyperLogLog at key(s). 

4963 

4964 For more information, see https://redis.io/commands/pfcount 

4965 """ 

4966 return self.execute_command("PFCOUNT", *sources) 

4967 

4968 def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT: 

4969 """ 

4970 Merge N different HyperLogLogs into a single one. 

4971 

4972 For more information, see https://redis.io/commands/pfmerge 

4973 """ 

4974 return self.execute_command("PFMERGE", dest, *sources) 

4975 

4976 

4977AsyncHyperlogCommands = HyperlogCommands 

4978 

4979 

4980class HashDataPersistOptions(Enum): 

4981 # set the value for each provided key to each 

4982 # provided value only if all do not already exist. 

4983 FNX = "FNX" 

4984 

4985 # set the value for each provided key to each 

4986 # provided value only if all already exist. 

4987 FXX = "FXX" 

4988 

4989 

4990class HashCommands(CommandsProtocol): 

4991 """ 

4992 Redis commands for Hash data type. 

4993 see: https://redis.io/topics/data-types-intro#redis-hashes 

4994 """ 

4995 

4996 def hdel(self, name: str, *keys: str) -> Union[Awaitable[int], int]: 

4997 """ 

4998 Delete ``keys`` from hash ``name`` 

4999 

5000 For more information, see https://redis.io/commands/hdel 

5001 """ 

5002 return self.execute_command("HDEL", name, *keys) 

5003 

5004 def hexists(self, name: str, key: str) -> Union[Awaitable[bool], bool]: 

5005 """ 

5006 Returns a boolean indicating if ``key`` exists within hash ``name`` 

5007 

5008 For more information, see https://redis.io/commands/hexists 

5009 """ 

5010 return self.execute_command("HEXISTS", name, key, keys=[name]) 

5011 

5012 def hget( 

5013 self, name: str, key: str 

5014 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

5015 """ 

5016 Return the value of ``key`` within the hash ``name`` 

5017 

5018 For more information, see https://redis.io/commands/hget 

5019 """ 

5020 return self.execute_command("HGET", name, key, keys=[name]) 

5021 

5022 def hgetall(self, name: str) -> Union[Awaitable[dict], dict]: 

5023 """ 

5024 Return a Python dict of the hash's name/value pairs 

5025 

5026 For more information, see https://redis.io/commands/hgetall 

5027 """ 

5028 return self.execute_command("HGETALL", name, keys=[name]) 

5029 

5030 def hgetdel( 

5031 self, name: str, *keys: str 

5032 ) -> Union[ 

5033 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]] 

5034 ]: 

5035 """ 

5036 Return the value of ``key`` within the hash ``name`` and 

5037 delete the field in the hash. 

5038 This command is similar to HGET, except for the fact that it also deletes 

5039 the key on success from the hash with the provided ```name```. 

5040 

5041 Available since Redis 8.0 

5042 For more information, see https://redis.io/commands/hgetdel 

5043 """ 

5044 if len(keys) == 0: 

5045 raise DataError("'hgetdel' should have at least one key provided") 

5046 

5047 return self.execute_command("HGETDEL", name, "FIELDS", len(keys), *keys) 

5048 

5049 def hgetex( 

5050 self, 

5051 name: KeyT, 

5052 *keys: str, 

5053 ex: Optional[ExpiryT] = None, 

5054 px: Optional[ExpiryT] = None, 

5055 exat: Optional[AbsExpiryT] = None, 

5056 pxat: Optional[AbsExpiryT] = None, 

5057 persist: bool = False, 

5058 ) -> Union[ 

5059 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]] 

5060 ]: 

5061 """ 

5062 Return the values of ``key`` and ``keys`` within the hash ``name`` 

5063 and optionally set their expiration. 

5064 

5065 ``ex`` sets an expire flag on ``kyes`` for ``ex`` seconds. 

5066 

5067 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds. 

5068 

5069 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds, 

5070 specified in unix time. 

5071 

5072 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds, 

5073 specified in unix time. 

5074 

5075 ``persist`` remove the time to live associated with the ``keys``. 

5076 

5077 Available since Redis 8.0 

5078 For more information, see https://redis.io/commands/hgetex 

5079 """ 

5080 if not keys: 

5081 raise DataError("'hgetex' should have at least one key provided") 

5082 

5083 opset = {ex, px, exat, pxat} 

5084 if len(opset) > 2 or len(opset) > 1 and persist: 

5085 raise DataError( 

5086 "``ex``, ``px``, ``exat``, ``pxat``, " 

5087 "and ``persist`` are mutually exclusive." 

5088 ) 

5089 

5090 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat) 

5091 

5092 if persist: 

5093 exp_options.append("PERSIST") 

5094 

5095 return self.execute_command( 

5096 "HGETEX", 

5097 name, 

5098 *exp_options, 

5099 "FIELDS", 

5100 len(keys), 

5101 *keys, 

5102 ) 

5103 

5104 def hincrby( 

5105 self, name: str, key: str, amount: int = 1 

5106 ) -> Union[Awaitable[int], int]: 

5107 """ 

5108 Increment the value of ``key`` in hash ``name`` by ``amount`` 

5109 

5110 For more information, see https://redis.io/commands/hincrby 

5111 """ 

5112 return self.execute_command("HINCRBY", name, key, amount) 

5113 

5114 def hincrbyfloat( 

5115 self, name: str, key: str, amount: float = 1.0 

5116 ) -> Union[Awaitable[float], float]: 

5117 """ 

5118 Increment the value of ``key`` in hash ``name`` by floating ``amount`` 

5119 

5120 For more information, see https://redis.io/commands/hincrbyfloat 

5121 """ 

5122 return self.execute_command("HINCRBYFLOAT", name, key, amount) 

5123 

5124 def hkeys(self, name: str) -> Union[Awaitable[List], List]: 

5125 """ 

5126 Return the list of keys within hash ``name`` 

5127 

5128 For more information, see https://redis.io/commands/hkeys 

5129 """ 

5130 return self.execute_command("HKEYS", name, keys=[name]) 

5131 

5132 def hlen(self, name: str) -> Union[Awaitable[int], int]: 

5133 """ 

5134 Return the number of elements in hash ``name`` 

5135 

5136 For more information, see https://redis.io/commands/hlen 

5137 """ 

5138 return self.execute_command("HLEN", name, keys=[name]) 

5139 

5140 def hset( 

5141 self, 

5142 name: str, 

5143 key: Optional[str] = None, 

5144 value: Optional[str] = None, 

5145 mapping: Optional[dict] = None, 

5146 items: Optional[list] = None, 

5147 ) -> Union[Awaitable[int], int]: 

5148 """ 

5149 Set ``key`` to ``value`` within hash ``name``, 

5150 ``mapping`` accepts a dict of key/value pairs that will be 

5151 added to hash ``name``. 

5152 ``items`` accepts a list of key/value pairs that will be 

5153 added to hash ``name``. 

5154 Returns the number of fields that were added. 

5155 

5156 For more information, see https://redis.io/commands/hset 

5157 """ 

5158 

5159 if key is None and not mapping and not items: 

5160 raise DataError("'hset' with no key value pairs") 

5161 

5162 pieces = [] 

5163 if items: 

5164 pieces.extend(items) 

5165 if key is not None: 

5166 pieces.extend((key, value)) 

5167 if mapping: 

5168 for pair in mapping.items(): 

5169 pieces.extend(pair) 

5170 

5171 return self.execute_command("HSET", name, *pieces) 

5172 

5173 def hsetex( 

5174 self, 

5175 name: str, 

5176 key: Optional[str] = None, 

5177 value: Optional[str] = None, 

5178 mapping: Optional[dict] = None, 

5179 items: Optional[list] = None, 

5180 ex: Optional[ExpiryT] = None, 

5181 px: Optional[ExpiryT] = None, 

5182 exat: Optional[AbsExpiryT] = None, 

5183 pxat: Optional[AbsExpiryT] = None, 

5184 data_persist_option: Optional[HashDataPersistOptions] = None, 

5185 keepttl: bool = False, 

5186 ) -> Union[Awaitable[int], int]: 

5187 """ 

5188 Set ``key`` to ``value`` within hash ``name`` 

5189 

5190 ``mapping`` accepts a dict of key/value pairs that will be 

5191 added to hash ``name``. 

5192 

5193 ``items`` accepts a list of key/value pairs that will be 

5194 added to hash ``name``. 

5195 

5196 ``ex`` sets an expire flag on ``keys`` for ``ex`` seconds. 

5197 

5198 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds. 

5199 

5200 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds, 

5201 specified in unix time. 

5202 

5203 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds, 

5204 specified in unix time. 

5205 

5206 ``data_persist_option`` can be set to ``FNX`` or ``FXX`` to control the 

5207 behavior of the command. 

5208 ``FNX`` will set the value for each provided key to each 

5209 provided value only if all do not already exist. 

5210 ``FXX`` will set the value for each provided key to each 

5211 provided value only if all already exist. 

5212 

5213 ``keepttl`` if True, retain the time to live associated with the keys. 

5214 

5215 Returns the number of fields that were added. 

5216 

5217 Available since Redis 8.0 

5218 For more information, see https://redis.io/commands/hsetex 

5219 """ 

5220 if key is None and not mapping and not items: 

5221 raise DataError("'hsetex' with no key value pairs") 

5222 

5223 if items and len(items) % 2 != 0: 

5224 raise DataError( 

5225 "'hsetex' with odd number of items. " 

5226 "'items' must contain a list of key/value pairs." 

5227 ) 

5228 

5229 opset = {ex, px, exat, pxat} 

5230 if len(opset) > 2 or len(opset) > 1 and keepttl: 

5231 raise DataError( 

5232 "``ex``, ``px``, ``exat``, ``pxat``, " 

5233 "and ``keepttl`` are mutually exclusive." 

5234 ) 

5235 

5236 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat) 

5237 if data_persist_option: 

5238 exp_options.append(data_persist_option.value) 

5239 

5240 if keepttl: 

5241 exp_options.append("KEEPTTL") 

5242 

5243 pieces = [] 

5244 if items: 

5245 pieces.extend(items) 

5246 if key is not None: 

5247 pieces.extend((key, value)) 

5248 if mapping: 

5249 for pair in mapping.items(): 

5250 pieces.extend(pair) 

5251 

5252 return self.execute_command( 

5253 "HSETEX", name, *exp_options, "FIELDS", int(len(pieces) / 2), *pieces 

5254 ) 

5255 

5256 def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool]: 

5257 """ 

5258 Set ``key`` to ``value`` within hash ``name`` if ``key`` does not 

5259 exist. Returns 1 if HSETNX created a field, otherwise 0. 

5260 

5261 For more information, see https://redis.io/commands/hsetnx 

5262 """ 

5263 return self.execute_command("HSETNX", name, key, value) 

5264 

5265 @deprecated_function( 

5266 version="4.0.0", 

5267 reason="Use 'hset' instead.", 

5268 name="hmset", 

5269 ) 

5270 def hmset(self, name: str, mapping: dict) -> Union[Awaitable[str], str]: 

5271 """ 

5272 Set key to value within hash ``name`` for each corresponding 

5273 key and value from the ``mapping`` dict. 

5274 

5275 For more information, see https://redis.io/commands/hmset 

5276 """ 

5277 if not mapping: 

5278 raise DataError("'hmset' with 'mapping' of length 0") 

5279 items = [] 

5280 for pair in mapping.items(): 

5281 items.extend(pair) 

5282 return self.execute_command("HMSET", name, *items) 

5283 

5284 def hmget(self, name: str, keys: List, *args: List) -> Union[Awaitable[List], List]: 

5285 """ 

5286 Returns a list of values ordered identically to ``keys`` 

5287 

5288 For more information, see https://redis.io/commands/hmget 

5289 """ 

5290 args = list_or_args(keys, args) 

5291 return self.execute_command("HMGET", name, *args, keys=[name]) 

5292 

5293 def hvals(self, name: str) -> Union[Awaitable[List], List]: 

5294 """ 

5295 Return the list of values within hash ``name`` 

5296 

5297 For more information, see https://redis.io/commands/hvals 

5298 """ 

5299 return self.execute_command("HVALS", name, keys=[name]) 

5300 

5301 def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]: 

5302 """ 

5303 Return the number of bytes stored in the value of ``key`` 

5304 within hash ``name`` 

5305 

5306 For more information, see https://redis.io/commands/hstrlen 

5307 """ 

5308 return self.execute_command("HSTRLEN", name, key, keys=[name]) 

5309 

5310 def hexpire( 

5311 self, 

5312 name: KeyT, 

5313 seconds: ExpiryT, 

5314 *fields: str, 

5315 nx: bool = False, 

5316 xx: bool = False, 

5317 gt: bool = False, 

5318 lt: bool = False, 

5319 ) -> ResponseT: 

5320 """ 

5321 Sets or updates the expiration time for fields within a hash key, using relative 

5322 time in seconds. 

5323 

5324 If a field already has an expiration time, the behavior of the update can be 

5325 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5326 

5327 The return value provides detailed information about the outcome for each field. 

5328 

5329 For more information, see https://redis.io/commands/hexpire 

5330 

5331 Args: 

5332 name: The name of the hash key. 

5333 seconds: Expiration time in seconds, relative. Can be an integer, or a 

5334 Python `timedelta` object. 

5335 fields: List of fields within the hash to apply the expiration time to. 

5336 nx: Set expiry only when the field has no expiry. 

5337 xx: Set expiry only when the field has an existing expiry. 

5338 gt: Set expiry only when the new expiry is greater than the current one. 

5339 lt: Set expiry only when the new expiry is less than the current one. 

5340 

5341 Returns: 

5342 Returns a list which contains for each field in the request: 

5343 - `-2` if the field does not exist, or if the key does not exist. 

5344 - `0` if the specified NX | XX | GT | LT condition was not met. 

5345 - `1` if the expiration time was set or updated. 

5346 - `2` if the field was deleted because the specified expiration time is 

5347 in the past. 

5348 """ 

5349 conditions = [nx, xx, gt, lt] 

5350 if sum(conditions) > 1: 

5351 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5352 

5353 if isinstance(seconds, datetime.timedelta): 

5354 seconds = int(seconds.total_seconds()) 

5355 

5356 options = [] 

5357 if nx: 

5358 options.append("NX") 

5359 if xx: 

5360 options.append("XX") 

5361 if gt: 

5362 options.append("GT") 

5363 if lt: 

5364 options.append("LT") 

5365 

5366 return self.execute_command( 

5367 "HEXPIRE", name, seconds, *options, "FIELDS", len(fields), *fields 

5368 ) 

5369 

5370 def hpexpire( 

5371 self, 

5372 name: KeyT, 

5373 milliseconds: ExpiryT, 

5374 *fields: str, 

5375 nx: bool = False, 

5376 xx: bool = False, 

5377 gt: bool = False, 

5378 lt: bool = False, 

5379 ) -> ResponseT: 

5380 """ 

5381 Sets or updates the expiration time for fields within a hash key, using relative 

5382 time in milliseconds. 

5383 

5384 If a field already has an expiration time, the behavior of the update can be 

5385 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5386 

5387 The return value provides detailed information about the outcome for each field. 

5388 

5389 For more information, see https://redis.io/commands/hpexpire 

5390 

5391 Args: 

5392 name: The name of the hash key. 

5393 milliseconds: Expiration time in milliseconds, relative. Can be an integer, 

5394 or a Python `timedelta` object. 

5395 fields: List of fields within the hash to apply the expiration time to. 

5396 nx: Set expiry only when the field has no expiry. 

5397 xx: Set expiry only when the field has an existing expiry. 

5398 gt: Set expiry only when the new expiry is greater than the current one. 

5399 lt: Set expiry only when the new expiry is less than the current one. 

5400 

5401 Returns: 

5402 Returns a list which contains for each field in the request: 

5403 - `-2` if the field does not exist, or if the key does not exist. 

5404 - `0` if the specified NX | XX | GT | LT condition was not met. 

5405 - `1` if the expiration time was set or updated. 

5406 - `2` if the field was deleted because the specified expiration time is 

5407 in the past. 

5408 """ 

5409 conditions = [nx, xx, gt, lt] 

5410 if sum(conditions) > 1: 

5411 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5412 

5413 if isinstance(milliseconds, datetime.timedelta): 

5414 milliseconds = int(milliseconds.total_seconds() * 1000) 

5415 

5416 options = [] 

5417 if nx: 

5418 options.append("NX") 

5419 if xx: 

5420 options.append("XX") 

5421 if gt: 

5422 options.append("GT") 

5423 if lt: 

5424 options.append("LT") 

5425 

5426 return self.execute_command( 

5427 "HPEXPIRE", name, milliseconds, *options, "FIELDS", len(fields), *fields 

5428 ) 

5429 

5430 def hexpireat( 

5431 self, 

5432 name: KeyT, 

5433 unix_time_seconds: AbsExpiryT, 

5434 *fields: str, 

5435 nx: bool = False, 

5436 xx: bool = False, 

5437 gt: bool = False, 

5438 lt: bool = False, 

5439 ) -> ResponseT: 

5440 """ 

5441 Sets or updates the expiration time for fields within a hash key, using an 

5442 absolute Unix timestamp in seconds. 

5443 

5444 If a field already has an expiration time, the behavior of the update can be 

5445 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5446 

5447 The return value provides detailed information about the outcome for each field. 

5448 

5449 For more information, see https://redis.io/commands/hexpireat 

5450 

5451 Args: 

5452 name: The name of the hash key. 

5453 unix_time_seconds: Expiration time as Unix timestamp in seconds. Can be an 

5454 integer or a Python `datetime` object. 

5455 fields: List of fields within the hash to apply the expiration time to. 

5456 nx: Set expiry only when the field has no expiry. 

5457 xx: Set expiry only when the field has an existing expiration time. 

5458 gt: Set expiry only when the new expiry is greater than the current one. 

5459 lt: Set expiry only when the new expiry is less than the current one. 

5460 

5461 Returns: 

5462 Returns a list which contains for each field in the request: 

5463 - `-2` if the field does not exist, or if the key does not exist. 

5464 - `0` if the specified NX | XX | GT | LT condition was not met. 

5465 - `1` if the expiration time was set or updated. 

5466 - `2` if the field was deleted because the specified expiration time is 

5467 in the past. 

5468 """ 

5469 conditions = [nx, xx, gt, lt] 

5470 if sum(conditions) > 1: 

5471 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5472 

5473 if isinstance(unix_time_seconds, datetime.datetime): 

5474 unix_time_seconds = int(unix_time_seconds.timestamp()) 

5475 

5476 options = [] 

5477 if nx: 

5478 options.append("NX") 

5479 if xx: 

5480 options.append("XX") 

5481 if gt: 

5482 options.append("GT") 

5483 if lt: 

5484 options.append("LT") 

5485 

5486 return self.execute_command( 

5487 "HEXPIREAT", 

5488 name, 

5489 unix_time_seconds, 

5490 *options, 

5491 "FIELDS", 

5492 len(fields), 

5493 *fields, 

5494 ) 

5495 

5496 def hpexpireat( 

5497 self, 

5498 name: KeyT, 

5499 unix_time_milliseconds: AbsExpiryT, 

5500 *fields: str, 

5501 nx: bool = False, 

5502 xx: bool = False, 

5503 gt: bool = False, 

5504 lt: bool = False, 

5505 ) -> ResponseT: 

5506 """ 

5507 Sets or updates the expiration time for fields within a hash key, using an 

5508 absolute Unix timestamp in milliseconds. 

5509 

5510 If a field already has an expiration time, the behavior of the update can be 

5511 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5512 

5513 The return value provides detailed information about the outcome for each field. 

5514 

5515 For more information, see https://redis.io/commands/hpexpireat 

5516 

5517 Args: 

5518 name: The name of the hash key. 

5519 unix_time_milliseconds: Expiration time as Unix timestamp in milliseconds. 

5520 Can be an integer or a Python `datetime` object. 

5521 fields: List of fields within the hash to apply the expiry. 

5522 nx: Set expiry only when the field has no expiry. 

5523 xx: Set expiry only when the field has an existing expiry. 

5524 gt: Set expiry only when the new expiry is greater than the current one. 

5525 lt: Set expiry only when the new expiry is less than the current one. 

5526 

5527 Returns: 

5528 Returns a list which contains for each field in the request: 

5529 - `-2` if the field does not exist, or if the key does not exist. 

5530 - `0` if the specified NX | XX | GT | LT condition was not met. 

5531 - `1` if the expiration time was set or updated. 

5532 - `2` if the field was deleted because the specified expiration time is 

5533 in the past. 

5534 """ 

5535 conditions = [nx, xx, gt, lt] 

5536 if sum(conditions) > 1: 

5537 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5538 

5539 if isinstance(unix_time_milliseconds, datetime.datetime): 

5540 unix_time_milliseconds = int(unix_time_milliseconds.timestamp() * 1000) 

5541 

5542 options = [] 

5543 if nx: 

5544 options.append("NX") 

5545 if xx: 

5546 options.append("XX") 

5547 if gt: 

5548 options.append("GT") 

5549 if lt: 

5550 options.append("LT") 

5551 

5552 return self.execute_command( 

5553 "HPEXPIREAT", 

5554 name, 

5555 unix_time_milliseconds, 

5556 *options, 

5557 "FIELDS", 

5558 len(fields), 

5559 *fields, 

5560 ) 

5561 

5562 def hpersist(self, name: KeyT, *fields: str) -> ResponseT: 

5563 """ 

5564 Removes the expiration time for each specified field in a hash. 

5565 

5566 For more information, see https://redis.io/commands/hpersist 

5567 

5568 Args: 

5569 name: The name of the hash key. 

5570 fields: A list of fields within the hash from which to remove the 

5571 expiration time. 

5572 

5573 Returns: 

5574 Returns a list which contains for each field in the request: 

5575 - `-2` if the field does not exist, or if the key does not exist. 

5576 - `-1` if the field exists but has no associated expiration time. 

5577 - `1` if the expiration time was successfully removed from the field. 

5578 """ 

5579 return self.execute_command("HPERSIST", name, "FIELDS", len(fields), *fields) 

5580 

5581 def hexpiretime(self, key: KeyT, *fields: str) -> ResponseT: 

5582 """ 

5583 Returns the expiration times of hash fields as Unix timestamps in seconds. 

5584 

5585 For more information, see https://redis.io/commands/hexpiretime 

5586 

5587 Args: 

5588 key: The hash key. 

5589 fields: A list of fields within the hash for which to get the expiration 

5590 time. 

5591 

5592 Returns: 

5593 Returns a list which contains for each field in the request: 

5594 - `-2` if the field does not exist, or if the key does not exist. 

5595 - `-1` if the field exists but has no associated expire time. 

5596 - A positive integer representing the expiration Unix timestamp in 

5597 seconds, if the field has an associated expiration time. 

5598 """ 

5599 return self.execute_command( 

5600 "HEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key] 

5601 ) 

5602 

5603 def hpexpiretime(self, key: KeyT, *fields: str) -> ResponseT: 

5604 """ 

5605 Returns the expiration times of hash fields as Unix timestamps in milliseconds. 

5606 

5607 For more information, see https://redis.io/commands/hpexpiretime 

5608 

5609 Args: 

5610 key: The hash key. 

5611 fields: A list of fields within the hash for which to get the expiration 

5612 time. 

5613 

5614 Returns: 

5615 Returns a list which contains for each field in the request: 

5616 - `-2` if the field does not exist, or if the key does not exist. 

5617 - `-1` if the field exists but has no associated expire time. 

5618 - A positive integer representing the expiration Unix timestamp in 

5619 milliseconds, if the field has an associated expiration time. 

5620 """ 

5621 return self.execute_command( 

5622 "HPEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key] 

5623 ) 

5624 

5625 def httl(self, key: KeyT, *fields: str) -> ResponseT: 

5626 """ 

5627 Returns the TTL (Time To Live) in seconds for each specified field within a hash 

5628 key. 

5629 

5630 For more information, see https://redis.io/commands/httl 

5631 

5632 Args: 

5633 key: The hash key. 

5634 fields: A list of fields within the hash for which to get the TTL. 

5635 

5636 Returns: 

5637 Returns a list which contains for each field in the request: 

5638 - `-2` if the field does not exist, or if the key does not exist. 

5639 - `-1` if the field exists but has no associated expire time. 

5640 - A positive integer representing the TTL in seconds if the field has 

5641 an associated expiration time. 

5642 """ 

5643 return self.execute_command( 

5644 "HTTL", key, "FIELDS", len(fields), *fields, keys=[key] 

5645 ) 

5646 

5647 def hpttl(self, key: KeyT, *fields: str) -> ResponseT: 

5648 """ 

5649 Returns the TTL (Time To Live) in milliseconds for each specified field within a 

5650 hash key. 

5651 

5652 For more information, see https://redis.io/commands/hpttl 

5653 

5654 Args: 

5655 key: The hash key. 

5656 fields: A list of fields within the hash for which to get the TTL. 

5657 

5658 Returns: 

5659 Returns a list which contains for each field in the request: 

5660 - `-2` if the field does not exist, or if the key does not exist. 

5661 - `-1` if the field exists but has no associated expire time. 

5662 - A positive integer representing the TTL in milliseconds if the field 

5663 has an associated expiration time. 

5664 """ 

5665 return self.execute_command( 

5666 "HPTTL", key, "FIELDS", len(fields), *fields, keys=[key] 

5667 ) 

5668 

5669 

5670AsyncHashCommands = HashCommands 

5671 

5672 

5673class Script: 

5674 """ 

5675 An executable Lua script object returned by ``register_script`` 

5676 """ 

5677 

5678 def __init__(self, registered_client: "redis.client.Redis", script: ScriptTextT): 

5679 self.registered_client = registered_client 

5680 self.script = script 

5681 # Precalculate and store the SHA1 hex digest of the script. 

5682 

5683 if isinstance(script, str): 

5684 # We need the encoding from the client in order to generate an 

5685 # accurate byte representation of the script 

5686 encoder = self.get_encoder() 

5687 script = encoder.encode(script) 

5688 self.sha = hashlib.sha1(script).hexdigest() 

5689 

5690 def __call__( 

5691 self, 

5692 keys: Union[Sequence[KeyT], None] = None, 

5693 args: Union[Iterable[EncodableT], None] = None, 

5694 client: Union["redis.client.Redis", None] = None, 

5695 ): 

5696 """Execute the script, passing any required ``args``""" 

5697 keys = keys or [] 

5698 args = args or [] 

5699 if client is None: 

5700 client = self.registered_client 

5701 args = tuple(keys) + tuple(args) 

5702 # make sure the Redis server knows about the script 

5703 from redis.client import Pipeline 

5704 

5705 if isinstance(client, Pipeline): 

5706 # Make sure the pipeline can register the script before executing. 

5707 client.scripts.add(self) 

5708 try: 

5709 return client.evalsha(self.sha, len(keys), *args) 

5710 except NoScriptError: 

5711 # Maybe the client is pointed to a different server than the client 

5712 # that created this instance? 

5713 # Overwrite the sha just in case there was a discrepancy. 

5714 self.sha = client.script_load(self.script) 

5715 return client.evalsha(self.sha, len(keys), *args) 

5716 

5717 def get_encoder(self): 

5718 """Get the encoder to encode string scripts into bytes.""" 

5719 try: 

5720 return self.registered_client.get_encoder() 

5721 except AttributeError: 

5722 # DEPRECATED 

5723 # In version <=4.1.2, this was the code we used to get the encoder. 

5724 # However, after 4.1.2 we added support for scripting in clustered 

5725 # redis. ClusteredRedis doesn't have a `.connection_pool` attribute 

5726 # so we changed the Script class to use 

5727 # `self.registered_client.get_encoder` (see above). 

5728 # However, that is technically a breaking change, as consumers who 

5729 # use Scripts directly might inject a `registered_client` that 

5730 # doesn't have a `.get_encoder` field. This try/except prevents us 

5731 # from breaking backward-compatibility. Ideally, it would be 

5732 # removed in the next major release. 

5733 return self.registered_client.connection_pool.get_encoder() 

5734 

5735 

5736class AsyncScript: 

5737 """ 

5738 An executable Lua script object returned by ``register_script`` 

5739 """ 

5740 

5741 def __init__( 

5742 self, 

5743 registered_client: "redis.asyncio.client.Redis", 

5744 script: ScriptTextT, 

5745 ): 

5746 self.registered_client = registered_client 

5747 self.script = script 

5748 # Precalculate and store the SHA1 hex digest of the script. 

5749 

5750 if isinstance(script, str): 

5751 # We need the encoding from the client in order to generate an 

5752 # accurate byte representation of the script 

5753 try: 

5754 encoder = registered_client.connection_pool.get_encoder() 

5755 except AttributeError: 

5756 # Cluster 

5757 encoder = registered_client.get_encoder() 

5758 script = encoder.encode(script) 

5759 self.sha = hashlib.sha1(script).hexdigest() 

5760 

5761 async def __call__( 

5762 self, 

5763 keys: Union[Sequence[KeyT], None] = None, 

5764 args: Union[Iterable[EncodableT], None] = None, 

5765 client: Union["redis.asyncio.client.Redis", None] = None, 

5766 ): 

5767 """Execute the script, passing any required ``args``""" 

5768 keys = keys or [] 

5769 args = args or [] 

5770 if client is None: 

5771 client = self.registered_client 

5772 args = tuple(keys) + tuple(args) 

5773 # make sure the Redis server knows about the script 

5774 from redis.asyncio.client import Pipeline 

5775 

5776 if isinstance(client, Pipeline): 

5777 # Make sure the pipeline can register the script before executing. 

5778 client.scripts.add(self) 

5779 try: 

5780 return await client.evalsha(self.sha, len(keys), *args) 

5781 except NoScriptError: 

5782 # Maybe the client is pointed to a different server than the client 

5783 # that created this instance? 

5784 # Overwrite the sha just in case there was a discrepancy. 

5785 self.sha = await client.script_load(self.script) 

5786 return await client.evalsha(self.sha, len(keys), *args) 

5787 

5788 

5789class PubSubCommands(CommandsProtocol): 

5790 """ 

5791 Redis PubSub commands. 

5792 see https://redis.io/topics/pubsub 

5793 """ 

5794 

5795 def publish(self, channel: ChannelT, message: EncodableT, **kwargs) -> ResponseT: 

5796 """ 

5797 Publish ``message`` on ``channel``. 

5798 Returns the number of subscribers the message was delivered to. 

5799 

5800 For more information, see https://redis.io/commands/publish 

5801 """ 

5802 return self.execute_command("PUBLISH", channel, message, **kwargs) 

5803 

5804 def spublish(self, shard_channel: ChannelT, message: EncodableT) -> ResponseT: 

5805 """ 

5806 Posts a message to the given shard channel. 

5807 Returns the number of clients that received the message 

5808 

5809 For more information, see https://redis.io/commands/spublish 

5810 """ 

5811 return self.execute_command("SPUBLISH", shard_channel, message) 

5812 

5813 def pubsub_channels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

5814 """ 

5815 Return a list of channels that have at least one subscriber 

5816 

5817 For more information, see https://redis.io/commands/pubsub-channels 

5818 """ 

5819 return self.execute_command("PUBSUB CHANNELS", pattern, **kwargs) 

5820 

5821 def pubsub_shardchannels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

5822 """ 

5823 Return a list of shard_channels that have at least one subscriber 

5824 

5825 For more information, see https://redis.io/commands/pubsub-shardchannels 

5826 """ 

5827 return self.execute_command("PUBSUB SHARDCHANNELS", pattern, **kwargs) 

5828 

5829 def pubsub_numpat(self, **kwargs) -> ResponseT: 

5830 """ 

5831 Returns the number of subscriptions to patterns 

5832 

5833 For more information, see https://redis.io/commands/pubsub-numpat 

5834 """ 

5835 return self.execute_command("PUBSUB NUMPAT", **kwargs) 

5836 

5837 def pubsub_numsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

5838 """ 

5839 Return a list of (channel, number of subscribers) tuples 

5840 for each channel given in ``*args`` 

5841 

5842 For more information, see https://redis.io/commands/pubsub-numsub 

5843 """ 

5844 return self.execute_command("PUBSUB NUMSUB", *args, **kwargs) 

5845 

5846 def pubsub_shardnumsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

5847 """ 

5848 Return a list of (shard_channel, number of subscribers) tuples 

5849 for each channel given in ``*args`` 

5850 

5851 For more information, see https://redis.io/commands/pubsub-shardnumsub 

5852 """ 

5853 return self.execute_command("PUBSUB SHARDNUMSUB", *args, **kwargs) 

5854 

5855 

5856AsyncPubSubCommands = PubSubCommands 

5857 

5858 

5859class ScriptCommands(CommandsProtocol): 

5860 """ 

5861 Redis Lua script commands. see: 

5862 https://redis.io/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/ 

5863 """ 

5864 

5865 def _eval( 

5866 self, command: str, script: str, numkeys: int, *keys_and_args: str 

5867 ) -> Union[Awaitable[str], str]: 

5868 return self.execute_command(command, script, numkeys, *keys_and_args) 

5869 

5870 def eval( 

5871 self, script: str, numkeys: int, *keys_and_args: str 

5872 ) -> Union[Awaitable[str], str]: 

5873 """ 

5874 Execute the Lua ``script``, specifying the ``numkeys`` the script 

5875 will touch and the key names and argument values in ``keys_and_args``. 

5876 Returns the result of the script. 

5877 

5878 In practice, use the object returned by ``register_script``. This 

5879 function exists purely for Redis API completion. 

5880 

5881 For more information, see https://redis.io/commands/eval 

5882 """ 

5883 return self._eval("EVAL", script, numkeys, *keys_and_args) 

5884 

5885 def eval_ro( 

5886 self, script: str, numkeys: int, *keys_and_args: str 

5887 ) -> Union[Awaitable[str], str]: 

5888 """ 

5889 The read-only variant of the EVAL command 

5890 

5891 Execute the read-only Lua ``script`` specifying the ``numkeys`` the script 

5892 will touch and the key names and argument values in ``keys_and_args``. 

5893 Returns the result of the script. 

5894 

5895 For more information, see https://redis.io/commands/eval_ro 

5896 """ 

5897 return self._eval("EVAL_RO", script, numkeys, *keys_and_args) 

5898 

5899 def _evalsha( 

5900 self, command: str, sha: str, numkeys: int, *keys_and_args: list 

5901 ) -> Union[Awaitable[str], str]: 

5902 return self.execute_command(command, sha, numkeys, *keys_and_args) 

5903 

5904 def evalsha( 

5905 self, sha: str, numkeys: int, *keys_and_args: str 

5906 ) -> Union[Awaitable[str], str]: 

5907 """ 

5908 Use the ``sha`` to execute a Lua script already registered via EVAL 

5909 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

5910 key names and argument values in ``keys_and_args``. Returns the result 

5911 of the script. 

5912 

5913 In practice, use the object returned by ``register_script``. This 

5914 function exists purely for Redis API completion. 

5915 

5916 For more information, see https://redis.io/commands/evalsha 

5917 """ 

5918 return self._evalsha("EVALSHA", sha, numkeys, *keys_and_args) 

5919 

5920 def evalsha_ro( 

5921 self, sha: str, numkeys: int, *keys_and_args: str 

5922 ) -> Union[Awaitable[str], str]: 

5923 """ 

5924 The read-only variant of the EVALSHA command 

5925 

5926 Use the ``sha`` to execute a read-only Lua script already registered via EVAL 

5927 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

5928 key names and argument values in ``keys_and_args``. Returns the result 

5929 of the script. 

5930 

5931 For more information, see https://redis.io/commands/evalsha_ro 

5932 """ 

5933 return self._evalsha("EVALSHA_RO", sha, numkeys, *keys_and_args) 

5934 

5935 def script_exists(self, *args: str) -> ResponseT: 

5936 """ 

5937 Check if a script exists in the script cache by specifying the SHAs of 

5938 each script as ``args``. Returns a list of boolean values indicating if 

5939 if each already script exists in the cache_data. 

5940 

5941 For more information, see https://redis.io/commands/script-exists 

5942 """ 

5943 return self.execute_command("SCRIPT EXISTS", *args) 

5944 

5945 def script_debug(self, *args) -> None: 

5946 raise NotImplementedError( 

5947 "SCRIPT DEBUG is intentionally not implemented in the client." 

5948 ) 

5949 

5950 def script_flush( 

5951 self, sync_type: Union[Literal["SYNC"], Literal["ASYNC"]] = None 

5952 ) -> ResponseT: 

5953 """Flush all scripts from the script cache_data. 

5954 

5955 ``sync_type`` is by default SYNC (synchronous) but it can also be 

5956 ASYNC. 

5957 

5958 For more information, see https://redis.io/commands/script-flush 

5959 """ 

5960 

5961 # Redis pre 6 had no sync_type. 

5962 if sync_type not in ["SYNC", "ASYNC", None]: 

5963 raise DataError( 

5964 "SCRIPT FLUSH defaults to SYNC in redis > 6.2, or " 

5965 "accepts SYNC/ASYNC. For older versions, " 

5966 "of redis leave as None." 

5967 ) 

5968 if sync_type is None: 

5969 pieces = [] 

5970 else: 

5971 pieces = [sync_type] 

5972 return self.execute_command("SCRIPT FLUSH", *pieces) 

5973 

5974 def script_kill(self) -> ResponseT: 

5975 """ 

5976 Kill the currently executing Lua script 

5977 

5978 For more information, see https://redis.io/commands/script-kill 

5979 """ 

5980 return self.execute_command("SCRIPT KILL") 

5981 

5982 def script_load(self, script: ScriptTextT) -> ResponseT: 

5983 """ 

5984 Load a Lua ``script`` into the script cache_data. Returns the SHA. 

5985 

5986 For more information, see https://redis.io/commands/script-load 

5987 """ 

5988 return self.execute_command("SCRIPT LOAD", script) 

5989 

5990 def register_script(self: "redis.client.Redis", script: ScriptTextT) -> Script: 

5991 """ 

5992 Register a Lua ``script`` specifying the ``keys`` it will touch. 

5993 Returns a Script object that is callable and hides the complexity of 

5994 deal with scripts, keys, and shas. This is the preferred way to work 

5995 with Lua scripts. 

5996 """ 

5997 return Script(self, script) 

5998 

5999 

6000class AsyncScriptCommands(ScriptCommands): 

6001 async def script_debug(self, *args) -> None: 

6002 return super().script_debug() 

6003 

6004 def register_script( 

6005 self: "redis.asyncio.client.Redis", 

6006 script: ScriptTextT, 

6007 ) -> AsyncScript: 

6008 """ 

6009 Register a Lua ``script`` specifying the ``keys`` it will touch. 

6010 Returns a Script object that is callable and hides the complexity of 

6011 deal with scripts, keys, and shas. This is the preferred way to work 

6012 with Lua scripts. 

6013 """ 

6014 return AsyncScript(self, script) 

6015 

6016 

6017class GeoCommands(CommandsProtocol): 

6018 """ 

6019 Redis Geospatial commands. 

6020 see: https://redis.com/redis-best-practices/indexing-patterns/geospatial/ 

6021 """ 

6022 

6023 def geoadd( 

6024 self, 

6025 name: KeyT, 

6026 values: Sequence[EncodableT], 

6027 nx: bool = False, 

6028 xx: bool = False, 

6029 ch: bool = False, 

6030 ) -> ResponseT: 

6031 """ 

6032 Add the specified geospatial items to the specified key identified 

6033 by the ``name`` argument. The Geospatial items are given as ordered 

6034 members of the ``values`` argument, each item or place is formed by 

6035 the triad longitude, latitude and name. 

6036 

6037 Note: You can use ZREM to remove elements. 

6038 

6039 ``nx`` forces ZADD to only create new elements and not to update 

6040 scores for elements that already exist. 

6041 

6042 ``xx`` forces ZADD to only update scores of elements that already 

6043 exist. New elements will not be added. 

6044 

6045 ``ch`` modifies the return value to be the numbers of elements changed. 

6046 Changed elements include new elements that were added and elements 

6047 whose scores changed. 

6048 

6049 For more information, see https://redis.io/commands/geoadd 

6050 """ 

6051 if nx and xx: 

6052 raise DataError("GEOADD allows either 'nx' or 'xx', not both") 

6053 if len(values) % 3 != 0: 

6054 raise DataError("GEOADD requires places with lon, lat and name values") 

6055 pieces = [name] 

6056 if nx: 

6057 pieces.append("NX") 

6058 if xx: 

6059 pieces.append("XX") 

6060 if ch: 

6061 pieces.append("CH") 

6062 pieces.extend(values) 

6063 return self.execute_command("GEOADD", *pieces) 

6064 

6065 def geodist( 

6066 self, name: KeyT, place1: FieldT, place2: FieldT, unit: Optional[str] = None 

6067 ) -> ResponseT: 

6068 """ 

6069 Return the distance between ``place1`` and ``place2`` members of the 

6070 ``name`` key. 

6071 The units must be one of the following : m, km mi, ft. By default 

6072 meters are used. 

6073 

6074 For more information, see https://redis.io/commands/geodist 

6075 """ 

6076 pieces: list[EncodableT] = [name, place1, place2] 

6077 if unit and unit not in ("m", "km", "mi", "ft"): 

6078 raise DataError("GEODIST invalid unit") 

6079 elif unit: 

6080 pieces.append(unit) 

6081 return self.execute_command("GEODIST", *pieces, keys=[name]) 

6082 

6083 def geohash(self, name: KeyT, *values: FieldT) -> ResponseT: 

6084 """ 

6085 Return the geo hash string for each item of ``values`` members of 

6086 the specified key identified by the ``name`` argument. 

6087 

6088 For more information, see https://redis.io/commands/geohash 

6089 """ 

6090 return self.execute_command("GEOHASH", name, *values, keys=[name]) 

6091 

6092 def geopos(self, name: KeyT, *values: FieldT) -> ResponseT: 

6093 """ 

6094 Return the positions of each item of ``values`` as members of 

6095 the specified key identified by the ``name`` argument. Each position 

6096 is represented by the pairs lon and lat. 

6097 

6098 For more information, see https://redis.io/commands/geopos 

6099 """ 

6100 return self.execute_command("GEOPOS", name, *values, keys=[name]) 

6101 

6102 def georadius( 

6103 self, 

6104 name: KeyT, 

6105 longitude: float, 

6106 latitude: float, 

6107 radius: float, 

6108 unit: Optional[str] = None, 

6109 withdist: bool = False, 

6110 withcoord: bool = False, 

6111 withhash: bool = False, 

6112 count: Optional[int] = None, 

6113 sort: Optional[str] = None, 

6114 store: Optional[KeyT] = None, 

6115 store_dist: Optional[KeyT] = None, 

6116 any: bool = False, 

6117 ) -> ResponseT: 

6118 """ 

6119 Return the members of the specified key identified by the 

6120 ``name`` argument which are within the borders of the area specified 

6121 with the ``latitude`` and ``longitude`` location and the maximum 

6122 distance from the center specified by the ``radius`` value. 

6123 

6124 The units must be one of the following : m, km mi, ft. By default 

6125 

6126 ``withdist`` indicates to return the distances of each place. 

6127 

6128 ``withcoord`` indicates to return the latitude and longitude of 

6129 each place. 

6130 

6131 ``withhash`` indicates to return the geohash string of each place. 

6132 

6133 ``count`` indicates to return the number of elements up to N. 

6134 

6135 ``sort`` indicates to return the places in a sorted way, ASC for 

6136 nearest to fairest and DESC for fairest to nearest. 

6137 

6138 ``store`` indicates to save the places names in a sorted set named 

6139 with a specific key, each element of the destination sorted set is 

6140 populated with the score got from the original geo sorted set. 

6141 

6142 ``store_dist`` indicates to save the places names in a sorted set 

6143 named with a specific key, instead of ``store`` the sorted set 

6144 destination score is set with the distance. 

6145 

6146 For more information, see https://redis.io/commands/georadius 

6147 """ 

6148 return self._georadiusgeneric( 

6149 "GEORADIUS", 

6150 name, 

6151 longitude, 

6152 latitude, 

6153 radius, 

6154 unit=unit, 

6155 withdist=withdist, 

6156 withcoord=withcoord, 

6157 withhash=withhash, 

6158 count=count, 

6159 sort=sort, 

6160 store=store, 

6161 store_dist=store_dist, 

6162 any=any, 

6163 ) 

6164 

6165 def georadiusbymember( 

6166 self, 

6167 name: KeyT, 

6168 member: FieldT, 

6169 radius: float, 

6170 unit: Optional[str] = None, 

6171 withdist: bool = False, 

6172 withcoord: bool = False, 

6173 withhash: bool = False, 

6174 count: Optional[int] = None, 

6175 sort: Optional[str] = None, 

6176 store: Union[KeyT, None] = None, 

6177 store_dist: Union[KeyT, None] = None, 

6178 any: bool = False, 

6179 ) -> ResponseT: 

6180 """ 

6181 This command is exactly like ``georadius`` with the sole difference 

6182 that instead of taking, as the center of the area to query, a longitude 

6183 and latitude value, it takes the name of a member already existing 

6184 inside the geospatial index represented by the sorted set. 

6185 

6186 For more information, see https://redis.io/commands/georadiusbymember 

6187 """ 

6188 return self._georadiusgeneric( 

6189 "GEORADIUSBYMEMBER", 

6190 name, 

6191 member, 

6192 radius, 

6193 unit=unit, 

6194 withdist=withdist, 

6195 withcoord=withcoord, 

6196 withhash=withhash, 

6197 count=count, 

6198 sort=sort, 

6199 store=store, 

6200 store_dist=store_dist, 

6201 any=any, 

6202 ) 

6203 

6204 def _georadiusgeneric( 

6205 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

6206 ) -> ResponseT: 

6207 pieces = list(args) 

6208 if kwargs["unit"] and kwargs["unit"] not in ("m", "km", "mi", "ft"): 

6209 raise DataError("GEORADIUS invalid unit") 

6210 elif kwargs["unit"]: 

6211 pieces.append(kwargs["unit"]) 

6212 else: 

6213 pieces.append("m") 

6214 

6215 if kwargs["any"] and kwargs["count"] is None: 

6216 raise DataError("``any`` can't be provided without ``count``") 

6217 

6218 for arg_name, byte_repr in ( 

6219 ("withdist", "WITHDIST"), 

6220 ("withcoord", "WITHCOORD"), 

6221 ("withhash", "WITHHASH"), 

6222 ): 

6223 if kwargs[arg_name]: 

6224 pieces.append(byte_repr) 

6225 

6226 if kwargs["count"] is not None: 

6227 pieces.extend(["COUNT", kwargs["count"]]) 

6228 if kwargs["any"]: 

6229 pieces.append("ANY") 

6230 

6231 if kwargs["sort"]: 

6232 if kwargs["sort"] == "ASC": 

6233 pieces.append("ASC") 

6234 elif kwargs["sort"] == "DESC": 

6235 pieces.append("DESC") 

6236 else: 

6237 raise DataError("GEORADIUS invalid sort") 

6238 

6239 if kwargs["store"] and kwargs["store_dist"]: 

6240 raise DataError("GEORADIUS store and store_dist cant be set together") 

6241 

6242 if kwargs["store"]: 

6243 pieces.extend([b"STORE", kwargs["store"]]) 

6244 

6245 if kwargs["store_dist"]: 

6246 pieces.extend([b"STOREDIST", kwargs["store_dist"]]) 

6247 

6248 return self.execute_command(command, *pieces, **kwargs) 

6249 

6250 def geosearch( 

6251 self, 

6252 name: KeyT, 

6253 member: Union[FieldT, None] = None, 

6254 longitude: Union[float, None] = None, 

6255 latitude: Union[float, None] = None, 

6256 unit: str = "m", 

6257 radius: Union[float, None] = None, 

6258 width: Union[float, None] = None, 

6259 height: Union[float, None] = None, 

6260 sort: Optional[str] = None, 

6261 count: Optional[int] = None, 

6262 any: bool = False, 

6263 withcoord: bool = False, 

6264 withdist: bool = False, 

6265 withhash: bool = False, 

6266 ) -> ResponseT: 

6267 """ 

6268 Return the members of specified key identified by the 

6269 ``name`` argument, which are within the borders of the 

6270 area specified by a given shape. This command extends the 

6271 GEORADIUS command, so in addition to searching within circular 

6272 areas, it supports searching within rectangular areas. 

6273 

6274 This command should be used in place of the deprecated 

6275 GEORADIUS and GEORADIUSBYMEMBER commands. 

6276 

6277 ``member`` Use the position of the given existing 

6278 member in the sorted set. Can't be given with ``longitude`` 

6279 and ``latitude``. 

6280 

6281 ``longitude`` and ``latitude`` Use the position given by 

6282 this coordinates. Can't be given with ``member`` 

6283 ``radius`` Similar to GEORADIUS, search inside circular 

6284 area according the given radius. Can't be given with 

6285 ``height`` and ``width``. 

6286 ``height`` and ``width`` Search inside an axis-aligned 

6287 rectangle, determined by the given height and width. 

6288 Can't be given with ``radius`` 

6289 

6290 ``unit`` must be one of the following : m, km, mi, ft. 

6291 `m` for meters (the default value), `km` for kilometers, 

6292 `mi` for miles and `ft` for feet. 

6293 

6294 ``sort`` indicates to return the places in a sorted way, 

6295 ASC for nearest to furthest and DESC for furthest to nearest. 

6296 

6297 ``count`` limit the results to the first count matching items. 

6298 

6299 ``any`` is set to True, the command will return as soon as 

6300 enough matches are found. Can't be provided without ``count`` 

6301 

6302 ``withdist`` indicates to return the distances of each place. 

6303 ``withcoord`` indicates to return the latitude and longitude of 

6304 each place. 

6305 

6306 ``withhash`` indicates to return the geohash string of each place. 

6307 

6308 For more information, see https://redis.io/commands/geosearch 

6309 """ 

6310 

6311 return self._geosearchgeneric( 

6312 "GEOSEARCH", 

6313 name, 

6314 member=member, 

6315 longitude=longitude, 

6316 latitude=latitude, 

6317 unit=unit, 

6318 radius=radius, 

6319 width=width, 

6320 height=height, 

6321 sort=sort, 

6322 count=count, 

6323 any=any, 

6324 withcoord=withcoord, 

6325 withdist=withdist, 

6326 withhash=withhash, 

6327 store=None, 

6328 store_dist=None, 

6329 ) 

6330 

6331 def geosearchstore( 

6332 self, 

6333 dest: KeyT, 

6334 name: KeyT, 

6335 member: Optional[FieldT] = None, 

6336 longitude: Optional[float] = None, 

6337 latitude: Optional[float] = None, 

6338 unit: str = "m", 

6339 radius: Optional[float] = None, 

6340 width: Optional[float] = None, 

6341 height: Optional[float] = None, 

6342 sort: Optional[str] = None, 

6343 count: Optional[int] = None, 

6344 any: bool = False, 

6345 storedist: bool = False, 

6346 ) -> ResponseT: 

6347 """ 

6348 This command is like GEOSEARCH, but stores the result in 

6349 ``dest``. By default, it stores the results in the destination 

6350 sorted set with their geospatial information. 

6351 if ``store_dist`` set to True, the command will stores the 

6352 items in a sorted set populated with their distance from the 

6353 center of the circle or box, as a floating-point number. 

6354 

6355 For more information, see https://redis.io/commands/geosearchstore 

6356 """ 

6357 return self._geosearchgeneric( 

6358 "GEOSEARCHSTORE", 

6359 dest, 

6360 name, 

6361 member=member, 

6362 longitude=longitude, 

6363 latitude=latitude, 

6364 unit=unit, 

6365 radius=radius, 

6366 width=width, 

6367 height=height, 

6368 sort=sort, 

6369 count=count, 

6370 any=any, 

6371 withcoord=None, 

6372 withdist=None, 

6373 withhash=None, 

6374 store=None, 

6375 store_dist=storedist, 

6376 ) 

6377 

6378 def _geosearchgeneric( 

6379 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

6380 ) -> ResponseT: 

6381 pieces = list(args) 

6382 

6383 # FROMMEMBER or FROMLONLAT 

6384 if kwargs["member"] is None: 

6385 if kwargs["longitude"] is None or kwargs["latitude"] is None: 

6386 raise DataError("GEOSEARCH must have member or longitude and latitude") 

6387 if kwargs["member"]: 

6388 if kwargs["longitude"] or kwargs["latitude"]: 

6389 raise DataError( 

6390 "GEOSEARCH member and longitude or latitude cant be set together" 

6391 ) 

6392 pieces.extend([b"FROMMEMBER", kwargs["member"]]) 

6393 if kwargs["longitude"] is not None and kwargs["latitude"] is not None: 

6394 pieces.extend([b"FROMLONLAT", kwargs["longitude"], kwargs["latitude"]]) 

6395 

6396 # BYRADIUS or BYBOX 

6397 if kwargs["radius"] is None: 

6398 if kwargs["width"] is None or kwargs["height"] is None: 

6399 raise DataError("GEOSEARCH must have radius or width and height") 

6400 if kwargs["unit"] is None: 

6401 raise DataError("GEOSEARCH must have unit") 

6402 if kwargs["unit"].lower() not in ("m", "km", "mi", "ft"): 

6403 raise DataError("GEOSEARCH invalid unit") 

6404 if kwargs["radius"]: 

6405 if kwargs["width"] or kwargs["height"]: 

6406 raise DataError( 

6407 "GEOSEARCH radius and width or height cant be set together" 

6408 ) 

6409 pieces.extend([b"BYRADIUS", kwargs["radius"], kwargs["unit"]]) 

6410 if kwargs["width"] and kwargs["height"]: 

6411 pieces.extend([b"BYBOX", kwargs["width"], kwargs["height"], kwargs["unit"]]) 

6412 

6413 # sort 

6414 if kwargs["sort"]: 

6415 if kwargs["sort"].upper() == "ASC": 

6416 pieces.append(b"ASC") 

6417 elif kwargs["sort"].upper() == "DESC": 

6418 pieces.append(b"DESC") 

6419 else: 

6420 raise DataError("GEOSEARCH invalid sort") 

6421 

6422 # count any 

6423 if kwargs["count"]: 

6424 pieces.extend([b"COUNT", kwargs["count"]]) 

6425 if kwargs["any"]: 

6426 pieces.append(b"ANY") 

6427 elif kwargs["any"]: 

6428 raise DataError("GEOSEARCH ``any`` can't be provided without count") 

6429 

6430 # other properties 

6431 for arg_name, byte_repr in ( 

6432 ("withdist", b"WITHDIST"), 

6433 ("withcoord", b"WITHCOORD"), 

6434 ("withhash", b"WITHHASH"), 

6435 ("store_dist", b"STOREDIST"), 

6436 ): 

6437 if kwargs[arg_name]: 

6438 pieces.append(byte_repr) 

6439 

6440 kwargs["keys"] = [args[0] if command == "GEOSEARCH" else args[1]] 

6441 

6442 return self.execute_command(command, *pieces, **kwargs) 

6443 

6444 

6445AsyncGeoCommands = GeoCommands 

6446 

6447 

6448class ModuleCommands(CommandsProtocol): 

6449 """ 

6450 Redis Module commands. 

6451 see: https://redis.io/topics/modules-intro 

6452 """ 

6453 

6454 def module_load(self, path, *args) -> ResponseT: 

6455 """ 

6456 Loads the module from ``path``. 

6457 Passes all ``*args`` to the module, during loading. 

6458 Raises ``ModuleError`` if a module is not found at ``path``. 

6459 

6460 For more information, see https://redis.io/commands/module-load 

6461 """ 

6462 return self.execute_command("MODULE LOAD", path, *args) 

6463 

6464 def module_loadex( 

6465 self, 

6466 path: str, 

6467 options: Optional[List[str]] = None, 

6468 args: Optional[List[str]] = None, 

6469 ) -> ResponseT: 

6470 """ 

6471 Loads a module from a dynamic library at runtime with configuration directives. 

6472 

6473 For more information, see https://redis.io/commands/module-loadex 

6474 """ 

6475 pieces = [] 

6476 if options is not None: 

6477 pieces.append("CONFIG") 

6478 pieces.extend(options) 

6479 if args is not None: 

6480 pieces.append("ARGS") 

6481 pieces.extend(args) 

6482 

6483 return self.execute_command("MODULE LOADEX", path, *pieces) 

6484 

6485 def module_unload(self, name) -> ResponseT: 

6486 """ 

6487 Unloads the module ``name``. 

6488 Raises ``ModuleError`` if ``name`` is not in loaded modules. 

6489 

6490 For more information, see https://redis.io/commands/module-unload 

6491 """ 

6492 return self.execute_command("MODULE UNLOAD", name) 

6493 

6494 def module_list(self) -> ResponseT: 

6495 """ 

6496 Returns a list of dictionaries containing the name and version of 

6497 all loaded modules. 

6498 

6499 For more information, see https://redis.io/commands/module-list 

6500 """ 

6501 return self.execute_command("MODULE LIST") 

6502 

6503 def command_info(self) -> None: 

6504 raise NotImplementedError( 

6505 "COMMAND INFO is intentionally not implemented in the client." 

6506 ) 

6507 

6508 def command_count(self) -> ResponseT: 

6509 return self.execute_command("COMMAND COUNT") 

6510 

6511 def command_getkeys(self, *args) -> ResponseT: 

6512 return self.execute_command("COMMAND GETKEYS", *args) 

6513 

6514 def command(self) -> ResponseT: 

6515 return self.execute_command("COMMAND") 

6516 

6517 

6518class AsyncModuleCommands(ModuleCommands): 

6519 async def command_info(self) -> None: 

6520 return super().command_info() 

6521 

6522 

6523class ClusterCommands(CommandsProtocol): 

6524 """ 

6525 Class for Redis Cluster commands 

6526 """ 

6527 

6528 def cluster(self, cluster_arg, *args, **kwargs) -> ResponseT: 

6529 return self.execute_command(f"CLUSTER {cluster_arg.upper()}", *args, **kwargs) 

6530 

6531 def readwrite(self, **kwargs) -> ResponseT: 

6532 """ 

6533 Disables read queries for a connection to a Redis Cluster slave node. 

6534 

6535 For more information, see https://redis.io/commands/readwrite 

6536 """ 

6537 return self.execute_command("READWRITE", **kwargs) 

6538 

6539 def readonly(self, **kwargs) -> ResponseT: 

6540 """ 

6541 Enables read queries for a connection to a Redis Cluster replica node. 

6542 

6543 For more information, see https://redis.io/commands/readonly 

6544 """ 

6545 return self.execute_command("READONLY", **kwargs) 

6546 

6547 

6548AsyncClusterCommands = ClusterCommands 

6549 

6550 

6551class FunctionCommands: 

6552 """ 

6553 Redis Function commands 

6554 """ 

6555 

6556 def function_load( 

6557 self, code: str, replace: Optional[bool] = False 

6558 ) -> Union[Awaitable[str], str]: 

6559 """ 

6560 Load a library to Redis. 

6561 :param code: the source code (must start with 

6562 Shebang statement that provides a metadata about the library) 

6563 :param replace: changes the behavior to overwrite the existing library 

6564 with the new contents. 

6565 Return the library name that was loaded. 

6566 

6567 For more information, see https://redis.io/commands/function-load 

6568 """ 

6569 pieces = ["REPLACE"] if replace else [] 

6570 pieces.append(code) 

6571 return self.execute_command("FUNCTION LOAD", *pieces) 

6572 

6573 def function_delete(self, library: str) -> Union[Awaitable[str], str]: 

6574 """ 

6575 Delete the library called ``library`` and all its functions. 

6576 

6577 For more information, see https://redis.io/commands/function-delete 

6578 """ 

6579 return self.execute_command("FUNCTION DELETE", library) 

6580 

6581 def function_flush(self, mode: str = "SYNC") -> Union[Awaitable[str], str]: 

6582 """ 

6583 Deletes all the libraries. 

6584 

6585 For more information, see https://redis.io/commands/function-flush 

6586 """ 

6587 return self.execute_command("FUNCTION FLUSH", mode) 

6588 

6589 def function_list( 

6590 self, library: Optional[str] = "*", withcode: Optional[bool] = False 

6591 ) -> Union[Awaitable[List], List]: 

6592 """ 

6593 Return information about the functions and libraries. 

6594 

6595 Args: 

6596 

6597 library: specify a pattern for matching library names 

6598 withcode: cause the server to include the libraries source implementation 

6599 in the reply 

6600 """ 

6601 args = ["LIBRARYNAME", library] 

6602 if withcode: 

6603 args.append("WITHCODE") 

6604 return self.execute_command("FUNCTION LIST", *args) 

6605 

6606 def _fcall( 

6607 self, command: str, function, numkeys: int, *keys_and_args: Any 

6608 ) -> Union[Awaitable[str], str]: 

6609 return self.execute_command(command, function, numkeys, *keys_and_args) 

6610 

6611 def fcall( 

6612 self, function, numkeys: int, *keys_and_args: Any 

6613 ) -> Union[Awaitable[str], str]: 

6614 """ 

6615 Invoke a function. 

6616 

6617 For more information, see https://redis.io/commands/fcall 

6618 """ 

6619 return self._fcall("FCALL", function, numkeys, *keys_and_args) 

6620 

6621 def fcall_ro( 

6622 self, function, numkeys: int, *keys_and_args: Any 

6623 ) -> Union[Awaitable[str], str]: 

6624 """ 

6625 This is a read-only variant of the FCALL command that cannot 

6626 execute commands that modify data. 

6627 

6628 For more information, see https://redis.io/commands/fcall_ro 

6629 """ 

6630 return self._fcall("FCALL_RO", function, numkeys, *keys_and_args) 

6631 

6632 def function_dump(self) -> Union[Awaitable[str], str]: 

6633 """ 

6634 Return the serialized payload of loaded libraries. 

6635 

6636 For more information, see https://redis.io/commands/function-dump 

6637 """ 

6638 from redis.client import NEVER_DECODE 

6639 

6640 options = {} 

6641 options[NEVER_DECODE] = [] 

6642 

6643 return self.execute_command("FUNCTION DUMP", **options) 

6644 

6645 def function_restore( 

6646 self, payload: str, policy: Optional[str] = "APPEND" 

6647 ) -> Union[Awaitable[str], str]: 

6648 """ 

6649 Restore libraries from the serialized ``payload``. 

6650 You can use the optional policy argument to provide a policy 

6651 for handling existing libraries. 

6652 

6653 For more information, see https://redis.io/commands/function-restore 

6654 """ 

6655 return self.execute_command("FUNCTION RESTORE", payload, policy) 

6656 

6657 def function_kill(self) -> Union[Awaitable[str], str]: 

6658 """ 

6659 Kill a function that is currently executing. 

6660 

6661 For more information, see https://redis.io/commands/function-kill 

6662 """ 

6663 return self.execute_command("FUNCTION KILL") 

6664 

6665 def function_stats(self) -> Union[Awaitable[List], List]: 

6666 """ 

6667 Return information about the function that's currently running 

6668 and information about the available execution engines. 

6669 

6670 For more information, see https://redis.io/commands/function-stats 

6671 """ 

6672 return self.execute_command("FUNCTION STATS") 

6673 

6674 

6675AsyncFunctionCommands = FunctionCommands 

6676 

6677 

6678class DataAccessCommands( 

6679 BasicKeyCommands, 

6680 HyperlogCommands, 

6681 HashCommands, 

6682 GeoCommands, 

6683 ListCommands, 

6684 ScanCommands, 

6685 SetCommands, 

6686 StreamCommands, 

6687 SortedSetCommands, 

6688): 

6689 """ 

6690 A class containing all of the implemented data access redis commands. 

6691 This class is to be used as a mixin for synchronous Redis clients. 

6692 """ 

6693 

6694 

6695class AsyncDataAccessCommands( 

6696 AsyncBasicKeyCommands, 

6697 AsyncHyperlogCommands, 

6698 AsyncHashCommands, 

6699 AsyncGeoCommands, 

6700 AsyncListCommands, 

6701 AsyncScanCommands, 

6702 AsyncSetCommands, 

6703 AsyncStreamCommands, 

6704 AsyncSortedSetCommands, 

6705): 

6706 """ 

6707 A class containing all of the implemented data access redis commands. 

6708 This class is to be used as a mixin for asynchronous Redis clients. 

6709 """ 

6710 

6711 

6712class CoreCommands( 

6713 ACLCommands, 

6714 ClusterCommands, 

6715 DataAccessCommands, 

6716 ManagementCommands, 

6717 ModuleCommands, 

6718 PubSubCommands, 

6719 ScriptCommands, 

6720 FunctionCommands, 

6721): 

6722 """ 

6723 A class containing all of the implemented redis commands. This class is 

6724 to be used as a mixin for synchronous Redis clients. 

6725 """ 

6726 

6727 

6728class AsyncCoreCommands( 

6729 AsyncACLCommands, 

6730 AsyncClusterCommands, 

6731 AsyncDataAccessCommands, 

6732 AsyncManagementCommands, 

6733 AsyncModuleCommands, 

6734 AsyncPubSubCommands, 

6735 AsyncScriptCommands, 

6736 AsyncFunctionCommands, 

6737): 

6738 """ 

6739 A class containing all of the implemented redis commands. This class is 

6740 to be used as a mixin for asynchronous Redis clients. 

6741 """