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

1810 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 07:16 +0000

1# from __future__ import annotations 

2 

3import datetime 

4import hashlib 

5import warnings 

6from typing import ( 

7 TYPE_CHECKING, 

8 Any, 

9 AsyncIterator, 

10 Awaitable, 

11 Callable, 

12 Dict, 

13 Iterable, 

14 Iterator, 

15 List, 

16 Mapping, 

17 Optional, 

18 Sequence, 

19 Set, 

20 Tuple, 

21 Union, 

22) 

23 

24from redis.compat import Literal 

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

26from redis.typing import ( 

27 AbsExpiryT, 

28 AnyKeyT, 

29 BitfieldOffsetT, 

30 ChannelT, 

31 CommandsProtocol, 

32 ConsumerT, 

33 EncodableT, 

34 ExpiryT, 

35 FieldT, 

36 GroupT, 

37 KeysT, 

38 KeyT, 

39 PatternT, 

40 ScriptTextT, 

41 StreamIdT, 

42 TimeoutSecT, 

43 ZScoreBoundT, 

44) 

45 

46from .helpers import list_or_args 

47 

48if TYPE_CHECKING: 

49 from redis.asyncio.client import Redis as AsyncRedis 

50 from redis.client import Redis 

51 

52ResponseT = Union[Awaitable, Any] 

53 

54 

55class ACLCommands(CommandsProtocol): 

56 """ 

57 Redis Access Control List (ACL) commands. 

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

59 """ 

60 

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

62 """ 

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

64 

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

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

67 that category. 

68 

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

70 """ 

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

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

73 

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

75 """ 

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

77 

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

79 """ 

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

81 

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

83 """ 

84 Delete the ACL for the specified ``username``s 

85 

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

87 """ 

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

89 

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

91 """Generate a random password value. 

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

93 the next multiple of 4. 

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

95 """ 

96 pieces = [] 

97 if bits is not None: 

98 try: 

99 b = int(bits) 

100 if b < 0 or b > 4096: 

101 raise ValueError 

102 except ValueError: 

103 raise DataError( 

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

105 ) 

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

107 

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

109 """ 

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

111 

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

113 

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

115 """ 

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

117 

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

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

120 the different subcommands. 

121 

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

123 """ 

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

125 

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

127 """ 

128 Return a list of all ACLs on the server 

129 

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

131 """ 

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

133 

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

135 """ 

136 Get ACL logs as a list. 

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

138 :rtype: List. 

139 

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

141 """ 

142 args = [] 

143 if count is not None: 

144 if not isinstance(count, int): 

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

146 args.append(count) 

147 

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

149 

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

151 """ 

152 Reset ACL logs. 

153 :rtype: Boolean. 

154 

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

156 """ 

157 args = [b"RESET"] 

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

159 

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

161 """ 

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

163 

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

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

166 

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

168 """ 

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

170 

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

172 """ 

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

174 

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

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

177 

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

179 """ 

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

181 

182 def acl_setuser( 

183 self, 

184 username: str, 

185 enabled: bool = False, 

186 nopass: bool = False, 

187 passwords: Union[str, Iterable[str], None] = None, 

188 hashed_passwords: Union[str, Iterable[str], None] = None, 

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

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

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

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

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

194 reset: bool = False, 

195 reset_keys: bool = False, 

196 reset_channels: bool = False, 

197 reset_passwords: bool = False, 

198 **kwargs, 

199 ) -> ResponseT: 

200 """ 

201 Create or update an ACL user. 

202 

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

204 the existing ACL is completely overwritten and replaced with the 

205 specified values. 

206 

207 ``enabled`` is a boolean indicating whether the user should be allowed 

208 to authenticate or not. Defaults to ``False``. 

209 

210 ``nopass`` is a boolean indicating whether the can authenticate without 

211 a password. This cannot be True if ``passwords`` are also specified. 

212 

213 ``passwords`` if specified is a list of plain text passwords 

214 to add to or remove from the user. Each password must be prefixed with 

215 a '+' to add or a '-' to remove. For convenience, the value of 

216 ``passwords`` can be a simple prefixed string when adding or 

217 removing a single password. 

218 

219 ``hashed_passwords`` if specified is a list of SHA-256 hashed passwords 

220 to add to or remove from the user. Each hashed password must be 

221 prefixed with a '+' to add or a '-' to remove. For convenience, 

222 the value of ``hashed_passwords`` can be a simple prefixed string when 

223 adding or removing a single password. 

224 

225 ``categories`` if specified is a list of strings representing category 

226 permissions. Each string must be prefixed with either a '+' to add the 

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

228 

229 ``commands`` if specified is a list of strings representing command 

230 permissions. Each string must be prefixed with either a '+' to add the 

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

232 

233 ``keys`` if specified is a list of key patterns to grant the user 

234 access to. Keys patterns allow '*' to support wildcard matching. For 

235 example, '*' grants access to all keys while 'cache:*' grants access 

236 to all keys that are prefixed with 'cache:'. ``keys`` should not be 

237 prefixed with a '~'. 

238 

239 ``reset`` is a boolean indicating whether the user should be fully 

240 reset prior to applying the new ACL. Setting this to True will 

241 remove all existing passwords, flags and privileges from the user and 

242 then apply the specified rules. If this is False, the user's existing 

243 passwords, flags and privileges will be kept and any new specified 

244 rules will be applied on top. 

245 

246 ``reset_keys`` is a boolean indicating whether the user's key 

247 permissions should be reset prior to applying any new key permissions 

248 specified in ``keys``. If this is False, the user's existing 

249 key permissions will be kept and any new specified key permissions 

250 will be applied on top. 

251 

252 ``reset_channels`` is a boolean indicating whether the user's channel 

253 permissions should be reset prior to applying any new channel permissions 

254 specified in ``channels``.If this is False, the user's existing 

255 channel permissions will be kept and any new specified channel permissions 

256 will be applied on top. 

257 

258 ``reset_passwords`` is a boolean indicating whether to remove all 

259 existing passwords and the 'nopass' flag from the user prior to 

260 applying any new passwords specified in 'passwords' or 

261 'hashed_passwords'. If this is False, the user's existing passwords 

262 and 'nopass' status will be kept and any new specified passwords 

263 or hashed_passwords will be applied on top. 

264 

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

266 """ 

267 encoder = self.get_encoder() 

268 pieces: List[EncodableT] = [username] 

269 

270 if reset: 

271 pieces.append(b"reset") 

272 

273 if reset_keys: 

274 pieces.append(b"resetkeys") 

275 

276 if reset_channels: 

277 pieces.append(b"resetchannels") 

278 

279 if reset_passwords: 

280 pieces.append(b"resetpass") 

281 

282 if enabled: 

283 pieces.append(b"on") 

284 else: 

285 pieces.append(b"off") 

286 

287 if (passwords or hashed_passwords) and nopass: 

288 raise DataError( 

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

290 ) 

291 

292 if passwords: 

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

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

295 passwords = list_or_args(passwords, []) 

296 for i, password in enumerate(passwords): 

297 password = encoder.encode(password) 

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

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

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

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

302 else: 

303 raise DataError( 

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

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

306 ) 

307 

308 if hashed_passwords: 

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

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

311 hashed_passwords = list_or_args(hashed_passwords, []) 

312 for i, hashed_password in enumerate(hashed_passwords): 

313 hashed_password = encoder.encode(hashed_password) 

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

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

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

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

318 else: 

319 raise DataError( 

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

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

322 ) 

323 

324 if nopass: 

325 pieces.append(b"nopass") 

326 

327 if categories: 

328 for category in categories: 

329 category = encoder.encode(category) 

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

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

332 pieces.append(category) 

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

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

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

336 pieces.append(category) 

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

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

339 else: 

340 raise DataError( 

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

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

343 ) 

344 if commands: 

345 for cmd in commands: 

346 cmd = encoder.encode(cmd) 

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

348 raise DataError( 

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

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

351 ) 

352 pieces.append(cmd) 

353 

354 if keys: 

355 for key in keys: 

356 key = encoder.encode(key) 

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

358 key = b"~%s" % key 

359 pieces.append(key) 

360 

361 if channels: 

362 for channel in channels: 

363 channel = encoder.encode(channel) 

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

365 

366 if selectors: 

367 for cmd, key in selectors: 

368 cmd = encoder.encode(cmd) 

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

370 raise DataError( 

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

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

373 ) 

374 

375 key = encoder.encode(key) 

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

377 key = b"~%s" % key 

378 

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

380 

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

382 

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

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

385 

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

387 """ 

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

389 

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

391 """Get the username for the current connection 

392 

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

394 """ 

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

396 

397 

398AsyncACLCommands = ACLCommands 

399 

400 

401class ManagementCommands(CommandsProtocol): 

402 """ 

403 Redis management commands 

404 """ 

405 

406 def auth(self, password, username=None, **kwargs): 

407 """ 

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

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

410 authenticate for the given user. 

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

412 """ 

413 pieces = [] 

414 if username is not None: 

415 pieces.append(username) 

416 pieces.append(password) 

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

418 

419 def bgrewriteaof(self, **kwargs): 

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

421 

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

423 """ 

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

425 

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

427 """ 

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

429 this method is asynchronous and returns immediately. 

430 

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

432 """ 

433 pieces = [] 

434 if schedule: 

435 pieces.append("SCHEDULE") 

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

437 

438 def role(self) -> ResponseT: 

439 """ 

440 Provide information on the role of a Redis instance in 

441 the context of replication, by returning if the instance 

442 is currently a master, slave, or sentinel. 

443 

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

445 """ 

446 return self.execute_command("ROLE") 

447 

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

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

450 

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

452 """ 

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

454 

455 def client_kill_filter( 

456 self, 

457 _id: Union[str, None] = None, 

458 _type: Union[str, None] = None, 

459 addr: Union[str, None] = None, 

460 skipme: Union[bool, None] = None, 

461 laddr: Union[bool, None] = None, 

462 user: str = None, 

463 **kwargs, 

464 ) -> ResponseT: 

465 """ 

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

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

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

469 'master', 'slave' or 'pubsub' 

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

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

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

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

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

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

476 """ 

477 args = [] 

478 if _type is not None: 

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

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

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

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

483 if skipme is not None: 

484 if not isinstance(skipme, bool): 

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

486 if skipme: 

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

488 else: 

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

490 if _id is not None: 

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

492 if addr is not None: 

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

494 if laddr is not None: 

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

496 if user is not None: 

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

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: Union[str, None] = 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 :param _type: optional. one of the client types (normal, master, 

521 replica, pubsub) 

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

523 

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

525 """ 

526 args = [] 

527 if _type is not None: 

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

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

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

531 args.append(b"TYPE") 

532 args.append(_type) 

533 if not isinstance(client_id, list): 

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

535 if client_id: 

536 args.append(b"ID") 

537 args.append(" ".join(client_id)) 

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

539 

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

541 """ 

542 Returns the current connection name 

543 

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

545 """ 

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

547 

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

549 """ 

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

551 redirecting tracking notifications. 

552 

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

554 """ 

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

556 

557 def client_reply( 

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

559 ) -> ResponseT: 

560 """ 

561 Enable and disable redis server replies. 

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

563 ON - The default most with server replies to commands 

564 OFF - Disable server responses to commands 

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

566 

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

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

569 TimeoutError. 

570 The test_client_reply unit test illustrates this, and 

571 conftest.py has a client with a timeout. 

572 

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

574 """ 

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

576 if reply not in replies: 

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

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

579 

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

581 """ 

582 Returns the current connection id 

583 

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

585 """ 

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

587 

588 def client_tracking_on( 

589 self, 

590 clientid: Union[int, None] = None, 

591 prefix: Sequence[KeyT] = [], 

592 bcast: bool = False, 

593 optin: bool = False, 

594 optout: bool = False, 

595 noloop: bool = False, 

596 ) -> ResponseT: 

597 """ 

598 Turn on the tracking mode. 

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

600 

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

602 """ 

603 return self.client_tracking( 

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

605 ) 

606 

607 def client_tracking_off( 

608 self, 

609 clientid: Union[int, None] = None, 

610 prefix: Sequence[KeyT] = [], 

611 bcast: bool = False, 

612 optin: bool = False, 

613 optout: bool = False, 

614 noloop: bool = False, 

615 ) -> ResponseT: 

616 """ 

617 Turn off the tracking mode. 

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

619 

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

621 """ 

622 return self.client_tracking( 

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

624 ) 

625 

626 def client_tracking( 

627 self, 

628 on: bool = True, 

629 clientid: Union[int, None] = None, 

630 prefix: Sequence[KeyT] = [], 

631 bcast: bool = False, 

632 optin: bool = False, 

633 optout: bool = False, 

634 noloop: bool = False, 

635 **kwargs, 

636 ) -> ResponseT: 

637 """ 

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

639 for server assisted client side caching. 

640 

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

642 

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

644 the specified ID. 

645 

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

647 invalidation messages are reported for all the prefixes 

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

649 

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

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

652 after a CLIENT CACHING yes command. 

653 

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

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

656 CLIENT CACHING no command. 

657 

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

659 connection itself. 

660 

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

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

663 

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

665 """ 

666 

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

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

669 

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

671 if clientid is not None: 

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

673 for p in prefix: 

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

675 if bcast: 

676 pieces.append("BCAST") 

677 if optin: 

678 pieces.append("OPTIN") 

679 if optout: 

680 pieces.append("OPTOUT") 

681 if noloop: 

682 pieces.append("NOLOOP") 

683 

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

685 

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

687 """ 

688 Returns the information about the current client connection's 

689 use of the server assisted client side cache. 

690 

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

692 """ 

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

694 

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

696 """ 

697 Sets the current connection name 

698 

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

700 

701 .. note:: 

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

703 

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

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

706 """ 

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

708 

709 def client_unblock( 

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

711 ) -> ResponseT: 

712 """ 

713 Unblocks a connection by its client id. 

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

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

716 regular timeout mechanism. 

717 

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

719 """ 

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

721 if error: 

722 args.append(b"ERROR") 

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

724 

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

726 """ 

727 Suspend all the Redis clients for the specified amount of time 

728 :param timeout: milliseconds to pause clients 

729 

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

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

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

733 a write command. 

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

735 EVAL/EVALSHA: Will block client for all scripts. 

736 PUBLISH: Will block client. 

737 PFCOUNT: Will block client. 

738 WAIT: Acknowledgments will be delayed, so this command will 

739 appear blocked. 

740 """ 

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

742 if not isinstance(timeout, int): 

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

744 if not all: 

745 args.append("WRITE") 

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

747 

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

749 """ 

750 Unpause all redis clients 

751 

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

753 """ 

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

755 

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

757 """ 

758 Sets the client eviction mode for the current connection. 

759 

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

761 """ 

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

763 

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

765 """ 

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

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

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

769 # unless it sends the TOUCH command. 

770 

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

772 """ 

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

774 

775 def command(self, **kwargs): 

776 """ 

777 Returns dict reply of details about all Redis commands. 

778 

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

780 """ 

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

782 

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

784 raise NotImplementedError( 

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

786 ) 

787 

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

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

790 

791 def command_list( 

792 self, 

793 module: Optional[str] = None, 

794 category: Optional[str] = None, 

795 pattern: Optional[str] = None, 

796 ) -> ResponseT: 

797 """ 

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

799 You can use one of the following filters: 

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

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

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

803 

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

805 """ 

806 pieces = [] 

807 if module is not None: 

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

809 if category is not None: 

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

811 if pattern is not None: 

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

813 

814 if pieces: 

815 pieces.insert(0, "FILTERBY") 

816 

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

818 

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

820 """ 

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

822 

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

824 """ 

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

826 

827 def command_docs(self, *args): 

828 """ 

829 This function throws a NotImplementedError since it is intentionally 

830 not supported. 

831 """ 

832 raise NotImplementedError( 

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

834 ) 

835 

836 def config_get( 

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

838 ) -> ResponseT: 

839 """ 

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

841 

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

843 """ 

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

845 

846 def config_set( 

847 self, 

848 name: KeyT, 

849 value: EncodableT, 

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

851 **kwargs, 

852 ) -> ResponseT: 

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

854 

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

856 """ 

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

858 

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

860 """ 

861 Reset runtime statistics 

862 

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

864 """ 

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

866 

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

868 """ 

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

870 

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

872 """ 

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

874 

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

876 """ 

877 Returns the number of keys in the current database 

878 

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

880 """ 

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

882 

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

884 """ 

885 Returns version specific meta information about a given key 

886 

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

888 """ 

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

890 

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

892 raise NotImplementedError( 

893 """ 

894 DEBUG SEGFAULT is intentionally not implemented in the client. 

895 

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

897 """ 

898 ) 

899 

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

901 """ 

902 Echo the string back from the server 

903 

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

905 """ 

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

907 

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

909 """ 

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

911 

912 ``asynchronous`` indicates whether the operation is 

913 executed asynchronously by the server. 

914 

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

916 """ 

917 args = [] 

918 if asynchronous: 

919 args.append(b"ASYNC") 

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

921 

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

923 """ 

924 Delete all keys in the current database. 

925 

926 ``asynchronous`` indicates whether the operation is 

927 executed asynchronously by the server. 

928 

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

930 """ 

931 args = [] 

932 if asynchronous: 

933 args.append(b"ASYNC") 

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

935 

936 def sync(self) -> ResponseT: 

937 """ 

938 Initiates a replication stream from the master. 

939 

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

941 """ 

942 from redis.client import NEVER_DECODE 

943 

944 options = {} 

945 options[NEVER_DECODE] = [] 

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

947 

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

949 """ 

950 Initiates a replication stream from the master. 

951 Newer version for `sync`. 

952 

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

954 """ 

955 from redis.client import NEVER_DECODE 

956 

957 options = {} 

958 options[NEVER_DECODE] = [] 

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

960 

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

962 """ 

963 Swap two databases 

964 

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

966 """ 

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

968 

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

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

971 

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

973 """ 

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

975 

976 def info( 

977 self, section: Union[str, None] = None, *args: List[str], **kwargs 

978 ) -> ResponseT: 

979 """ 

980 Returns a dictionary containing information about the Redis server 

981 

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

983 of information 

984 

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

986 and will generate ResponseError 

987 

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

989 """ 

990 if section is None: 

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

992 else: 

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

994 

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

996 """ 

997 Return a Python datetime object representing the last time the 

998 Redis database was saved to disk 

999 

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

1001 """ 

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

1003 

1004 def latency_doctor(self): 

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

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

1007 

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

1009 """ 

1010 raise NotImplementedError( 

1011 """ 

1012 LATENCY DOCTOR is intentionally not implemented in the client. 

1013 

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

1015 """ 

1016 ) 

1017 

1018 def latency_graph(self): 

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

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

1021 

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

1023 """ 

1024 raise NotImplementedError( 

1025 """ 

1026 LATENCY GRAPH is intentionally not implemented in the client. 

1027 

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

1029 """ 

1030 ) 

1031 

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

1033 """ 

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

1035 

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

1037 """ 

1038 if version_numbers: 

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

1040 else: 

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

1042 

1043 def reset(self) -> ResponseT: 

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

1045 

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

1047 """ 

1048 return self.execute_command("RESET") 

1049 

1050 def migrate( 

1051 self, 

1052 host: str, 

1053 port: int, 

1054 keys: KeysT, 

1055 destination_db: int, 

1056 timeout: int, 

1057 copy: bool = False, 

1058 replace: bool = False, 

1059 auth: Union[str, None] = None, 

1060 **kwargs, 

1061 ) -> ResponseT: 

1062 """ 

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

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

1065 

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

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

1068 command is interrupted. 

1069 

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

1071 the source server. 

1072 

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

1074 on the destination server if they exist. 

1075 

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

1077 the password provided. 

1078 

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

1080 """ 

1081 keys = list_or_args(keys, []) 

1082 if not keys: 

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

1084 pieces = [] 

1085 if copy: 

1086 pieces.append(b"COPY") 

1087 if replace: 

1088 pieces.append(b"REPLACE") 

1089 if auth: 

1090 pieces.append(b"AUTH") 

1091 pieces.append(auth) 

1092 pieces.append(b"KEYS") 

1093 pieces.extend(keys) 

1094 return self.execute_command( 

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

1096 ) 

1097 

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

1099 """ 

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

1101 """ 

1102 return self.execute_command( 

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

1104 ) 

1105 

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

1107 raise NotImplementedError( 

1108 """ 

1109 MEMORY DOCTOR is intentionally not implemented in the client. 

1110 

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

1112 """ 

1113 ) 

1114 

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

1116 raise NotImplementedError( 

1117 """ 

1118 MEMORY HELP is intentionally not implemented in the client. 

1119 

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

1121 """ 

1122 ) 

1123 

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

1125 """ 

1126 Return a dictionary of memory stats 

1127 

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

1129 """ 

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

1131 

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

1133 """ 

1134 Return an internal statistics report from the memory allocator. 

1135 

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

1137 """ 

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

1139 

1140 def memory_usage( 

1141 self, key: KeyT, samples: Union[int, None] = None, **kwargs 

1142 ) -> ResponseT: 

1143 """ 

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

1145 administrative overheads. 

1146 

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

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

1149 all elements. 

1150 

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

1152 """ 

1153 args = [] 

1154 if isinstance(samples, int): 

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

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

1157 

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

1159 """ 

1160 Attempts to purge dirty pages for reclamation by allocator 

1161 

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

1163 """ 

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

1165 

1166 def latency_histogram(self, *args): 

1167 """ 

1168 This function throws a NotImplementedError since it is intentionally 

1169 not supported. 

1170 """ 

1171 raise NotImplementedError( 

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

1173 ) 

1174 

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

1176 """ 

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

1178 

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

1180 """ 

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

1182 

1183 def latency_latest(self) -> ResponseT: 

1184 """ 

1185 Reports the latest latency events logged. 

1186 

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

1188 """ 

1189 return self.execute_command("LATENCY LATEST") 

1190 

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

1192 """ 

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

1194 

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

1196 """ 

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

1198 

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

1200 """ 

1201 Ping the Redis server 

1202 

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

1204 """ 

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

1206 

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

1208 """ 

1209 Ask the server to close the connection. 

1210 

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

1212 """ 

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

1214 

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

1216 """ 

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

1218 Examples of valid arguments include: 

1219 NO ONE (set no replication) 

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

1221 

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

1223 """ 

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

1225 

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

1227 """ 

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

1229 blocking until the save is complete 

1230 

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

1232 """ 

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

1234 

1235 def shutdown( 

1236 self, 

1237 save: bool = False, 

1238 nosave: bool = False, 

1239 now: bool = False, 

1240 force: bool = False, 

1241 abort: bool = False, 

1242 **kwargs, 

1243 ) -> None: 

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

1245 data will be flushed before shutdown. 

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

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

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

1249 are configured. 

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

1251 the shutdown sequence. 

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

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

1254 

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

1256 """ 

1257 if save and nosave: 

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

1259 args = ["SHUTDOWN"] 

1260 if save: 

1261 args.append("SAVE") 

1262 if nosave: 

1263 args.append("NOSAVE") 

1264 if now: 

1265 args.append("NOW") 

1266 if force: 

1267 args.append("FORCE") 

1268 if abort: 

1269 args.append("ABORT") 

1270 try: 

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

1272 except ConnectionError: 

1273 # a ConnectionError here is expected 

1274 return 

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

1276 

1277 def slaveof( 

1278 self, host: Union[str, None] = None, port: Union[int, None] = None, **kwargs 

1279 ) -> ResponseT: 

1280 """ 

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

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

1283 instance is promoted to a master instead. 

1284 

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

1286 """ 

1287 if host is None and port is None: 

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

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

1290 

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

1292 """ 

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

1294 most recent ``num`` items. 

1295 

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

1297 """ 

1298 from redis.client import NEVER_DECODE 

1299 

1300 args = ["SLOWLOG GET"] 

1301 if num is not None: 

1302 args.append(num) 

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

1304 if decode_responses is True: 

1305 kwargs[NEVER_DECODE] = [] 

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

1307 

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

1309 """ 

1310 Get the number of items in the slowlog 

1311 

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

1313 """ 

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

1315 

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

1317 """ 

1318 Remove all items in the slowlog 

1319 

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

1321 """ 

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

1323 

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

1325 """ 

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

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

1328 

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

1330 """ 

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

1332 

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

1334 """ 

1335 Redis synchronous replication 

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

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

1338 reached. 

1339 

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

1341 """ 

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

1343 

1344 def hello(self): 

1345 """ 

1346 This function throws a NotImplementedError since it is intentionally 

1347 not supported. 

1348 """ 

1349 raise NotImplementedError( 

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

1351 ) 

1352 

1353 def failover(self): 

1354 """ 

1355 This function throws a NotImplementedError since it is intentionally 

1356 not supported. 

1357 """ 

1358 raise NotImplementedError( 

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

1360 ) 

1361 

1362 

1363AsyncManagementCommands = ManagementCommands 

1364 

1365 

1366class AsyncManagementCommands(ManagementCommands): 

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

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

1369 

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

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

1372 

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

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

1375 

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

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

1378 

1379 async def shutdown( 

1380 self, 

1381 save: bool = False, 

1382 nosave: bool = False, 

1383 now: bool = False, 

1384 force: bool = False, 

1385 abort: bool = False, 

1386 **kwargs, 

1387 ) -> None: 

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

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

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

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

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

1393 

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

1395 """ 

1396 if save and nosave: 

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

1398 args = ["SHUTDOWN"] 

1399 if save: 

1400 args.append("SAVE") 

1401 if nosave: 

1402 args.append("NOSAVE") 

1403 if now: 

1404 args.append("NOW") 

1405 if force: 

1406 args.append("FORCE") 

1407 if abort: 

1408 args.append("ABORT") 

1409 try: 

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

1411 except ConnectionError: 

1412 # a ConnectionError here is expected 

1413 return 

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

1415 

1416 

1417class BitFieldOperation: 

1418 """ 

1419 Command builder for BITFIELD commands. 

1420 """ 

1421 

1422 def __init__( 

1423 self, 

1424 client: Union["Redis", "AsyncRedis"], 

1425 key: str, 

1426 default_overflow: Union[str, None] = None, 

1427 ): 

1428 self.client = client 

1429 self.key = key 

1430 self._default_overflow = default_overflow 

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

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

1433 self._last_overflow = "WRAP" 

1434 self.reset() 

1435 

1436 def reset(self): 

1437 """ 

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

1439 """ 

1440 self.operations = [] 

1441 self._last_overflow = "WRAP" 

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

1443 

1444 def overflow(self, overflow: str): 

1445 """ 

1446 Update the overflow algorithm of successive INCRBY operations 

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

1448 Redis docs for descriptions of these algorithmsself. 

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

1450 """ 

1451 overflow = overflow.upper() 

1452 if overflow != self._last_overflow: 

1453 self._last_overflow = overflow 

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

1455 return self 

1456 

1457 def incrby( 

1458 self, 

1459 fmt: str, 

1460 offset: BitfieldOffsetT, 

1461 increment: int, 

1462 overflow: Union[str, None] = None, 

1463 ): 

1464 """ 

1465 Increment a bitfield by a given amount. 

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

1467 for an unsigned 8-bit integer. 

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

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

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

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

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

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

1474 descriptions of these algorithms. 

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

1476 """ 

1477 if overflow is not None: 

1478 self.overflow(overflow) 

1479 

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

1481 return self 

1482 

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

1484 """ 

1485 Get the value of a given bitfield. 

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

1487 an unsigned 8-bit integer. 

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

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

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

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

1492 """ 

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

1494 return self 

1495 

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

1497 """ 

1498 Set the value of a given bitfield. 

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

1500 an unsigned 8-bit integer. 

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

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

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

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

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

1506 """ 

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

1508 return self 

1509 

1510 @property 

1511 def command(self): 

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

1513 for ops in self.operations: 

1514 cmd.extend(ops) 

1515 return cmd 

1516 

1517 def execute(self) -> ResponseT: 

1518 """ 

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

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

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

1522 will be present within the pipeline's execute. 

1523 """ 

1524 command = self.command 

1525 self.reset() 

1526 return self.client.execute_command(*command) 

1527 

1528 

1529class BasicKeyCommands(CommandsProtocol): 

1530 """ 

1531 Redis basic key-based commands 

1532 """ 

1533 

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

1535 """ 

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

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

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

1539 

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

1541 """ 

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

1543 

1544 def bitcount( 

1545 self, 

1546 key: KeyT, 

1547 start: Union[int, None] = None, 

1548 end: Union[int, None] = None, 

1549 mode: Optional[str] = None, 

1550 ) -> ResponseT: 

1551 """ 

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

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

1554 

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

1556 """ 

1557 params = [key] 

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

1559 params.append(start) 

1560 params.append(end) 

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

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

1563 if mode is not None: 

1564 params.append(mode) 

1565 return self.execute_command("BITCOUNT", *params) 

1566 

1567 def bitfield( 

1568 self: Union["Redis", "AsyncRedis"], 

1569 key: KeyT, 

1570 default_overflow: Union[str, None] = None, 

1571 ) -> BitFieldOperation: 

1572 """ 

1573 Return a BitFieldOperation instance to conveniently construct one or 

1574 more bitfield operations on ``key``. 

1575 

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

1577 """ 

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

1579 

1580 def bitfield_ro( 

1581 self: Union["Redis", "AsyncRedis"], 

1582 key: KeyT, 

1583 encoding: str, 

1584 offset: BitfieldOffsetT, 

1585 items: Optional[list] = None, 

1586 ) -> ResponseT: 

1587 """ 

1588 Return an array of the specified bitfield values 

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

1590 parameters and remaining values are result of corresponding 

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

1592 Read-only variant of the BITFIELD command. 

1593 

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

1595 """ 

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

1597 

1598 items = items or [] 

1599 for encoding, offset in items: 

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

1601 return self.execute_command("BITFIELD_RO", *params) 

1602 

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

1604 """ 

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

1606 store the result in ``dest``. 

1607 

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

1609 """ 

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

1611 

1612 def bitpos( 

1613 self, 

1614 key: KeyT, 

1615 bit: int, 

1616 start: Union[int, None] = None, 

1617 end: Union[int, None] = None, 

1618 mode: Optional[str] = None, 

1619 ) -> ResponseT: 

1620 """ 

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

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

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

1624 means to look at the first three bytes. 

1625 

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

1627 """ 

1628 if bit not in (0, 1): 

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

1630 params = [key, bit] 

1631 

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

1633 

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

1635 params.append(end) 

1636 elif start is None and end is not None: 

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

1638 

1639 if mode is not None: 

1640 params.append(mode) 

1641 return self.execute_command("BITPOS", *params) 

1642 

1643 def copy( 

1644 self, 

1645 source: str, 

1646 destination: str, 

1647 destination_db: Union[str, None] = None, 

1648 replace: bool = False, 

1649 ) -> ResponseT: 

1650 """ 

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

1652 

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

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

1655 

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

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

1658 the ``destination`` key already exists. 

1659 

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

1661 """ 

1662 params = [source, destination] 

1663 if destination_db is not None: 

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

1665 if replace: 

1666 params.append("REPLACE") 

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

1668 

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

1670 """ 

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

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

1673 

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

1675 """ 

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

1677 

1678 decr = decrby 

1679 

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

1681 """ 

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

1683 """ 

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

1685 

1686 def __delitem__(self, name: KeyT): 

1687 self.delete(name) 

1688 

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

1690 """ 

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

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

1693 

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

1695 """ 

1696 from redis.client import NEVER_DECODE 

1697 

1698 options = {} 

1699 options[NEVER_DECODE] = [] 

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

1701 

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

1703 """ 

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

1705 

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

1707 """ 

1708 return self.execute_command("EXISTS", *names) 

1709 

1710 __contains__ = exists 

1711 

1712 def expire( 

1713 self, 

1714 name: KeyT, 

1715 time: ExpiryT, 

1716 nx: bool = False, 

1717 xx: bool = False, 

1718 gt: bool = False, 

1719 lt: bool = False, 

1720 ) -> ResponseT: 

1721 """ 

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

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

1724 object. 

1725 

1726 Valid options are: 

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

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

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

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

1731 

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

1733 """ 

1734 if isinstance(time, datetime.timedelta): 

1735 time = int(time.total_seconds()) 

1736 

1737 exp_option = list() 

1738 if nx: 

1739 exp_option.append("NX") 

1740 if xx: 

1741 exp_option.append("XX") 

1742 if gt: 

1743 exp_option.append("GT") 

1744 if lt: 

1745 exp_option.append("LT") 

1746 

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

1748 

1749 def expireat( 

1750 self, 

1751 name: KeyT, 

1752 when: AbsExpiryT, 

1753 nx: bool = False, 

1754 xx: bool = False, 

1755 gt: bool = False, 

1756 lt: bool = False, 

1757 ) -> ResponseT: 

1758 """ 

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

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

1761 datetime object. 

1762 

1763 Valid options are: 

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

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

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

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

1768 

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

1770 """ 

1771 if isinstance(when, datetime.datetime): 

1772 when = int(when.timestamp()) 

1773 

1774 exp_option = list() 

1775 if nx: 

1776 exp_option.append("NX") 

1777 if xx: 

1778 exp_option.append("XX") 

1779 if gt: 

1780 exp_option.append("GT") 

1781 if lt: 

1782 exp_option.append("LT") 

1783 

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

1785 

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

1787 """ 

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

1789 at which the given key will expire. 

1790 

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

1792 """ 

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

1794 

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

1796 """ 

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

1798 

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

1800 """ 

1801 return self.execute_command("GET", name) 

1802 

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

1804 """ 

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

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

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

1808 is a string). 

1809 

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

1811 """ 

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

1813 

1814 def getex( 

1815 self, 

1816 name: KeyT, 

1817 ex: Union[ExpiryT, None] = None, 

1818 px: Union[ExpiryT, None] = None, 

1819 exat: Union[AbsExpiryT, None] = None, 

1820 pxat: Union[AbsExpiryT, None] = None, 

1821 persist: bool = False, 

1822 ) -> ResponseT: 

1823 """ 

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

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

1826 additional options. All time parameters can be given as 

1827 datetime.timedelta or integers. 

1828 

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

1830 

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

1832 

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

1834 specified in unix time. 

1835 

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

1837 specified in unix time. 

1838 

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

1840 

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

1842 """ 

1843 

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

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

1846 raise DataError( 

1847 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

1849 ) 

1850 

1851 pieces: list[EncodableT] = [] 

1852 # similar to set command 

1853 if ex is not None: 

1854 pieces.append("EX") 

1855 if isinstance(ex, datetime.timedelta): 

1856 ex = int(ex.total_seconds()) 

1857 pieces.append(ex) 

1858 if px is not None: 

1859 pieces.append("PX") 

1860 if isinstance(px, datetime.timedelta): 

1861 px = int(px.total_seconds() * 1000) 

1862 pieces.append(px) 

1863 # similar to pexpireat command 

1864 if exat is not None: 

1865 pieces.append("EXAT") 

1866 if isinstance(exat, datetime.datetime): 

1867 exat = int(exat.timestamp()) 

1868 pieces.append(exat) 

1869 if pxat is not None: 

1870 pieces.append("PXAT") 

1871 if isinstance(pxat, datetime.datetime): 

1872 pxat = int(pxat.timestamp() * 1000) 

1873 pieces.append(pxat) 

1874 if persist: 

1875 pieces.append("PERSIST") 

1876 

1877 return self.execute_command("GETEX", name, *pieces) 

1878 

1879 def __getitem__(self, name: KeyT): 

1880 """ 

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

1882 doesn't exist. 

1883 """ 

1884 value = self.get(name) 

1885 if value is not None: 

1886 return value 

1887 raise KeyError(name) 

1888 

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

1890 """ 

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

1892 

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

1894 """ 

1895 return self.execute_command("GETBIT", name, offset) 

1896 

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

1898 """ 

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

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

1901 

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

1903 """ 

1904 return self.execute_command("GETRANGE", key, start, end) 

1905 

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

1907 """ 

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

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

1910 

1911 As per Redis 6.2, GETSET is considered deprecated. 

1912 Please use SET with GET parameter in new code. 

1913 

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

1915 """ 

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

1917 

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

1919 """ 

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

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

1922 

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

1924 """ 

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

1926 

1927 incr = incrby 

1928 

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

1930 """ 

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

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

1933 

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

1935 """ 

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

1937 

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

1939 """ 

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

1941 

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

1943 """ 

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

1945 

1946 def lmove( 

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

1948 ) -> ResponseT: 

1949 """ 

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

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

1952 Returns the element being popped and pushed. 

1953 

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

1955 """ 

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

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

1958 

1959 def blmove( 

1960 self, 

1961 first_list: str, 

1962 second_list: str, 

1963 timeout: int, 

1964 src: str = "LEFT", 

1965 dest: str = "RIGHT", 

1966 ) -> ResponseT: 

1967 """ 

1968 Blocking version of lmove. 

1969 

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

1971 """ 

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

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

1974 

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

1976 """ 

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

1978 

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

1980 """ 

1981 from redis.client import EMPTY_RESPONSE 

1982 

1983 args = list_or_args(keys, args) 

1984 options = {} 

1985 if not args: 

1986 options[EMPTY_RESPONSE] = [] 

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

1988 

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

1990 """ 

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

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

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

1994 

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

1996 """ 

1997 items = [] 

1998 for pair in mapping.items(): 

1999 items.extend(pair) 

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

2001 

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

2003 """ 

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

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

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

2007 Returns a boolean indicating if the operation was successful. 

2008 

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

2010 """ 

2011 items = [] 

2012 for pair in mapping.items(): 

2013 items.extend(pair) 

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

2015 

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

2017 """ 

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

2019 

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

2021 """ 

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

2023 

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

2025 """ 

2026 Removes an expiration on ``name`` 

2027 

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

2029 """ 

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

2031 

2032 def pexpire( 

2033 self, 

2034 name: KeyT, 

2035 time: ExpiryT, 

2036 nx: bool = False, 

2037 xx: bool = False, 

2038 gt: bool = False, 

2039 lt: bool = False, 

2040 ) -> ResponseT: 

2041 """ 

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

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

2044 integer or a Python timedelta object. 

2045 

2046 Valid options are: 

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

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

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

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

2051 

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

2053 """ 

2054 if isinstance(time, datetime.timedelta): 

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

2056 

2057 exp_option = list() 

2058 if nx: 

2059 exp_option.append("NX") 

2060 if xx: 

2061 exp_option.append("XX") 

2062 if gt: 

2063 exp_option.append("GT") 

2064 if lt: 

2065 exp_option.append("LT") 

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

2067 

2068 def pexpireat( 

2069 self, 

2070 name: KeyT, 

2071 when: AbsExpiryT, 

2072 nx: bool = False, 

2073 xx: bool = False, 

2074 gt: bool = False, 

2075 lt: bool = False, 

2076 ) -> ResponseT: 

2077 """ 

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

2079 can be represented as an integer representing unix time in 

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

2081 

2082 Valid options are: 

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

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

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

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

2087 

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

2089 """ 

2090 if isinstance(when, datetime.datetime): 

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

2092 exp_option = list() 

2093 if nx: 

2094 exp_option.append("NX") 

2095 if xx: 

2096 exp_option.append("XX") 

2097 if gt: 

2098 exp_option.append("GT") 

2099 if lt: 

2100 exp_option.append("LT") 

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

2102 

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

2104 """ 

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

2106 at which the given key will expire. 

2107 

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

2109 """ 

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

2111 

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

2113 """ 

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

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

2116 timedelta object 

2117 

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

2119 """ 

2120 if isinstance(time_ms, datetime.timedelta): 

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

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

2123 

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

2125 """ 

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

2127 

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

2129 """ 

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

2131 

2132 def hrandfield( 

2133 self, key: str, count: int = None, withvalues: bool = False 

2134 ) -> ResponseT: 

2135 """ 

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

2137 

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

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

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

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

2142 specified count. 

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

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

2145 

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

2147 """ 

2148 params = [] 

2149 if count is not None: 

2150 params.append(count) 

2151 if withvalues: 

2152 params.append("WITHVALUES") 

2153 

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

2155 

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

2157 """ 

2158 Returns the name of a random key 

2159 

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

2161 """ 

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

2163 

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

2165 """ 

2166 Rename key ``src`` to ``dst`` 

2167 

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

2169 """ 

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

2171 

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

2173 """ 

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

2175 

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

2177 """ 

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

2179 

2180 def restore( 

2181 self, 

2182 name: KeyT, 

2183 ttl: float, 

2184 value: EncodableT, 

2185 replace: bool = False, 

2186 absttl: bool = False, 

2187 idletime: Union[int, None] = None, 

2188 frequency: Union[int, None] = None, 

2189 ) -> ResponseT: 

2190 """ 

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

2192 using DUMP. 

2193 

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

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

2196 

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

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

2199 greater). 

2200 

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

2202 key must be idle, prior to execution. 

2203 

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

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

2206 

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

2208 """ 

2209 params = [name, ttl, value] 

2210 if replace: 

2211 params.append("REPLACE") 

2212 if absttl: 

2213 params.append("ABSTTL") 

2214 if idletime is not None: 

2215 params.append("IDLETIME") 

2216 try: 

2217 params.append(int(idletime)) 

2218 except ValueError: 

2219 raise DataError("idletimemust be an integer") 

2220 

2221 if frequency is not None: 

2222 params.append("FREQ") 

2223 try: 

2224 params.append(int(frequency)) 

2225 except ValueError: 

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

2227 

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

2229 

2230 def set( 

2231 self, 

2232 name: KeyT, 

2233 value: EncodableT, 

2234 ex: Union[ExpiryT, None] = None, 

2235 px: Union[ExpiryT, None] = None, 

2236 nx: bool = False, 

2237 xx: bool = False, 

2238 keepttl: bool = False, 

2239 get: bool = False, 

2240 exat: Union[AbsExpiryT, None] = None, 

2241 pxat: Union[AbsExpiryT, None] = None, 

2242 ) -> ResponseT: 

2243 """ 

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

2245 

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

2247 

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

2249 

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

2251 if it does not exist. 

2252 

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

2254 if it already exists. 

2255 

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

2257 (Available since Redis 6.0) 

2258 

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

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

2261 (Available since Redis 6.2) 

2262 

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

2264 specified in unix time. 

2265 

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

2267 specified in unix time. 

2268 

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

2270 """ 

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

2272 options = {} 

2273 if ex is not None: 

2274 pieces.append("EX") 

2275 if isinstance(ex, datetime.timedelta): 

2276 pieces.append(int(ex.total_seconds())) 

2277 elif isinstance(ex, int): 

2278 pieces.append(ex) 

2279 elif isinstance(ex, str) and ex.isdigit(): 

2280 pieces.append(int(ex)) 

2281 else: 

2282 raise DataError("ex must be datetime.timedelta or int") 

2283 if px is not None: 

2284 pieces.append("PX") 

2285 if isinstance(px, datetime.timedelta): 

2286 pieces.append(int(px.total_seconds() * 1000)) 

2287 elif isinstance(px, int): 

2288 pieces.append(px) 

2289 else: 

2290 raise DataError("px must be datetime.timedelta or int") 

2291 if exat is not None: 

2292 pieces.append("EXAT") 

2293 if isinstance(exat, datetime.datetime): 

2294 exat = int(exat.timestamp()) 

2295 pieces.append(exat) 

2296 if pxat is not None: 

2297 pieces.append("PXAT") 

2298 if isinstance(pxat, datetime.datetime): 

2299 pxat = int(pxat.timestamp() * 1000) 

2300 pieces.append(pxat) 

2301 if keepttl: 

2302 pieces.append("KEEPTTL") 

2303 

2304 if nx: 

2305 pieces.append("NX") 

2306 if xx: 

2307 pieces.append("XX") 

2308 

2309 if get: 

2310 pieces.append("GET") 

2311 options["get"] = True 

2312 

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

2314 

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

2316 self.set(name, value) 

2317 

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

2319 """ 

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

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

2322 

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

2324 """ 

2325 value = value and 1 or 0 

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

2327 

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

2329 """ 

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

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

2332 timedelta object. 

2333 

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

2335 """ 

2336 if isinstance(time, datetime.timedelta): 

2337 time = int(time.total_seconds()) 

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

2339 

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

2341 """ 

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

2343 

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

2345 """ 

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

2347 

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

2349 """ 

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

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

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

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

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

2355 of what's being injected. 

2356 

2357 Returns the length of the new string. 

2358 

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

2360 """ 

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

2362 

2363 def stralgo( 

2364 self, 

2365 algo: Literal["LCS"], 

2366 value1: KeyT, 

2367 value2: KeyT, 

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

2369 len: bool = False, 

2370 idx: bool = False, 

2371 minmatchlen: Union[int, None] = None, 

2372 withmatchlen: bool = False, 

2373 **kwargs, 

2374 ) -> ResponseT: 

2375 """ 

2376 Implements complex algorithms that operate on strings. 

2377 Right now the only algorithm implemented is the LCS algorithm 

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

2379 implemented in the future. 

2380 

2381 ``algo`` Right now must be LCS 

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

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

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

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

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

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

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

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

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

2391 

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

2393 """ 

2394 # check validity 

2395 supported_algo = ["LCS"] 

2396 if algo not in supported_algo: 

2397 supported_algos_str = ", ".join(supported_algo) 

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

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

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

2401 if len and idx: 

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

2403 

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

2405 if len: 

2406 pieces.append(b"LEN") 

2407 if idx: 

2408 pieces.append(b"IDX") 

2409 try: 

2410 int(minmatchlen) 

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

2412 except TypeError: 

2413 pass 

2414 if withmatchlen: 

2415 pieces.append(b"WITHMATCHLEN") 

2416 

2417 return self.execute_command( 

2418 "STRALGO", 

2419 *pieces, 

2420 len=len, 

2421 idx=idx, 

2422 minmatchlen=minmatchlen, 

2423 withmatchlen=withmatchlen, 

2424 **kwargs, 

2425 ) 

2426 

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

2428 """ 

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

2430 

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

2432 """ 

2433 return self.execute_command("STRLEN", name) 

2434 

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

2436 """ 

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

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

2439 """ 

2440 return self.execute_command("SUBSTR", name, start, end) 

2441 

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

2443 """ 

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

2445 if it does not exist. 

2446 

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

2448 """ 

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

2450 

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

2452 """ 

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

2454 

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

2456 """ 

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

2458 

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

2460 """ 

2461 Returns the type of key ``name`` 

2462 

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

2464 """ 

2465 return self.execute_command("TYPE", name) 

2466 

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

2468 """ 

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

2470 

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

2472 """ 

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

2474 

2475 def unwatch(self) -> None: 

2476 """ 

2477 Unwatches the value at key ``name``, or None of the key doesn't exist 

2478 

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

2480 """ 

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

2482 

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

2484 """ 

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

2486 

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

2488 """ 

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

2490 

2491 def lcs( 

2492 self, 

2493 key1: str, 

2494 key2: str, 

2495 len: Optional[bool] = False, 

2496 idx: Optional[bool] = False, 

2497 minmatchlen: Optional[int] = 0, 

2498 withmatchlen: Optional[bool] = False, 

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

2500 """ 

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

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

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

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

2505 the given ``minmatchlen``. 

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

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

2508 """ 

2509 pieces = [key1, key2] 

2510 if len: 

2511 pieces.append("LEN") 

2512 if idx: 

2513 pieces.append("IDX") 

2514 if minmatchlen != 0: 

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

2516 if withmatchlen: 

2517 pieces.append("WITHMATCHLEN") 

2518 return self.execute_command("LCS", *pieces) 

2519 

2520 

2521class AsyncBasicKeyCommands(BasicKeyCommands): 

2522 def __delitem__(self, name: KeyT): 

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

2524 

2525 def __contains__(self, name: KeyT): 

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

2527 

2528 def __getitem__(self, name: KeyT): 

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

2530 

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

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

2533 

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

2535 return super().watch(*names) 

2536 

2537 async def unwatch(self) -> None: 

2538 return super().unwatch() 

2539 

2540 

2541class ListCommands(CommandsProtocol): 

2542 """ 

2543 Redis commands for List data type. 

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

2545 """ 

2546 

2547 def blpop( 

2548 self, keys: List, timeout: Optional[int] = 0 

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

2550 """ 

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

2552 named in the ``keys`` list. 

2553 

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

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

2556 of the lists. 

2557 

2558 If timeout is 0, then block indefinitely. 

2559 

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

2561 """ 

2562 if timeout is None: 

2563 timeout = 0 

2564 keys = list_or_args(keys, None) 

2565 keys.append(timeout) 

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

2567 

2568 def brpop( 

2569 self, keys: List, timeout: Optional[int] = 0 

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

2571 """ 

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

2573 named in the ``keys`` list. 

2574 

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

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

2577 of the lists. 

2578 

2579 If timeout is 0, then block indefinitely. 

2580 

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

2582 """ 

2583 if timeout is None: 

2584 timeout = 0 

2585 keys = list_or_args(keys, None) 

2586 keys.append(timeout) 

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

2588 

2589 def brpoplpush( 

2590 self, src: str, dst: str, timeout: Optional[int] = 0 

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

2592 """ 

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

2594 and then return it. 

2595 

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

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

2598 forever. 

2599 

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

2601 """ 

2602 if timeout is None: 

2603 timeout = 0 

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

2605 

2606 def blmpop( 

2607 self, 

2608 timeout: float, 

2609 numkeys: int, 

2610 *args: List[str], 

2611 direction: str, 

2612 count: Optional[int] = 1, 

2613 ) -> Optional[list]: 

2614 """ 

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

2616 of provided key names. 

2617 

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

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

2620 

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

2622 """ 

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

2624 

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

2626 

2627 def lmpop( 

2628 self, 

2629 num_keys: int, 

2630 *args: List[str], 

2631 direction: str, 

2632 count: Optional[int] = 1, 

2633 ) -> Union[Awaitable[list], list]: 

2634 """ 

2635 Pop ``count`` values (default 1) first non-empty list key from the list 

2636 of args provided key names. 

2637 

2638 For more information see https://redis.io/commands/lmpop 

2639 """ 

2640 args = [num_keys] + list(args) + [direction] 

2641 if count != 1: 

2642 args.extend(["COUNT", count]) 

2643 

2644 return self.execute_command("LMPOP", *args) 

2645 

2646 def lindex( 

2647 self, name: str, index: int 

2648 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

2649 """ 

2650 Return the item from list ``name`` at position ``index`` 

2651 

2652 Negative indexes are supported and will return an item at the 

2653 end of the list 

2654 

2655 For more information see https://redis.io/commands/lindex 

2656 """ 

2657 return self.execute_command("LINDEX", name, index) 

2658 

2659 def linsert( 

2660 self, name: str, where: str, refvalue: str, value: str 

2661 ) -> Union[Awaitable[int], int]: 

2662 """ 

2663 Insert ``value`` in list ``name`` either immediately before or after 

2664 [``where``] ``refvalue`` 

2665 

2666 Returns the new length of the list on success or -1 if ``refvalue`` 

2667 is not in the list. 

2668 

2669 For more information see https://redis.io/commands/linsert 

2670 """ 

2671 return self.execute_command("LINSERT", name, where, refvalue, value) 

2672 

2673 def llen(self, name: str) -> Union[Awaitable[int], int]: 

2674 """ 

2675 Return the length of the list ``name`` 

2676 

2677 For more information see https://redis.io/commands/llen 

2678 """ 

2679 return self.execute_command("LLEN", name) 

2680 

2681 def lpop( 

2682 self, 

2683 name: str, 

2684 count: Optional[int] = None, 

2685 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]: 

2686 """ 

2687 Removes and returns the first elements of the list ``name``. 

2688 

2689 By default, the command pops a single element from the beginning of 

2690 the list. When provided with the optional ``count`` argument, the reply 

2691 will consist of up to count elements, depending on the list's length. 

2692 

2693 For more information see https://redis.io/commands/lpop 

2694 """ 

2695 if count is not None: 

2696 return self.execute_command("LPOP", name, count) 

2697 else: 

2698 return self.execute_command("LPOP", name) 

2699 

2700 def lpush(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2701 """ 

2702 Push ``values`` onto the head of the list ``name`` 

2703 

2704 For more information see https://redis.io/commands/lpush 

2705 """ 

2706 return self.execute_command("LPUSH", name, *values) 

2707 

2708 def lpushx(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2709 """ 

2710 Push ``value`` onto the head of the list ``name`` if ``name`` exists 

2711 

2712 For more information see https://redis.io/commands/lpushx 

2713 """ 

2714 return self.execute_command("LPUSHX", name, *values) 

2715 

2716 def lrange(self, name: str, start: int, end: int) -> Union[Awaitable[list], list]: 

2717 """ 

2718 Return a slice of the list ``name`` between 

2719 position ``start`` and ``end`` 

2720 

2721 ``start`` and ``end`` can be negative numbers just like 

2722 Python slicing notation 

2723 

2724 For more information see https://redis.io/commands/lrange 

2725 """ 

2726 return self.execute_command("LRANGE", name, start, end) 

2727 

2728 def lrem(self, name: str, count: int, value: str) -> Union[Awaitable[int], int]: 

2729 """ 

2730 Remove the first ``count`` occurrences of elements equal to ``value`` 

2731 from the list stored at ``name``. 

2732 

2733 The count argument influences the operation in the following ways: 

2734 count > 0: Remove elements equal to value moving from head to tail. 

2735 count < 0: Remove elements equal to value moving from tail to head. 

2736 count = 0: Remove all elements equal to value. 

2737 

2738 For more information see https://redis.io/commands/lrem 

2739 """ 

2740 return self.execute_command("LREM", name, count, value) 

2741 

2742 def lset(self, name: str, index: int, value: str) -> Union[Awaitable[str], str]: 

2743 """ 

2744 Set element at ``index`` of list ``name`` to ``value`` 

2745 

2746 For more information see https://redis.io/commands/lset 

2747 """ 

2748 return self.execute_command("LSET", name, index, value) 

2749 

2750 def ltrim(self, name: str, start: int, end: int) -> Union[Awaitable[str], str]: 

2751 """ 

2752 Trim the list ``name``, removing all values not within the slice 

2753 between ``start`` and ``end`` 

2754 

2755 ``start`` and ``end`` can be negative numbers just like 

2756 Python slicing notation 

2757 

2758 For more information see https://redis.io/commands/ltrim 

2759 """ 

2760 return self.execute_command("LTRIM", name, start, end) 

2761 

2762 def rpop( 

2763 self, 

2764 name: str, 

2765 count: Optional[int] = None, 

2766 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]: 

2767 """ 

2768 Removes and returns the last elements of the list ``name``. 

2769 

2770 By default, the command pops a single element from the end of the list. 

2771 When provided with the optional ``count`` argument, the reply will 

2772 consist of up to count elements, depending on the list's length. 

2773 

2774 For more information see https://redis.io/commands/rpop 

2775 """ 

2776 if count is not None: 

2777 return self.execute_command("RPOP", name, count) 

2778 else: 

2779 return self.execute_command("RPOP", name) 

2780 

2781 def rpoplpush(self, src: str, dst: str) -> Union[Awaitable[str], str]: 

2782 """ 

2783 RPOP a value off of the ``src`` list and atomically LPUSH it 

2784 on to the ``dst`` list. Returns the value. 

2785 

2786 For more information see https://redis.io/commands/rpoplpush 

2787 """ 

2788 return self.execute_command("RPOPLPUSH", src, dst) 

2789 

2790 def rpush(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2791 """ 

2792 Push ``values`` onto the tail of the list ``name`` 

2793 

2794 For more information see https://redis.io/commands/rpush 

2795 """ 

2796 return self.execute_command("RPUSH", name, *values) 

2797 

2798 def rpushx(self, name: str, value: str) -> Union[Awaitable[int], int]: 

2799 """ 

2800 Push ``value`` onto the tail of the list ``name`` if ``name`` exists 

2801 

2802 For more information see https://redis.io/commands/rpushx 

2803 """ 

2804 return self.execute_command("RPUSHX", name, value) 

2805 

2806 def lpos( 

2807 self, 

2808 name: str, 

2809 value: str, 

2810 rank: Optional[int] = None, 

2811 count: Optional[int] = None, 

2812 maxlen: Optional[int] = None, 

2813 ) -> Union[str, List, None]: 

2814 """ 

2815 Get position of ``value`` within the list ``name`` 

2816 

2817 If specified, ``rank`` indicates the "rank" of the first element to 

2818 return in case there are multiple copies of ``value`` in the list. 

2819 By default, LPOS returns the position of the first occurrence of 

2820 ``value`` in the list. When ``rank`` 2, LPOS returns the position of 

2821 the second ``value`` in the list. If ``rank`` is negative, LPOS 

2822 searches the list in reverse. For example, -1 would return the 

2823 position of the last occurrence of ``value`` and -2 would return the 

2824 position of the next to last occurrence of ``value``. 

2825 

2826 If specified, ``count`` indicates that LPOS should return a list of 

2827 up to ``count`` positions. A ``count`` of 2 would return a list of 

2828 up to 2 positions. A ``count`` of 0 returns a list of all positions 

2829 matching ``value``. When ``count`` is specified and but ``value`` 

2830 does not exist in the list, an empty list is returned. 

2831 

2832 If specified, ``maxlen`` indicates the maximum number of list 

2833 elements to scan. A ``maxlen`` of 1000 will only return the 

2834 position(s) of items within the first 1000 entries in the list. 

2835 A ``maxlen`` of 0 (the default) will scan the entire list. 

2836 

2837 For more information see https://redis.io/commands/lpos 

2838 """ 

2839 pieces: list[EncodableT] = [name, value] 

2840 if rank is not None: 

2841 pieces.extend(["RANK", rank]) 

2842 

2843 if count is not None: 

2844 pieces.extend(["COUNT", count]) 

2845 

2846 if maxlen is not None: 

2847 pieces.extend(["MAXLEN", maxlen]) 

2848 

2849 return self.execute_command("LPOS", *pieces) 

2850 

2851 def sort( 

2852 self, 

2853 name: str, 

2854 start: Optional[int] = None, 

2855 num: Optional[int] = None, 

2856 by: Optional[str] = None, 

2857 get: Optional[List[str]] = None, 

2858 desc: bool = False, 

2859 alpha: bool = False, 

2860 store: Optional[str] = None, 

2861 groups: Optional[bool] = False, 

2862 ) -> Union[List, int]: 

2863 """ 

2864 Sort and return the list, set or sorted set at ``name``. 

2865 

2866 ``start`` and ``num`` allow for paging through the sorted data 

2867 

2868 ``by`` allows using an external key to weight and sort the items. 

2869 Use an "*" to indicate where in the key the item value is located 

2870 

2871 ``get`` allows for returning items from external keys rather than the 

2872 sorted data itself. Use an "*" to indicate where in the key 

2873 the item value is located 

2874 

2875 ``desc`` allows for reversing the sort 

2876 

2877 ``alpha`` allows for sorting lexicographically rather than numerically 

2878 

2879 ``store`` allows for storing the result of the sort into 

2880 the key ``store`` 

2881 

2882 ``groups`` if set to True and if ``get`` contains at least two 

2883 elements, sort will return a list of tuples, each containing the 

2884 values fetched from the arguments to ``get``. 

2885 

2886 For more information see https://redis.io/commands/sort 

2887 """ 

2888 if (start is not None and num is None) or (num is not None and start is None): 

2889 raise DataError("``start`` and ``num`` must both be specified") 

2890 

2891 pieces: list[EncodableT] = [name] 

2892 if by is not None: 

2893 pieces.extend([b"BY", by]) 

2894 if start is not None and num is not None: 

2895 pieces.extend([b"LIMIT", start, num]) 

2896 if get is not None: 

2897 # If get is a string assume we want to get a single value. 

2898 # Otherwise assume it's an interable and we want to get multiple 

2899 # values. We can't just iterate blindly because strings are 

2900 # iterable. 

2901 if isinstance(get, (bytes, str)): 

2902 pieces.extend([b"GET", get]) 

2903 else: 

2904 for g in get: 

2905 pieces.extend([b"GET", g]) 

2906 if desc: 

2907 pieces.append(b"DESC") 

2908 if alpha: 

2909 pieces.append(b"ALPHA") 

2910 if store is not None: 

2911 pieces.extend([b"STORE", store]) 

2912 if groups: 

2913 if not get or isinstance(get, (bytes, str)) or len(get) < 2: 

2914 raise DataError( 

2915 'when using "groups" the "get" argument ' 

2916 "must be specified and contain at least " 

2917 "two keys" 

2918 ) 

2919 

2920 options = {"groups": len(get) if groups else None} 

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: Union[int, None] = None, 

2971 _type: Union[str, None] = 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: Union[int, None] = None, 

3003 _type: Union[str, None] = 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: Union[int, None] = 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: Union[int, None] = 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: Union[int, None] = None, 

3076 ) -> ResponseT: 

3077 """ 

3078 Incrementally return key/value slices in a hash. Also return a cursor 

3079 indicating the scan position. 

3080 

3081 ``match`` allows for filtering the keys by pattern 

3082 

3083 ``count`` allows for hint the minimum number of returns 

3084 

3085 For more information see https://redis.io/commands/hscan 

3086 """ 

3087 pieces: list[EncodableT] = [name, cursor] 

3088 if match is not None: 

3089 pieces.extend([b"MATCH", match]) 

3090 if count is not None: 

3091 pieces.extend([b"COUNT", count]) 

3092 return self.execute_command("HSCAN", *pieces) 

3093 

3094 def hscan_iter( 

3095 self, 

3096 name: str, 

3097 match: Union[PatternT, None] = None, 

3098 count: Union[int, None] = None, 

3099 ) -> Iterator: 

3100 """ 

3101 Make an iterator using the HSCAN command so that the client doesn't 

3102 need to remember the cursor position. 

3103 

3104 ``match`` allows for filtering the keys by pattern 

3105 

3106 ``count`` allows for hint the minimum number of returns 

3107 """ 

3108 cursor = "0" 

3109 while cursor != 0: 

3110 cursor, data = self.hscan(name, cursor=cursor, match=match, count=count) 

3111 yield from data.items() 

3112 

3113 def zscan( 

3114 self, 

3115 name: KeyT, 

3116 cursor: int = 0, 

3117 match: Union[PatternT, None] = None, 

3118 count: Union[int, None] = None, 

3119 score_cast_func: Union[type, Callable] = float, 

3120 ) -> ResponseT: 

3121 """ 

3122 Incrementally return lists of elements in a sorted set. Also return a 

3123 cursor indicating the scan position. 

3124 

3125 ``match`` allows for filtering the keys by pattern 

3126 

3127 ``count`` allows for hint the minimum number of returns 

3128 

3129 ``score_cast_func`` a callable used to cast the score return value 

3130 

3131 For more information see https://redis.io/commands/zscan 

3132 """ 

3133 pieces = [name, cursor] 

3134 if match is not None: 

3135 pieces.extend([b"MATCH", match]) 

3136 if count is not None: 

3137 pieces.extend([b"COUNT", count]) 

3138 options = {"score_cast_func": score_cast_func} 

3139 return self.execute_command("ZSCAN", *pieces, **options) 

3140 

3141 def zscan_iter( 

3142 self, 

3143 name: KeyT, 

3144 match: Union[PatternT, None] = None, 

3145 count: Union[int, None] = None, 

3146 score_cast_func: Union[type, Callable] = float, 

3147 ) -> Iterator: 

3148 """ 

3149 Make an iterator using the ZSCAN command so that the client doesn't 

3150 need to remember the cursor position. 

3151 

3152 ``match`` allows for filtering the keys by pattern 

3153 

3154 ``count`` allows for hint the minimum number of returns 

3155 

3156 ``score_cast_func`` a callable used to cast the score return value 

3157 """ 

3158 cursor = "0" 

3159 while cursor != 0: 

3160 cursor, data = self.zscan( 

3161 name, 

3162 cursor=cursor, 

3163 match=match, 

3164 count=count, 

3165 score_cast_func=score_cast_func, 

3166 ) 

3167 yield from data 

3168 

3169 

3170class AsyncScanCommands(ScanCommands): 

3171 async def scan_iter( 

3172 self, 

3173 match: Union[PatternT, None] = None, 

3174 count: Union[int, None] = None, 

3175 _type: Union[str, None] = None, 

3176 **kwargs, 

3177 ) -> AsyncIterator: 

3178 """ 

3179 Make an iterator using the SCAN command so that the client doesn't 

3180 need to remember the cursor position. 

3181 

3182 ``match`` allows for filtering the keys by pattern 

3183 

3184 ``count`` provides a hint to Redis about the number of keys to 

3185 return per batch. 

3186 

3187 ``_type`` filters the returned values by a particular Redis type. 

3188 Stock Redis instances allow for the following types: 

3189 HASH, LIST, SET, STREAM, STRING, ZSET 

3190 Additionally, Redis modules can expose other types as well. 

3191 """ 

3192 cursor = "0" 

3193 while cursor != 0: 

3194 cursor, data = await self.scan( 

3195 cursor=cursor, match=match, count=count, _type=_type, **kwargs 

3196 ) 

3197 for d in data: 

3198 yield d 

3199 

3200 async def sscan_iter( 

3201 self, 

3202 name: KeyT, 

3203 match: Union[PatternT, None] = None, 

3204 count: Union[int, None] = None, 

3205 ) -> AsyncIterator: 

3206 """ 

3207 Make an iterator using the SSCAN command so that the client doesn't 

3208 need to remember the cursor position. 

3209 

3210 ``match`` allows for filtering the keys by pattern 

3211 

3212 ``count`` allows for hint the minimum number of returns 

3213 """ 

3214 cursor = "0" 

3215 while cursor != 0: 

3216 cursor, data = await self.sscan( 

3217 name, cursor=cursor, match=match, count=count 

3218 ) 

3219 for d in data: 

3220 yield d 

3221 

3222 async def hscan_iter( 

3223 self, 

3224 name: str, 

3225 match: Union[PatternT, None] = None, 

3226 count: Union[int, None] = None, 

3227 ) -> AsyncIterator: 

3228 """ 

3229 Make an iterator using the HSCAN command so that the client doesn't 

3230 need to remember the cursor position. 

3231 

3232 ``match`` allows for filtering the keys by pattern 

3233 

3234 ``count`` allows for hint the minimum number of returns 

3235 """ 

3236 cursor = "0" 

3237 while cursor != 0: 

3238 cursor, data = await self.hscan( 

3239 name, cursor=cursor, match=match, count=count 

3240 ) 

3241 for it in data.items(): 

3242 yield it 

3243 

3244 async def zscan_iter( 

3245 self, 

3246 name: KeyT, 

3247 match: Union[PatternT, None] = None, 

3248 count: Union[int, None] = None, 

3249 score_cast_func: Union[type, Callable] = float, 

3250 ) -> AsyncIterator: 

3251 """ 

3252 Make an iterator using the ZSCAN command so that the client doesn't 

3253 need to remember the cursor position. 

3254 

3255 ``match`` allows for filtering the keys by pattern 

3256 

3257 ``count`` allows for hint the minimum number of returns 

3258 

3259 ``score_cast_func`` a callable used to cast the score return value 

3260 """ 

3261 cursor = "0" 

3262 while cursor != 0: 

3263 cursor, data = await self.zscan( 

3264 name, 

3265 cursor=cursor, 

3266 match=match, 

3267 count=count, 

3268 score_cast_func=score_cast_func, 

3269 ) 

3270 for d in data: 

3271 yield d 

3272 

3273 

3274class SetCommands(CommandsProtocol): 

3275 """ 

3276 Redis commands for Set data type. 

3277 see: https://redis.io/topics/data-types#sets 

3278 """ 

3279 

3280 def sadd(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

3281 """ 

3282 Add ``value(s)`` to set ``name`` 

3283 

3284 For more information see https://redis.io/commands/sadd 

3285 """ 

3286 return self.execute_command("SADD", name, *values) 

3287 

3288 def scard(self, name: str) -> Union[Awaitable[int], int]: 

3289 """ 

3290 Return the number of elements in set ``name`` 

3291 

3292 For more information see https://redis.io/commands/scard 

3293 """ 

3294 return self.execute_command("SCARD", name) 

3295 

3296 def sdiff(self, keys: List, *args: List) -> Union[Awaitable[list], list]: 

3297 """ 

3298 Return the difference of sets specified by ``keys`` 

3299 

3300 For more information see https://redis.io/commands/sdiff 

3301 """ 

3302 args = list_or_args(keys, args) 

3303 return self.execute_command("SDIFF", *args) 

3304 

3305 def sdiffstore( 

3306 self, dest: str, keys: List, *args: List 

3307 ) -> Union[Awaitable[int], int]: 

3308 """ 

3309 Store the difference of sets specified by ``keys`` into a new 

3310 set named ``dest``. Returns the number of keys in the new set. 

3311 

3312 For more information see https://redis.io/commands/sdiffstore 

3313 """ 

3314 args = list_or_args(keys, args) 

3315 return self.execute_command("SDIFFSTORE", dest, *args) 

3316 

3317 def sinter(self, keys: List, *args: List) -> Union[Awaitable[list], list]: 

3318 """ 

3319 Return the intersection of sets specified by ``keys`` 

3320 

3321 For more information see https://redis.io/commands/sinter 

3322 """ 

3323 args = list_or_args(keys, args) 

3324 return self.execute_command("SINTER", *args) 

3325 

3326 def sintercard( 

3327 self, numkeys: int, keys: List[str], limit: int = 0 

3328 ) -> Union[Awaitable[int], int]: 

3329 """ 

3330 Return the cardinality of the intersect of multiple sets specified by ``keys`. 

3331 

3332 When LIMIT provided (defaults to 0 and means unlimited), if the intersection 

3333 cardinality reaches limit partway through the computation, the algorithm will 

3334 exit and yield limit as the cardinality 

3335 

3336 For more information see https://redis.io/commands/sintercard 

3337 """ 

3338 args = [numkeys, *keys, "LIMIT", limit] 

3339 return self.execute_command("SINTERCARD", *args) 

3340 

3341 def sinterstore( 

3342 self, dest: str, keys: List, *args: List 

3343 ) -> Union[Awaitable[int], int]: 

3344 """ 

3345 Store the intersection of sets specified by ``keys`` into a new 

3346 set named ``dest``. Returns the number of keys in the new set. 

3347 

3348 For more information see https://redis.io/commands/sinterstore 

3349 """ 

3350 args = list_or_args(keys, args) 

3351 return self.execute_command("SINTERSTORE", dest, *args) 

3352 

3353 def sismember(self, name: str, value: str) -> Union[Awaitable[bool], bool]: 

3354 """ 

3355 Return a boolean indicating if ``value`` is a member of set ``name`` 

3356 

3357 For more information see https://redis.io/commands/sismember 

3358 """ 

3359 return self.execute_command("SISMEMBER", name, value) 

3360 

3361 def smembers(self, name: str) -> Union[Awaitable[Set], Set]: 

3362 """ 

3363 Return all members of the set ``name`` 

3364 

3365 For more information see https://redis.io/commands/smembers 

3366 """ 

3367 return self.execute_command("SMEMBERS", name) 

3368 

3369 def smismember( 

3370 self, name: str, values: List, *args: List 

3371 ) -> Union[ 

3372 Awaitable[List[Union[Literal[0], Literal[1]]]], 

3373 List[Union[Literal[0], Literal[1]]], 

3374 ]: 

3375 """ 

3376 Return whether each value in ``values`` is a member of the set ``name`` 

3377 as a list of ``int`` in the order of ``values``: 

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/smismember 

3382 """ 

3383 args = list_or_args(values, args) 

3384 return self.execute_command("SMISMEMBER", name, *args) 

3385 

3386 def smove(self, src: str, dst: str, value: str) -> Union[Awaitable[bool], bool]: 

3387 """ 

3388 Move ``value`` from set ``src`` to set ``dst`` atomically 

3389 

3390 For more information see https://redis.io/commands/smove 

3391 """ 

3392 return self.execute_command("SMOVE", src, dst, value) 

3393 

3394 def spop(self, name: str, count: Optional[int] = None) -> Union[str, List, None]: 

3395 """ 

3396 Remove and return a random member of set ``name`` 

3397 

3398 For more information see https://redis.io/commands/spop 

3399 """ 

3400 args = (count is not None) and [count] or [] 

3401 return self.execute_command("SPOP", name, *args) 

3402 

3403 def srandmember( 

3404 self, name: str, number: Optional[int] = None 

3405 ) -> Union[str, List, None]: 

3406 """ 

3407 If ``number`` is None, returns a random member of set ``name``. 

3408 

3409 If ``number`` is supplied, returns a list of ``number`` random 

3410 members of set ``name``. Note this is only available when running 

3411 Redis 2.6+. 

3412 

3413 For more information see https://redis.io/commands/srandmember 

3414 """ 

3415 args = (number is not None) and [number] or [] 

3416 return self.execute_command("SRANDMEMBER", name, *args) 

3417 

3418 def srem(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

3419 """ 

3420 Remove ``values`` from set ``name`` 

3421 

3422 For more information see https://redis.io/commands/srem 

3423 """ 

3424 return self.execute_command("SREM", name, *values) 

3425 

3426 def sunion(self, keys: List, *args: List) -> Union[Awaitable[List], List]: 

3427 """ 

3428 Return the union of sets specified by ``keys`` 

3429 

3430 For more information see https://redis.io/commands/sunion 

3431 """ 

3432 args = list_or_args(keys, args) 

3433 return self.execute_command("SUNION", *args) 

3434 

3435 def sunionstore( 

3436 self, dest: str, keys: List, *args: List 

3437 ) -> Union[Awaitable[int], int]: 

3438 """ 

3439 Store the union of sets specified by ``keys`` into a new 

3440 set named ``dest``. Returns the number of keys in the new set. 

3441 

3442 For more information see https://redis.io/commands/sunionstore 

3443 """ 

3444 args = list_or_args(keys, args) 

3445 return self.execute_command("SUNIONSTORE", dest, *args) 

3446 

3447 

3448AsyncSetCommands = SetCommands 

3449 

3450 

3451class StreamCommands(CommandsProtocol): 

3452 """ 

3453 Redis commands for Stream data type. 

3454 see: https://redis.io/topics/streams-intro 

3455 """ 

3456 

3457 def xack(self, name: KeyT, groupname: GroupT, *ids: StreamIdT) -> ResponseT: 

3458 """ 

3459 Acknowledges the successful processing of one or more messages. 

3460 name: name of the stream. 

3461 groupname: name of the consumer group. 

3462 *ids: message ids to acknowledge. 

3463 

3464 For more information see https://redis.io/commands/xack 

3465 """ 

3466 return self.execute_command("XACK", name, groupname, *ids) 

3467 

3468 def xadd( 

3469 self, 

3470 name: KeyT, 

3471 fields: Dict[FieldT, EncodableT], 

3472 id: StreamIdT = "*", 

3473 maxlen: Union[int, None] = None, 

3474 approximate: bool = True, 

3475 nomkstream: bool = False, 

3476 minid: Union[StreamIdT, None] = None, 

3477 limit: Union[int, None] = None, 

3478 ) -> ResponseT: 

3479 """ 

3480 Add to a stream. 

3481 name: name of the stream 

3482 fields: dict of field/value pairs to insert into the stream 

3483 id: Location to insert this record. By default it is appended. 

3484 maxlen: truncate old stream members beyond this size. 

3485 Can't be specified with minid. 

3486 approximate: actual stream length may be slightly more than maxlen 

3487 nomkstream: When set to true, do not make a stream 

3488 minid: the minimum id in the stream to query. 

3489 Can't be specified with maxlen. 

3490 limit: specifies the maximum number of entries to retrieve 

3491 

3492 For more information see https://redis.io/commands/xadd 

3493 """ 

3494 pieces: list[EncodableT] = [] 

3495 if maxlen is not None and minid is not None: 

3496 raise DataError("Only one of ```maxlen``` or ```minid``` may be specified") 

3497 

3498 if maxlen is not None: 

3499 if not isinstance(maxlen, int) or maxlen < 0: 

3500 raise DataError("XADD maxlen must be non-negative integer") 

3501 pieces.append(b"MAXLEN") 

3502 if approximate: 

3503 pieces.append(b"~") 

3504 pieces.append(str(maxlen)) 

3505 if minid is not None: 

3506 pieces.append(b"MINID") 

3507 if approximate: 

3508 pieces.append(b"~") 

3509 pieces.append(minid) 

3510 if limit is not None: 

3511 pieces.extend([b"LIMIT", limit]) 

3512 if nomkstream: 

3513 pieces.append(b"NOMKSTREAM") 

3514 pieces.append(id) 

3515 if not isinstance(fields, dict) or len(fields) == 0: 

3516 raise DataError("XADD fields must be a non-empty dict") 

3517 for pair in fields.items(): 

3518 pieces.extend(pair) 

3519 return self.execute_command("XADD", name, *pieces) 

3520 

3521 def xautoclaim( 

3522 self, 

3523 name: KeyT, 

3524 groupname: GroupT, 

3525 consumername: ConsumerT, 

3526 min_idle_time: int, 

3527 start_id: StreamIdT = "0-0", 

3528 count: Union[int, None] = None, 

3529 justid: bool = False, 

3530 ) -> ResponseT: 

3531 """ 

3532 Transfers ownership of pending stream entries that match the specified 

3533 criteria. Conceptually, equivalent to calling XPENDING and then XCLAIM, 

3534 but provides a more straightforward way to deal with message delivery 

3535 failures via SCAN-like semantics. 

3536 name: name of the stream. 

3537 groupname: name of the consumer group. 

3538 consumername: name of a consumer that claims the message. 

3539 min_idle_time: filter messages that were idle less than this amount of 

3540 milliseconds. 

3541 start_id: filter messages with equal or greater ID. 

3542 count: optional integer, upper limit of the number of entries that the 

3543 command attempts to claim. Set to 100 by default. 

3544 justid: optional boolean, false by default. Return just an array of IDs 

3545 of messages successfully claimed, without returning the actual message 

3546 

3547 For more information see https://redis.io/commands/xautoclaim 

3548 """ 

3549 try: 

3550 if int(min_idle_time) < 0: 

3551 raise DataError( 

3552 "XAUTOCLAIM min_idle_time must be a nonnegative integer" 

3553 ) 

3554 except TypeError: 

3555 pass 

3556 

3557 kwargs = {} 

3558 pieces = [name, groupname, consumername, min_idle_time, start_id] 

3559 

3560 try: 

3561 if int(count) < 0: 

3562 raise DataError("XPENDING count must be a integer >= 0") 

3563 pieces.extend([b"COUNT", count]) 

3564 except TypeError: 

3565 pass 

3566 if justid: 

3567 pieces.append(b"JUSTID") 

3568 kwargs["parse_justid"] = True 

3569 

3570 return self.execute_command("XAUTOCLAIM", *pieces, **kwargs) 

3571 

3572 def xclaim( 

3573 self, 

3574 name: KeyT, 

3575 groupname: GroupT, 

3576 consumername: ConsumerT, 

3577 min_idle_time: int, 

3578 message_ids: Union[List[StreamIdT], Tuple[StreamIdT]], 

3579 idle: Union[int, None] = None, 

3580 time: Union[int, None] = None, 

3581 retrycount: Union[int, None] = None, 

3582 force: bool = False, 

3583 justid: bool = False, 

3584 ) -> ResponseT: 

3585 """ 

3586 Changes the ownership of a pending message. 

3587 name: name of the stream. 

3588 groupname: name of the consumer group. 

3589 consumername: name of a consumer that claims the message. 

3590 min_idle_time: filter messages that were idle less than this amount of 

3591 milliseconds 

3592 message_ids: non-empty list or tuple of message IDs to claim 

3593 idle: optional. Set the idle time (last time it was delivered) of the 

3594 message in ms 

3595 time: optional integer. This is the same as idle but instead of a 

3596 relative amount of milliseconds, it sets the idle time to a specific 

3597 Unix time (in milliseconds). 

3598 retrycount: optional integer. set the retry counter to the specified 

3599 value. This counter is incremented every time a message is delivered 

3600 again. 

3601 force: optional boolean, false by default. Creates the pending message 

3602 entry in the PEL even if certain specified IDs are not already in the 

3603 PEL assigned to a different client. 

3604 justid: optional boolean, false by default. Return just an array of IDs 

3605 of messages successfully claimed, without returning the actual message 

3606 

3607 For more information see https://redis.io/commands/xclaim 

3608 """ 

3609 if not isinstance(min_idle_time, int) or min_idle_time < 0: 

3610 raise DataError("XCLAIM min_idle_time must be a non negative integer") 

3611 if not isinstance(message_ids, (list, tuple)) or not message_ids: 

3612 raise DataError( 

3613 "XCLAIM message_ids must be a non empty list or " 

3614 "tuple of message IDs to claim" 

3615 ) 

3616 

3617 kwargs = {} 

3618 pieces: list[EncodableT] = [name, groupname, consumername, str(min_idle_time)] 

3619 pieces.extend(list(message_ids)) 

3620 

3621 if idle is not None: 

3622 if not isinstance(idle, int): 

3623 raise DataError("XCLAIM idle must be an integer") 

3624 pieces.extend((b"IDLE", str(idle))) 

3625 if time is not None: 

3626 if not isinstance(time, int): 

3627 raise DataError("XCLAIM time must be an integer") 

3628 pieces.extend((b"TIME", str(time))) 

3629 if retrycount is not None: 

3630 if not isinstance(retrycount, int): 

3631 raise DataError("XCLAIM retrycount must be an integer") 

3632 pieces.extend((b"RETRYCOUNT", str(retrycount))) 

3633 

3634 if force: 

3635 if not isinstance(force, bool): 

3636 raise DataError("XCLAIM force must be a boolean") 

3637 pieces.append(b"FORCE") 

3638 if justid: 

3639 if not isinstance(justid, bool): 

3640 raise DataError("XCLAIM justid must be a boolean") 

3641 pieces.append(b"JUSTID") 

3642 kwargs["parse_justid"] = True 

3643 return self.execute_command("XCLAIM", *pieces, **kwargs) 

3644 

3645 def xdel(self, name: KeyT, *ids: StreamIdT) -> ResponseT: 

3646 """ 

3647 Deletes one or more messages from a stream. 

3648 name: name of the stream. 

3649 *ids: message ids to delete. 

3650 

3651 For more information see https://redis.io/commands/xdel 

3652 """ 

3653 return self.execute_command("XDEL", name, *ids) 

3654 

3655 def xgroup_create( 

3656 self, 

3657 name: KeyT, 

3658 groupname: GroupT, 

3659 id: StreamIdT = "$", 

3660 mkstream: bool = False, 

3661 entries_read: Optional[int] = None, 

3662 ) -> ResponseT: 

3663 """ 

3664 Create a new consumer group associated with a stream. 

3665 name: name of the stream. 

3666 groupname: name of the consumer group. 

3667 id: ID of the last item in the stream to consider already delivered. 

3668 

3669 For more information see https://redis.io/commands/xgroup-create 

3670 """ 

3671 pieces: list[EncodableT] = ["XGROUP CREATE", name, groupname, id] 

3672 if mkstream: 

3673 pieces.append(b"MKSTREAM") 

3674 if entries_read is not None: 

3675 pieces.extend(["ENTRIESREAD", entries_read]) 

3676 

3677 return self.execute_command(*pieces) 

3678 

3679 def xgroup_delconsumer( 

3680 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

3681 ) -> ResponseT: 

3682 """ 

3683 Remove a specific consumer from a consumer group. 

3684 Returns the number of pending messages that the consumer had before it 

3685 was deleted. 

3686 name: name of the stream. 

3687 groupname: name of the consumer group. 

3688 consumername: name of consumer to delete 

3689 

3690 For more information see https://redis.io/commands/xgroup-delconsumer 

3691 """ 

3692 return self.execute_command("XGROUP DELCONSUMER", name, groupname, consumername) 

3693 

3694 def xgroup_destroy(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3695 """ 

3696 Destroy a consumer group. 

3697 name: name of the stream. 

3698 groupname: name of the consumer group. 

3699 

3700 For more information see https://redis.io/commands/xgroup-destroy 

3701 """ 

3702 return self.execute_command("XGROUP DESTROY", name, groupname) 

3703 

3704 def xgroup_createconsumer( 

3705 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

3706 ) -> ResponseT: 

3707 """ 

3708 Consumers in a consumer group are auto-created every time a new 

3709 consumer name is mentioned by some command. 

3710 They can be explicitly created by using this command. 

3711 name: name of the stream. 

3712 groupname: name of the consumer group. 

3713 consumername: name of consumer to create. 

3714 

3715 See: https://redis.io/commands/xgroup-createconsumer 

3716 """ 

3717 return self.execute_command( 

3718 "XGROUP CREATECONSUMER", name, groupname, consumername 

3719 ) 

3720 

3721 def xgroup_setid( 

3722 self, 

3723 name: KeyT, 

3724 groupname: GroupT, 

3725 id: StreamIdT, 

3726 entries_read: Optional[int] = None, 

3727 ) -> ResponseT: 

3728 """ 

3729 Set the consumer group last delivered ID to something else. 

3730 name: name of the stream. 

3731 groupname: name of the consumer group. 

3732 id: ID of the last item in the stream to consider already delivered. 

3733 

3734 For more information see https://redis.io/commands/xgroup-setid 

3735 """ 

3736 pieces = [name, groupname, id] 

3737 if entries_read is not None: 

3738 pieces.extend(["ENTRIESREAD", entries_read]) 

3739 return self.execute_command("XGROUP SETID", *pieces) 

3740 

3741 def xinfo_consumers(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3742 """ 

3743 Returns general information about the consumers in the group. 

3744 name: name of the stream. 

3745 groupname: name of the consumer group. 

3746 

3747 For more information see https://redis.io/commands/xinfo-consumers 

3748 """ 

3749 return self.execute_command("XINFO CONSUMERS", name, groupname) 

3750 

3751 def xinfo_groups(self, name: KeyT) -> ResponseT: 

3752 """ 

3753 Returns general information about the consumer groups of the stream. 

3754 name: name of the stream. 

3755 

3756 For more information see https://redis.io/commands/xinfo-groups 

3757 """ 

3758 return self.execute_command("XINFO GROUPS", name) 

3759 

3760 def xinfo_stream(self, name: KeyT, full: bool = False) -> ResponseT: 

3761 """ 

3762 Returns general information about the stream. 

3763 name: name of the stream. 

3764 full: optional boolean, false by default. Return full summary 

3765 

3766 For more information see https://redis.io/commands/xinfo-stream 

3767 """ 

3768 pieces = [name] 

3769 options = {} 

3770 if full: 

3771 pieces.append(b"FULL") 

3772 options = {"full": full} 

3773 return self.execute_command("XINFO STREAM", *pieces, **options) 

3774 

3775 def xlen(self, name: KeyT) -> ResponseT: 

3776 """ 

3777 Returns the number of elements in a given stream. 

3778 

3779 For more information see https://redis.io/commands/xlen 

3780 """ 

3781 return self.execute_command("XLEN", name) 

3782 

3783 def xpending(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3784 """ 

3785 Returns information about pending messages of a group. 

3786 name: name of the stream. 

3787 groupname: name of the consumer group. 

3788 

3789 For more information see https://redis.io/commands/xpending 

3790 """ 

3791 return self.execute_command("XPENDING", name, groupname) 

3792 

3793 def xpending_range( 

3794 self, 

3795 name: KeyT, 

3796 groupname: GroupT, 

3797 min: StreamIdT, 

3798 max: StreamIdT, 

3799 count: int, 

3800 consumername: Union[ConsumerT, None] = None, 

3801 idle: Union[int, None] = None, 

3802 ) -> ResponseT: 

3803 """ 

3804 Returns information about pending messages, in a range. 

3805 

3806 name: name of the stream. 

3807 groupname: name of the consumer group. 

3808 idle: available from version 6.2. filter entries by their 

3809 idle-time, given in milliseconds (optional). 

3810 min: minimum stream ID. 

3811 max: maximum stream ID. 

3812 count: number of messages to return 

3813 consumername: name of a consumer to filter by (optional). 

3814 """ 

3815 if {min, max, count} == {None}: 

3816 if idle is not None or consumername is not None: 

3817 raise DataError( 

3818 "if XPENDING is provided with idle time" 

3819 " or consumername, it must be provided" 

3820 " with min, max and count parameters" 

3821 ) 

3822 return self.xpending(name, groupname) 

3823 

3824 pieces = [name, groupname] 

3825 if min is None or max is None or count is None: 

3826 raise DataError( 

3827 "XPENDING must be provided with min, max " 

3828 "and count parameters, or none of them." 

3829 ) 

3830 # idle 

3831 try: 

3832 if int(idle) < 0: 

3833 raise DataError("XPENDING idle must be a integer >= 0") 

3834 pieces.extend(["IDLE", idle]) 

3835 except TypeError: 

3836 pass 

3837 # count 

3838 try: 

3839 if int(count) < 0: 

3840 raise DataError("XPENDING count must be a integer >= 0") 

3841 pieces.extend([min, max, count]) 

3842 except TypeError: 

3843 pass 

3844 # consumername 

3845 if consumername: 

3846 pieces.append(consumername) 

3847 

3848 return self.execute_command("XPENDING", *pieces, parse_detail=True) 

3849 

3850 def xrange( 

3851 self, 

3852 name: KeyT, 

3853 min: StreamIdT = "-", 

3854 max: StreamIdT = "+", 

3855 count: Union[int, None] = None, 

3856 ) -> ResponseT: 

3857 """ 

3858 Read stream values within an interval. 

3859 name: name of the stream. 

3860 start: first stream ID. defaults to '-', 

3861 meaning the earliest available. 

3862 finish: last stream ID. defaults to '+', 

3863 meaning the latest available. 

3864 count: if set, only return this many items, beginning with the 

3865 earliest available. 

3866 

3867 For more information see https://redis.io/commands/xrange 

3868 """ 

3869 pieces = [min, max] 

3870 if count is not None: 

3871 if not isinstance(count, int) or count < 1: 

3872 raise DataError("XRANGE count must be a positive integer") 

3873 pieces.append(b"COUNT") 

3874 pieces.append(str(count)) 

3875 

3876 return self.execute_command("XRANGE", name, *pieces) 

3877 

3878 def xread( 

3879 self, 

3880 streams: Dict[KeyT, StreamIdT], 

3881 count: Union[int, None] = None, 

3882 block: Union[int, None] = None, 

3883 ) -> ResponseT: 

3884 """ 

3885 Block and monitor multiple streams for new data. 

3886 streams: a dict of stream names to stream IDs, where 

3887 IDs indicate the last ID already seen. 

3888 count: if set, only return this many items, beginning with the 

3889 earliest available. 

3890 block: number of milliseconds to wait, if nothing already present. 

3891 

3892 For more information see https://redis.io/commands/xread 

3893 """ 

3894 pieces = [] 

3895 if block is not None: 

3896 if not isinstance(block, int) or block < 0: 

3897 raise DataError("XREAD block must be a non-negative integer") 

3898 pieces.append(b"BLOCK") 

3899 pieces.append(str(block)) 

3900 if count is not None: 

3901 if not isinstance(count, int) or count < 1: 

3902 raise DataError("XREAD count must be a positive integer") 

3903 pieces.append(b"COUNT") 

3904 pieces.append(str(count)) 

3905 if not isinstance(streams, dict) or len(streams) == 0: 

3906 raise DataError("XREAD streams must be a non empty dict") 

3907 pieces.append(b"STREAMS") 

3908 keys, values = zip(*streams.items()) 

3909 pieces.extend(keys) 

3910 pieces.extend(values) 

3911 return self.execute_command("XREAD", *pieces) 

3912 

3913 def xreadgroup( 

3914 self, 

3915 groupname: str, 

3916 consumername: str, 

3917 streams: Dict[KeyT, StreamIdT], 

3918 count: Union[int, None] = None, 

3919 block: Union[int, None] = None, 

3920 noack: bool = False, 

3921 ) -> ResponseT: 

3922 """ 

3923 Read from a stream via a consumer group. 

3924 groupname: name of the consumer group. 

3925 consumername: name of the requesting consumer. 

3926 streams: a dict of stream names to stream IDs, where 

3927 IDs indicate the last ID already seen. 

3928 count: if set, only return this many items, beginning with the 

3929 earliest available. 

3930 block: number of milliseconds to wait, if nothing already present. 

3931 noack: do not add messages to the PEL 

3932 

3933 For more information see https://redis.io/commands/xreadgroup 

3934 """ 

3935 pieces: list[EncodableT] = [b"GROUP", groupname, consumername] 

3936 if count is not None: 

3937 if not isinstance(count, int) or count < 1: 

3938 raise DataError("XREADGROUP count must be a positive integer") 

3939 pieces.append(b"COUNT") 

3940 pieces.append(str(count)) 

3941 if block is not None: 

3942 if not isinstance(block, int) or block < 0: 

3943 raise DataError("XREADGROUP block must be a non-negative integer") 

3944 pieces.append(b"BLOCK") 

3945 pieces.append(str(block)) 

3946 if noack: 

3947 pieces.append(b"NOACK") 

3948 if not isinstance(streams, dict) or len(streams) == 0: 

3949 raise DataError("XREADGROUP streams must be a non empty dict") 

3950 pieces.append(b"STREAMS") 

3951 pieces.extend(streams.keys()) 

3952 pieces.extend(streams.values()) 

3953 return self.execute_command("XREADGROUP", *pieces) 

3954 

3955 def xrevrange( 

3956 self, 

3957 name: KeyT, 

3958 max: StreamIdT = "+", 

3959 min: StreamIdT = "-", 

3960 count: Union[int, None] = None, 

3961 ) -> ResponseT: 

3962 """ 

3963 Read stream values within an interval, in reverse order. 

3964 name: name of the stream 

3965 start: first stream ID. defaults to '+', 

3966 meaning the latest available. 

3967 finish: last stream ID. defaults to '-', 

3968 meaning the earliest available. 

3969 count: if set, only return this many items, beginning with the 

3970 latest available. 

3971 

3972 For more information see https://redis.io/commands/xrevrange 

3973 """ 

3974 pieces: list[EncodableT] = [max, min] 

3975 if count is not None: 

3976 if not isinstance(count, int) or count < 1: 

3977 raise DataError("XREVRANGE count must be a positive integer") 

3978 pieces.append(b"COUNT") 

3979 pieces.append(str(count)) 

3980 

3981 return self.execute_command("XREVRANGE", name, *pieces) 

3982 

3983 def xtrim( 

3984 self, 

3985 name: KeyT, 

3986 maxlen: Union[int, None] = None, 

3987 approximate: bool = True, 

3988 minid: Union[StreamIdT, None] = None, 

3989 limit: Union[int, None] = None, 

3990 ) -> ResponseT: 

3991 """ 

3992 Trims old messages from a stream. 

3993 name: name of the stream. 

3994 maxlen: truncate old stream messages beyond this size 

3995 Can't be specified with minid. 

3996 approximate: actual stream length may be slightly more than maxlen 

3997 minid: the minimum id in the stream to query 

3998 Can't be specified with maxlen. 

3999 limit: specifies the maximum number of entries to retrieve 

4000 

4001 For more information see https://redis.io/commands/xtrim 

4002 """ 

4003 pieces: list[EncodableT] = [] 

4004 if maxlen is not None and minid is not None: 

4005 raise DataError("Only one of ``maxlen`` or ``minid`` may be specified") 

4006 

4007 if maxlen is None and minid is None: 

4008 raise DataError("One of ``maxlen`` or ``minid`` must be specified") 

4009 

4010 if maxlen is not None: 

4011 pieces.append(b"MAXLEN") 

4012 if minid is not None: 

4013 pieces.append(b"MINID") 

4014 if approximate: 

4015 pieces.append(b"~") 

4016 if maxlen is not None: 

4017 pieces.append(maxlen) 

4018 if minid is not None: 

4019 pieces.append(minid) 

4020 if limit is not None: 

4021 pieces.append(b"LIMIT") 

4022 pieces.append(limit) 

4023 

4024 return self.execute_command("XTRIM", name, *pieces) 

4025 

4026 

4027AsyncStreamCommands = StreamCommands 

4028 

4029 

4030class SortedSetCommands(CommandsProtocol): 

4031 """ 

4032 Redis commands for Sorted Sets data type. 

4033 see: https://redis.io/topics/data-types-intro#redis-sorted-sets 

4034 """ 

4035 

4036 def zadd( 

4037 self, 

4038 name: KeyT, 

4039 mapping: Mapping[AnyKeyT, EncodableT], 

4040 nx: bool = False, 

4041 xx: bool = False, 

4042 ch: bool = False, 

4043 incr: bool = False, 

4044 gt: bool = False, 

4045 lt: bool = False, 

4046 ) -> ResponseT: 

4047 """ 

4048 Set any number of element-name, score pairs to the key ``name``. Pairs 

4049 are specified as a dict of element-names keys to score values. 

4050 

4051 ``nx`` forces ZADD to only create new elements and not to update 

4052 scores for elements that already exist. 

4053 

4054 ``xx`` forces ZADD to only update scores of elements that already 

4055 exist. New elements will not be added. 

4056 

4057 ``ch`` modifies the return value to be the numbers of elements changed. 

4058 Changed elements include new elements that were added and elements 

4059 whose scores changed. 

4060 

4061 ``incr`` modifies ZADD to behave like ZINCRBY. In this mode only a 

4062 single element/score pair can be specified and the score is the amount 

4063 the existing score will be incremented by. When using this mode the 

4064 return value of ZADD will be the new score of the element. 

4065 

4066 ``LT`` Only update existing elements if the new score is less than 

4067 the current score. This flag doesn't prevent adding new elements. 

4068 

4069 ``GT`` Only update existing elements if the new score is greater than 

4070 the current score. This flag doesn't prevent adding new elements. 

4071 

4072 The return value of ZADD varies based on the mode specified. With no 

4073 options, ZADD returns the number of new elements added to the sorted 

4074 set. 

4075 

4076 ``NX``, ``LT``, and ``GT`` are mutually exclusive options. 

4077 

4078 See: https://redis.io/commands/ZADD 

4079 """ 

4080 if not mapping: 

4081 raise DataError("ZADD requires at least one element/score pair") 

4082 if nx and xx: 

4083 raise DataError("ZADD allows either 'nx' or 'xx', not both") 

4084 if gt and lt: 

4085 raise DataError("ZADD allows either 'gt' or 'lt', not both") 

4086 if incr and len(mapping) != 1: 

4087 raise DataError( 

4088 "ZADD option 'incr' only works when passing a " 

4089 "single element/score pair" 

4090 ) 

4091 if nx and (gt or lt): 

4092 raise DataError("Only one of 'nx', 'lt', or 'gr' may be defined.") 

4093 

4094 pieces: list[EncodableT] = [] 

4095 options = {} 

4096 if nx: 

4097 pieces.append(b"NX") 

4098 if xx: 

4099 pieces.append(b"XX") 

4100 if ch: 

4101 pieces.append(b"CH") 

4102 if incr: 

4103 pieces.append(b"INCR") 

4104 options["as_score"] = True 

4105 if gt: 

4106 pieces.append(b"GT") 

4107 if lt: 

4108 pieces.append(b"LT") 

4109 for pair in mapping.items(): 

4110 pieces.append(pair[1]) 

4111 pieces.append(pair[0]) 

4112 return self.execute_command("ZADD", name, *pieces, **options) 

4113 

4114 def zcard(self, name: KeyT) -> ResponseT: 

4115 """ 

4116 Return the number of elements in the sorted set ``name`` 

4117 

4118 For more information see https://redis.io/commands/zcard 

4119 """ 

4120 return self.execute_command("ZCARD", name) 

4121 

4122 def zcount(self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT) -> ResponseT: 

4123 """ 

4124 Returns the number of elements in the sorted set at key ``name`` with 

4125 a score between ``min`` and ``max``. 

4126 

4127 For more information see https://redis.io/commands/zcount 

4128 """ 

4129 return self.execute_command("ZCOUNT", name, min, max) 

4130 

4131 def zdiff(self, keys: KeysT, withscores: bool = False) -> ResponseT: 

4132 """ 

4133 Returns the difference between the first and all successive input 

4134 sorted sets provided in ``keys``. 

4135 

4136 For more information see https://redis.io/commands/zdiff 

4137 """ 

4138 pieces = [len(keys), *keys] 

4139 if withscores: 

4140 pieces.append("WITHSCORES") 

4141 return self.execute_command("ZDIFF", *pieces) 

4142 

4143 def zdiffstore(self, dest: KeyT, keys: KeysT) -> ResponseT: 

4144 """ 

4145 Computes the difference between the first and all successive input 

4146 sorted sets provided in ``keys`` and stores the result in ``dest``. 

4147 

4148 For more information see https://redis.io/commands/zdiffstore 

4149 """ 

4150 pieces = [len(keys), *keys] 

4151 return self.execute_command("ZDIFFSTORE", dest, *pieces) 

4152 

4153 def zincrby(self, name: KeyT, amount: float, value: EncodableT) -> ResponseT: 

4154 """ 

4155 Increment the score of ``value`` in sorted set ``name`` by ``amount`` 

4156 

4157 For more information see https://redis.io/commands/zincrby 

4158 """ 

4159 return self.execute_command("ZINCRBY", name, amount, value) 

4160 

4161 def zinter( 

4162 self, keys: KeysT, aggregate: Union[str, None] = None, withscores: bool = False 

4163 ) -> ResponseT: 

4164 """ 

4165 Return the intersect of multiple sorted sets specified by ``keys``. 

4166 With the ``aggregate`` option, it is possible to specify how the 

4167 results of the union are aggregated. This option defaults to SUM, 

4168 where the score of an element is summed across the inputs where it 

4169 exists. When this option is set to either MIN or MAX, the resulting 

4170 set will contain the minimum or maximum score of an element across 

4171 the inputs where it exists. 

4172 

4173 For more information see https://redis.io/commands/zinter 

4174 """ 

4175 return self._zaggregate("ZINTER", None, keys, aggregate, withscores=withscores) 

4176 

4177 def zinterstore( 

4178 self, 

4179 dest: KeyT, 

4180 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4181 aggregate: Union[str, None] = None, 

4182 ) -> ResponseT: 

4183 """ 

4184 Intersect multiple sorted sets specified by ``keys`` into a new 

4185 sorted set, ``dest``. Scores in the destination will be aggregated 

4186 based on the ``aggregate``. This option defaults to SUM, where the 

4187 score of an element is summed across the inputs where it exists. 

4188 When this option is set to either MIN or MAX, the resulting set will 

4189 contain the minimum or maximum score of an element across the inputs 

4190 where it exists. 

4191 

4192 For more information see https://redis.io/commands/zinterstore 

4193 """ 

4194 return self._zaggregate("ZINTERSTORE", dest, keys, aggregate) 

4195 

4196 def zintercard( 

4197 self, numkeys: int, keys: List[str], limit: int = 0 

4198 ) -> Union[Awaitable[int], int]: 

4199 """ 

4200 Return the cardinality of the intersect of multiple sorted sets 

4201 specified by ``keys`. 

4202 When LIMIT provided (defaults to 0 and means unlimited), if the intersection 

4203 cardinality reaches limit partway through the computation, the algorithm will 

4204 exit and yield limit as the cardinality 

4205 

4206 For more information see https://redis.io/commands/zintercard 

4207 """ 

4208 args = [numkeys, *keys, "LIMIT", limit] 

4209 return self.execute_command("ZINTERCARD", *args) 

4210 

4211 def zlexcount(self, name, min, max): 

4212 """ 

4213 Return the number of items in the sorted set ``name`` between the 

4214 lexicographical range ``min`` and ``max``. 

4215 

4216 For more information see https://redis.io/commands/zlexcount 

4217 """ 

4218 return self.execute_command("ZLEXCOUNT", name, min, max) 

4219 

4220 def zpopmax(self, name: KeyT, count: Union[int, None] = None) -> ResponseT: 

4221 """ 

4222 Remove and return up to ``count`` members with the highest scores 

4223 from the sorted set ``name``. 

4224 

4225 For more information see https://redis.io/commands/zpopmax 

4226 """ 

4227 args = (count is not None) and [count] or [] 

4228 options = {"withscores": True} 

4229 return self.execute_command("ZPOPMAX", name, *args, **options) 

4230 

4231 def zpopmin(self, name: KeyT, count: Union[int, None] = None) -> ResponseT: 

4232 """ 

4233 Remove and return up to ``count`` members with the lowest scores 

4234 from the sorted set ``name``. 

4235 

4236 For more information see https://redis.io/commands/zpopmin 

4237 """ 

4238 args = (count is not None) and [count] or [] 

4239 options = {"withscores": True} 

4240 return self.execute_command("ZPOPMIN", name, *args, **options) 

4241 

4242 def zrandmember( 

4243 self, key: KeyT, count: int = None, withscores: bool = False 

4244 ) -> ResponseT: 

4245 """ 

4246 Return a random element from the sorted set value stored at key. 

4247 

4248 ``count`` if the argument is positive, return an array of distinct 

4249 fields. If called with a negative count, the behavior changes and 

4250 the command is allowed to return the same field multiple times. 

4251 In this case, the number of returned fields is the absolute value 

4252 of the specified count. 

4253 

4254 ``withscores`` The optional WITHSCORES modifier changes the reply so it 

4255 includes the respective scores of the randomly selected elements from 

4256 the sorted set. 

4257 

4258 For more information see https://redis.io/commands/zrandmember 

4259 """ 

4260 params = [] 

4261 if count is not None: 

4262 params.append(count) 

4263 if withscores: 

4264 params.append("WITHSCORES") 

4265 

4266 return self.execute_command("ZRANDMEMBER", key, *params) 

4267 

4268 def bzpopmax(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4269 """ 

4270 ZPOPMAX a value off of the first non-empty sorted set 

4271 named in the ``keys`` list. 

4272 

4273 If none of the sorted sets in ``keys`` has a value to ZPOPMAX, 

4274 then block for ``timeout`` seconds, or until a member gets added 

4275 to one of the sorted sets. 

4276 

4277 If timeout is 0, then block indefinitely. 

4278 

4279 For more information see https://redis.io/commands/bzpopmax 

4280 """ 

4281 if timeout is None: 

4282 timeout = 0 

4283 keys = list_or_args(keys, None) 

4284 keys.append(timeout) 

4285 return self.execute_command("BZPOPMAX", *keys) 

4286 

4287 def bzpopmin(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4288 """ 

4289 ZPOPMIN a value off of the first non-empty sorted set 

4290 named in the ``keys`` list. 

4291 

4292 If none of the sorted sets in ``keys`` has a value to ZPOPMIN, 

4293 then block for ``timeout`` seconds, or until a member gets added 

4294 to one of the sorted sets. 

4295 

4296 If timeout is 0, then block indefinitely. 

4297 

4298 For more information see https://redis.io/commands/bzpopmin 

4299 """ 

4300 if timeout is None: 

4301 timeout = 0 

4302 keys: list[EncodableT] = list_or_args(keys, None) 

4303 keys.append(timeout) 

4304 return self.execute_command("BZPOPMIN", *keys) 

4305 

4306 def zmpop( 

4307 self, 

4308 num_keys: int, 

4309 keys: List[str], 

4310 min: Optional[bool] = False, 

4311 max: Optional[bool] = False, 

4312 count: Optional[int] = 1, 

4313 ) -> Union[Awaitable[list], list]: 

4314 """ 

4315 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4316 named in the ``keys`` list. 

4317 For more information see https://redis.io/commands/zmpop 

4318 """ 

4319 args = [num_keys] + keys 

4320 if (min and max) or (not min and not max): 

4321 raise DataError 

4322 elif min: 

4323 args.append("MIN") 

4324 else: 

4325 args.append("MAX") 

4326 if count != 1: 

4327 args.extend(["COUNT", count]) 

4328 

4329 return self.execute_command("ZMPOP", *args) 

4330 

4331 def bzmpop( 

4332 self, 

4333 timeout: float, 

4334 numkeys: int, 

4335 keys: List[str], 

4336 min: Optional[bool] = False, 

4337 max: Optional[bool] = False, 

4338 count: Optional[int] = 1, 

4339 ) -> Optional[list]: 

4340 """ 

4341 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4342 named in the ``keys`` list. 

4343 

4344 If none of the sorted sets in ``keys`` has a value to pop, 

4345 then block for ``timeout`` seconds, or until a member gets added 

4346 to one of the sorted sets. 

4347 

4348 If timeout is 0, then block indefinitely. 

4349 

4350 For more information see https://redis.io/commands/bzmpop 

4351 """ 

4352 args = [timeout, numkeys, *keys] 

4353 if (min and max) or (not min and not max): 

4354 raise DataError("Either min or max, but not both must be set") 

4355 elif min: 

4356 args.append("MIN") 

4357 else: 

4358 args.append("MAX") 

4359 args.extend(["COUNT", count]) 

4360 

4361 return self.execute_command("BZMPOP", *args) 

4362 

4363 def _zrange( 

4364 self, 

4365 command, 

4366 dest: Union[KeyT, None], 

4367 name: KeyT, 

4368 start: int, 

4369 end: int, 

4370 desc: bool = False, 

4371 byscore: bool = False, 

4372 bylex: bool = False, 

4373 withscores: bool = False, 

4374 score_cast_func: Union[type, Callable, None] = float, 

4375 offset: Union[int, None] = None, 

4376 num: Union[int, None] = None, 

4377 ) -> ResponseT: 

4378 if byscore and bylex: 

4379 raise DataError("``byscore`` and ``bylex`` can not be specified together.") 

4380 if (offset is not None and num is None) or (num is not None and offset is None): 

4381 raise DataError("``offset`` and ``num`` must both be specified.") 

4382 if bylex and withscores: 

4383 raise DataError( 

4384 "``withscores`` not supported in combination with ``bylex``." 

4385 ) 

4386 pieces = [command] 

4387 if dest: 

4388 pieces.append(dest) 

4389 pieces.extend([name, start, end]) 

4390 if byscore: 

4391 pieces.append("BYSCORE") 

4392 if bylex: 

4393 pieces.append("BYLEX") 

4394 if desc: 

4395 pieces.append("REV") 

4396 if offset is not None and num is not None: 

4397 pieces.extend(["LIMIT", offset, num]) 

4398 if withscores: 

4399 pieces.append("WITHSCORES") 

4400 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4401 return self.execute_command(*pieces, **options) 

4402 

4403 def zrange( 

4404 self, 

4405 name: KeyT, 

4406 start: int, 

4407 end: int, 

4408 desc: bool = False, 

4409 withscores: bool = False, 

4410 score_cast_func: Union[type, Callable] = float, 

4411 byscore: bool = False, 

4412 bylex: bool = False, 

4413 offset: int = None, 

4414 num: int = None, 

4415 ) -> ResponseT: 

4416 """ 

4417 Return a range of values from sorted set ``name`` between 

4418 ``start`` and ``end`` sorted in ascending order. 

4419 

4420 ``start`` and ``end`` can be negative, indicating the end of the range. 

4421 

4422 ``desc`` a boolean indicating whether to sort the results in reversed 

4423 order. 

4424 

4425 ``withscores`` indicates to return the scores along with the values. 

4426 The return type is a list of (value, score) pairs. 

4427 

4428 ``score_cast_func`` a callable used to cast the score return value. 

4429 

4430 ``byscore`` when set to True, returns the range of elements from the 

4431 sorted set having scores equal or between ``start`` and ``end``. 

4432 

4433 ``bylex`` when set to True, returns the range of elements from the 

4434 sorted set between the ``start`` and ``end`` lexicographical closed 

4435 range intervals. 

4436 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

4437 whether the range interval is exclusive or inclusive, respectively. 

4438 

4439 ``offset`` and ``num`` are specified, then return a slice of the range. 

4440 Can't be provided when using ``bylex``. 

4441 

4442 For more information see https://redis.io/commands/zrange 

4443 """ 

4444 # Need to support ``desc`` also when using old redis version 

4445 # because it was supported in 3.5.3 (of redis-py) 

4446 if not byscore and not bylex and (offset is None and num is None) and desc: 

4447 return self.zrevrange(name, start, end, withscores, score_cast_func) 

4448 

4449 return self._zrange( 

4450 "ZRANGE", 

4451 None, 

4452 name, 

4453 start, 

4454 end, 

4455 desc, 

4456 byscore, 

4457 bylex, 

4458 withscores, 

4459 score_cast_func, 

4460 offset, 

4461 num, 

4462 ) 

4463 

4464 def zrevrange( 

4465 self, 

4466 name: KeyT, 

4467 start: int, 

4468 end: int, 

4469 withscores: bool = False, 

4470 score_cast_func: Union[type, Callable] = float, 

4471 ) -> ResponseT: 

4472 """ 

4473 Return a range of values from sorted set ``name`` between 

4474 ``start`` and ``end`` sorted in descending order. 

4475 

4476 ``start`` and ``end`` can be negative, indicating the end of the range. 

4477 

4478 ``withscores`` indicates to return the scores along with the values 

4479 The return type is a list of (value, score) pairs 

4480 

4481 ``score_cast_func`` a callable used to cast the score return value 

4482 

4483 For more information see https://redis.io/commands/zrevrange 

4484 """ 

4485 pieces = ["ZREVRANGE", name, start, end] 

4486 if withscores: 

4487 pieces.append(b"WITHSCORES") 

4488 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4489 return self.execute_command(*pieces, **options) 

4490 

4491 def zrangestore( 

4492 self, 

4493 dest: KeyT, 

4494 name: KeyT, 

4495 start: int, 

4496 end: int, 

4497 byscore: bool = False, 

4498 bylex: bool = False, 

4499 desc: bool = False, 

4500 offset: Union[int, None] = None, 

4501 num: Union[int, None] = None, 

4502 ) -> ResponseT: 

4503 """ 

4504 Stores in ``dest`` the result of a range of values from sorted set 

4505 ``name`` between ``start`` and ``end`` sorted in ascending order. 

4506 

4507 ``start`` and ``end`` can be negative, indicating the end of the range. 

4508 

4509 ``byscore`` when set to True, returns the range of elements from the 

4510 sorted set having scores equal or between ``start`` and ``end``. 

4511 

4512 ``bylex`` when set to True, returns the range of elements from the 

4513 sorted set between the ``start`` and ``end`` lexicographical closed 

4514 range intervals. 

4515 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

4516 whether the range interval is exclusive or inclusive, respectively. 

4517 

4518 ``desc`` a boolean indicating whether to sort the results in reversed 

4519 order. 

4520 

4521 ``offset`` and ``num`` are specified, then return a slice of the range. 

4522 Can't be provided when using ``bylex``. 

4523 

4524 For more information see https://redis.io/commands/zrangestore 

4525 """ 

4526 return self._zrange( 

4527 "ZRANGESTORE", 

4528 dest, 

4529 name, 

4530 start, 

4531 end, 

4532 desc, 

4533 byscore, 

4534 bylex, 

4535 False, 

4536 None, 

4537 offset, 

4538 num, 

4539 ) 

4540 

4541 def zrangebylex( 

4542 self, 

4543 name: KeyT, 

4544 min: EncodableT, 

4545 max: EncodableT, 

4546 start: Union[int, None] = None, 

4547 num: Union[int, None] = None, 

4548 ) -> ResponseT: 

4549 """ 

4550 Return the lexicographical range of values from sorted set ``name`` 

4551 between ``min`` and ``max``. 

4552 

4553 If ``start`` and ``num`` are specified, then return a slice of the 

4554 range. 

4555 

4556 For more information see https://redis.io/commands/zrangebylex 

4557 """ 

4558 if (start is not None and num is None) or (num is not None and start is None): 

4559 raise DataError("``start`` and ``num`` must both be specified") 

4560 pieces = ["ZRANGEBYLEX", name, min, max] 

4561 if start is not None and num is not None: 

4562 pieces.extend([b"LIMIT", start, num]) 

4563 return self.execute_command(*pieces) 

4564 

4565 def zrevrangebylex( 

4566 self, 

4567 name: KeyT, 

4568 max: EncodableT, 

4569 min: EncodableT, 

4570 start: Union[int, None] = None, 

4571 num: Union[int, None] = None, 

4572 ) -> ResponseT: 

4573 """ 

4574 Return the reversed lexicographical range of values from sorted set 

4575 ``name`` between ``max`` and ``min``. 

4576 

4577 If ``start`` and ``num`` are specified, then return a slice of the 

4578 range. 

4579 

4580 For more information see https://redis.io/commands/zrevrangebylex 

4581 """ 

4582 if (start is not None and num is None) or (num is not None and start is None): 

4583 raise DataError("``start`` and ``num`` must both be specified") 

4584 pieces = ["ZREVRANGEBYLEX", name, max, min] 

4585 if start is not None and num is not None: 

4586 pieces.extend(["LIMIT", start, num]) 

4587 return self.execute_command(*pieces) 

4588 

4589 def zrangebyscore( 

4590 self, 

4591 name: KeyT, 

4592 min: ZScoreBoundT, 

4593 max: ZScoreBoundT, 

4594 start: Union[int, None] = None, 

4595 num: Union[int, None] = None, 

4596 withscores: bool = False, 

4597 score_cast_func: Union[type, Callable] = float, 

4598 ) -> ResponseT: 

4599 """ 

4600 Return a range of values from the sorted set ``name`` with scores 

4601 between ``min`` and ``max``. 

4602 

4603 If ``start`` and ``num`` are specified, then return a slice 

4604 of the range. 

4605 

4606 ``withscores`` indicates to return the scores along with the values. 

4607 The return type is a list of (value, score) pairs 

4608 

4609 `score_cast_func`` a callable used to cast the score return value 

4610 

4611 For more information see https://redis.io/commands/zrangebyscore 

4612 """ 

4613 if (start is not None and num is None) or (num is not None and start is None): 

4614 raise DataError("``start`` and ``num`` must both be specified") 

4615 pieces = ["ZRANGEBYSCORE", name, min, max] 

4616 if start is not None and num is not None: 

4617 pieces.extend(["LIMIT", start, num]) 

4618 if withscores: 

4619 pieces.append("WITHSCORES") 

4620 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4621 return self.execute_command(*pieces, **options) 

4622 

4623 def zrevrangebyscore( 

4624 self, 

4625 name: KeyT, 

4626 max: ZScoreBoundT, 

4627 min: ZScoreBoundT, 

4628 start: Union[int, None] = None, 

4629 num: Union[int, None] = None, 

4630 withscores: bool = False, 

4631 score_cast_func: Union[type, Callable] = float, 

4632 ): 

4633 """ 

4634 Return a range of values from the sorted set ``name`` with scores 

4635 between ``min`` and ``max`` in descending order. 

4636 

4637 If ``start`` and ``num`` are specified, then return a slice 

4638 of the range. 

4639 

4640 ``withscores`` indicates to return the scores along with the values. 

4641 The return type is a list of (value, score) pairs 

4642 

4643 ``score_cast_func`` a callable used to cast the score return value 

4644 

4645 For more information see https://redis.io/commands/zrevrangebyscore 

4646 """ 

4647 if (start is not None and num is None) or (num is not None and start is None): 

4648 raise DataError("``start`` and ``num`` must both be specified") 

4649 pieces = ["ZREVRANGEBYSCORE", name, max, min] 

4650 if start is not None and num is not None: 

4651 pieces.extend(["LIMIT", start, num]) 

4652 if withscores: 

4653 pieces.append("WITHSCORES") 

4654 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4655 return self.execute_command(*pieces, **options) 

4656 

4657 def zrank( 

4658 self, 

4659 name: KeyT, 

4660 value: EncodableT, 

4661 withscore: bool = False, 

4662 ) -> ResponseT: 

4663 """ 

4664 Returns a 0-based value indicating the rank of ``value`` in sorted set 

4665 ``name``. 

4666 The optional WITHSCORE argument supplements the command's 

4667 reply with the score of the element returned. 

4668 

4669 For more information see https://redis.io/commands/zrank 

4670 """ 

4671 if withscore: 

4672 return self.execute_command("ZRANK", name, value, "WITHSCORE") 

4673 return self.execute_command("ZRANK", name, value) 

4674 

4675 def zrem(self, name: KeyT, *values: FieldT) -> ResponseT: 

4676 """ 

4677 Remove member ``values`` from sorted set ``name`` 

4678 

4679 For more information see https://redis.io/commands/zrem 

4680 """ 

4681 return self.execute_command("ZREM", name, *values) 

4682 

4683 def zremrangebylex(self, name: KeyT, min: EncodableT, max: EncodableT) -> ResponseT: 

4684 """ 

4685 Remove all elements in the sorted set ``name`` between the 

4686 lexicographical range specified by ``min`` and ``max``. 

4687 

4688 Returns the number of elements removed. 

4689 

4690 For more information see https://redis.io/commands/zremrangebylex 

4691 """ 

4692 return self.execute_command("ZREMRANGEBYLEX", name, min, max) 

4693 

4694 def zremrangebyrank(self, name: KeyT, min: int, max: int) -> ResponseT: 

4695 """ 

4696 Remove all elements in the sorted set ``name`` with ranks between 

4697 ``min`` and ``max``. Values are 0-based, ordered from smallest score 

4698 to largest. Values can be negative indicating the highest scores. 

4699 Returns the number of elements removed 

4700 

4701 For more information see https://redis.io/commands/zremrangebyrank 

4702 """ 

4703 return self.execute_command("ZREMRANGEBYRANK", name, min, max) 

4704 

4705 def zremrangebyscore( 

4706 self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT 

4707 ) -> ResponseT: 

4708 """ 

4709 Remove all elements in the sorted set ``name`` with scores 

4710 between ``min`` and ``max``. Returns the number of elements removed. 

4711 

4712 For more information see https://redis.io/commands/zremrangebyscore 

4713 """ 

4714 return self.execute_command("ZREMRANGEBYSCORE", name, min, max) 

4715 

4716 def zrevrank( 

4717 self, 

4718 name: KeyT, 

4719 value: EncodableT, 

4720 withscore: bool = False, 

4721 ) -> ResponseT: 

4722 """ 

4723 Returns a 0-based value indicating the descending rank of 

4724 ``value`` in sorted set ``name``. 

4725 The optional ``withscore`` argument supplements the command's 

4726 reply with the score of the element returned. 

4727 

4728 For more information see https://redis.io/commands/zrevrank 

4729 """ 

4730 if withscore: 

4731 return self.execute_command("ZREVRANK", name, value, "WITHSCORE") 

4732 return self.execute_command("ZREVRANK", name, value) 

4733 

4734 def zscore(self, name: KeyT, value: EncodableT) -> ResponseT: 

4735 """ 

4736 Return the score of element ``value`` in sorted set ``name`` 

4737 

4738 For more information see https://redis.io/commands/zscore 

4739 """ 

4740 return self.execute_command("ZSCORE", name, value) 

4741 

4742 def zunion( 

4743 self, 

4744 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4745 aggregate: Union[str, None] = None, 

4746 withscores: bool = False, 

4747 ) -> ResponseT: 

4748 """ 

4749 Return the union of multiple sorted sets specified by ``keys``. 

4750 ``keys`` can be provided as dictionary of keys and their weights. 

4751 Scores will be aggregated based on the ``aggregate``, or SUM if 

4752 none is provided. 

4753 

4754 For more information see https://redis.io/commands/zunion 

4755 """ 

4756 return self._zaggregate("ZUNION", None, keys, aggregate, withscores=withscores) 

4757 

4758 def zunionstore( 

4759 self, 

4760 dest: KeyT, 

4761 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4762 aggregate: Union[str, None] = None, 

4763 ) -> ResponseT: 

4764 """ 

4765 Union multiple sorted sets specified by ``keys`` into 

4766 a new sorted set, ``dest``. Scores in the destination will be 

4767 aggregated based on the ``aggregate``, or SUM if none is provided. 

4768 

4769 For more information see https://redis.io/commands/zunionstore 

4770 """ 

4771 return self._zaggregate("ZUNIONSTORE", dest, keys, aggregate) 

4772 

4773 def zmscore(self, key: KeyT, members: List[str]) -> ResponseT: 

4774 """ 

4775 Returns the scores associated with the specified members 

4776 in the sorted set stored at key. 

4777 ``members`` should be a list of the member name. 

4778 Return type is a list of score. 

4779 If the member does not exist, a None will be returned 

4780 in corresponding position. 

4781 

4782 For more information see https://redis.io/commands/zmscore 

4783 """ 

4784 if not members: 

4785 raise DataError("ZMSCORE members must be a non-empty list") 

4786 pieces = [key] + members 

4787 return self.execute_command("ZMSCORE", *pieces) 

4788 

4789 def _zaggregate( 

4790 self, 

4791 command: str, 

4792 dest: Union[KeyT, None], 

4793 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4794 aggregate: Union[str, None] = None, 

4795 **options, 

4796 ) -> ResponseT: 

4797 pieces: list[EncodableT] = [command] 

4798 if dest is not None: 

4799 pieces.append(dest) 

4800 pieces.append(len(keys)) 

4801 if isinstance(keys, dict): 

4802 keys, weights = keys.keys(), keys.values() 

4803 else: 

4804 weights = None 

4805 pieces.extend(keys) 

4806 if weights: 

4807 pieces.append(b"WEIGHTS") 

4808 pieces.extend(weights) 

4809 if aggregate: 

4810 if aggregate.upper() in ["SUM", "MIN", "MAX"]: 

4811 pieces.append(b"AGGREGATE") 

4812 pieces.append(aggregate) 

4813 else: 

4814 raise DataError("aggregate can be sum, min or max.") 

4815 if options.get("withscores", False): 

4816 pieces.append(b"WITHSCORES") 

4817 return self.execute_command(*pieces, **options) 

4818 

4819 

4820AsyncSortedSetCommands = SortedSetCommands 

4821 

4822 

4823class HyperlogCommands(CommandsProtocol): 

4824 """ 

4825 Redis commands of HyperLogLogs data type. 

4826 see: https://redis.io/topics/data-types-intro#hyperloglogs 

4827 """ 

4828 

4829 def pfadd(self, name: KeyT, *values: FieldT) -> ResponseT: 

4830 """ 

4831 Adds the specified elements to the specified HyperLogLog. 

4832 

4833 For more information see https://redis.io/commands/pfadd 

4834 """ 

4835 return self.execute_command("PFADD", name, *values) 

4836 

4837 def pfcount(self, *sources: KeyT) -> ResponseT: 

4838 """ 

4839 Return the approximated cardinality of 

4840 the set observed by the HyperLogLog at key(s). 

4841 

4842 For more information see https://redis.io/commands/pfcount 

4843 """ 

4844 return self.execute_command("PFCOUNT", *sources) 

4845 

4846 def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT: 

4847 """ 

4848 Merge N different HyperLogLogs into a single one. 

4849 

4850 For more information see https://redis.io/commands/pfmerge 

4851 """ 

4852 return self.execute_command("PFMERGE", dest, *sources) 

4853 

4854 

4855AsyncHyperlogCommands = HyperlogCommands 

4856 

4857 

4858class HashCommands(CommandsProtocol): 

4859 """ 

4860 Redis commands for Hash data type. 

4861 see: https://redis.io/topics/data-types-intro#redis-hashes 

4862 """ 

4863 

4864 def hdel(self, name: str, *keys: List) -> Union[Awaitable[int], int]: 

4865 """ 

4866 Delete ``keys`` from hash ``name`` 

4867 

4868 For more information see https://redis.io/commands/hdel 

4869 """ 

4870 return self.execute_command("HDEL", name, *keys) 

4871 

4872 def hexists(self, name: str, key: str) -> Union[Awaitable[bool], bool]: 

4873 """ 

4874 Returns a boolean indicating if ``key`` exists within hash ``name`` 

4875 

4876 For more information see https://redis.io/commands/hexists 

4877 """ 

4878 return self.execute_command("HEXISTS", name, key) 

4879 

4880 def hget( 

4881 self, name: str, key: str 

4882 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

4883 """ 

4884 Return the value of ``key`` within the hash ``name`` 

4885 

4886 For more information see https://redis.io/commands/hget 

4887 """ 

4888 return self.execute_command("HGET", name, key) 

4889 

4890 def hgetall(self, name: str) -> Union[Awaitable[dict], dict]: 

4891 """ 

4892 Return a Python dict of the hash's name/value pairs 

4893 

4894 For more information see https://redis.io/commands/hgetall 

4895 """ 

4896 return self.execute_command("HGETALL", name) 

4897 

4898 def hincrby( 

4899 self, name: str, key: str, amount: int = 1 

4900 ) -> Union[Awaitable[int], int]: 

4901 """ 

4902 Increment the value of ``key`` in hash ``name`` by ``amount`` 

4903 

4904 For more information see https://redis.io/commands/hincrby 

4905 """ 

4906 return self.execute_command("HINCRBY", name, key, amount) 

4907 

4908 def hincrbyfloat( 

4909 self, name: str, key: str, amount: float = 1.0 

4910 ) -> Union[Awaitable[float], float]: 

4911 """ 

4912 Increment the value of ``key`` in hash ``name`` by floating ``amount`` 

4913 

4914 For more information see https://redis.io/commands/hincrbyfloat 

4915 """ 

4916 return self.execute_command("HINCRBYFLOAT", name, key, amount) 

4917 

4918 def hkeys(self, name: str) -> Union[Awaitable[List], List]: 

4919 """ 

4920 Return the list of keys within hash ``name`` 

4921 

4922 For more information see https://redis.io/commands/hkeys 

4923 """ 

4924 return self.execute_command("HKEYS", name) 

4925 

4926 def hlen(self, name: str) -> Union[Awaitable[int], int]: 

4927 """ 

4928 Return the number of elements in hash ``name`` 

4929 

4930 For more information see https://redis.io/commands/hlen 

4931 """ 

4932 return self.execute_command("HLEN", name) 

4933 

4934 def hset( 

4935 self, 

4936 name: str, 

4937 key: Optional[str] = None, 

4938 value: Optional[str] = None, 

4939 mapping: Optional[dict] = None, 

4940 items: Optional[list] = None, 

4941 ) -> Union[Awaitable[int], int]: 

4942 """ 

4943 Set ``key`` to ``value`` within hash ``name``, 

4944 ``mapping`` accepts a dict of key/value pairs that will be 

4945 added to hash ``name``. 

4946 ``items`` accepts a list of key/value pairs that will be 

4947 added to hash ``name``. 

4948 Returns the number of fields that were added. 

4949 

4950 For more information see https://redis.io/commands/hset 

4951 """ 

4952 if key is None and not mapping and not items: 

4953 raise DataError("'hset' with no key value pairs") 

4954 items = items or [] 

4955 if key is not None: 

4956 items.extend((key, value)) 

4957 if mapping: 

4958 for pair in mapping.items(): 

4959 items.extend(pair) 

4960 

4961 return self.execute_command("HSET", name, *items) 

4962 

4963 def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool]: 

4964 """ 

4965 Set ``key`` to ``value`` within hash ``name`` if ``key`` does not 

4966 exist. Returns 1 if HSETNX created a field, otherwise 0. 

4967 

4968 For more information see https://redis.io/commands/hsetnx 

4969 """ 

4970 return self.execute_command("HSETNX", name, key, value) 

4971 

4972 def hmset(self, name: str, mapping: dict) -> Union[Awaitable[str], str]: 

4973 """ 

4974 Set key to value within hash ``name`` for each corresponding 

4975 key and value from the ``mapping`` dict. 

4976 

4977 For more information see https://redis.io/commands/hmset 

4978 """ 

4979 warnings.warn( 

4980 f"{self.__class__.__name__}.hmset() is deprecated. " 

4981 f"Use {self.__class__.__name__}.hset() instead.", 

4982 DeprecationWarning, 

4983 stacklevel=2, 

4984 ) 

4985 if not mapping: 

4986 raise DataError("'hmset' with 'mapping' of length 0") 

4987 items = [] 

4988 for pair in mapping.items(): 

4989 items.extend(pair) 

4990 return self.execute_command("HMSET", name, *items) 

4991 

4992 def hmget(self, name: str, keys: List, *args: List) -> Union[Awaitable[List], List]: 

4993 """ 

4994 Returns a list of values ordered identically to ``keys`` 

4995 

4996 For more information see https://redis.io/commands/hmget 

4997 """ 

4998 args = list_or_args(keys, args) 

4999 return self.execute_command("HMGET", name, *args) 

5000 

5001 def hvals(self, name: str) -> Union[Awaitable[List], List]: 

5002 """ 

5003 Return the list of values within hash ``name`` 

5004 

5005 For more information see https://redis.io/commands/hvals 

5006 """ 

5007 return self.execute_command("HVALS", name) 

5008 

5009 def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]: 

5010 """ 

5011 Return the number of bytes stored in the value of ``key`` 

5012 within hash ``name`` 

5013 

5014 For more information see https://redis.io/commands/hstrlen 

5015 """ 

5016 return self.execute_command("HSTRLEN", name, key) 

5017 

5018 

5019AsyncHashCommands = HashCommands 

5020 

5021 

5022class Script: 

5023 """ 

5024 An executable Lua script object returned by ``register_script`` 

5025 """ 

5026 

5027 def __init__(self, registered_client: "Redis", script: ScriptTextT): 

5028 self.registered_client = registered_client 

5029 self.script = script 

5030 # Precalculate and store the SHA1 hex digest of the script. 

5031 

5032 if isinstance(script, str): 

5033 # We need the encoding from the client in order to generate an 

5034 # accurate byte representation of the script 

5035 try: 

5036 encoder = registered_client.connection_pool.get_encoder() 

5037 except AttributeError: 

5038 # Cluster 

5039 encoder = registered_client.get_encoder() 

5040 script = encoder.encode(script) 

5041 self.sha = hashlib.sha1(script).hexdigest() 

5042 

5043 def __call__( 

5044 self, 

5045 keys: Union[Sequence[KeyT], None] = None, 

5046 args: Union[Iterable[EncodableT], None] = None, 

5047 client: Union["Redis", None] = None, 

5048 ): 

5049 """Execute the script, passing any required ``args``""" 

5050 keys = keys or [] 

5051 args = args or [] 

5052 if client is None: 

5053 client = self.registered_client 

5054 args = tuple(keys) + tuple(args) 

5055 # make sure the Redis server knows about the script 

5056 from redis.client import Pipeline 

5057 

5058 if isinstance(client, Pipeline): 

5059 # Make sure the pipeline can register the script before executing. 

5060 client.scripts.add(self) 

5061 try: 

5062 return client.evalsha(self.sha, len(keys), *args) 

5063 except NoScriptError: 

5064 # Maybe the client is pointed to a different server than the client 

5065 # that created this instance? 

5066 # Overwrite the sha just in case there was a discrepancy. 

5067 self.sha = client.script_load(self.script) 

5068 return client.evalsha(self.sha, len(keys), *args) 

5069 

5070 

5071class AsyncScript: 

5072 """ 

5073 An executable Lua script object returned by ``register_script`` 

5074 """ 

5075 

5076 def __init__(self, registered_client: "AsyncRedis", script: ScriptTextT): 

5077 self.registered_client = registered_client 

5078 self.script = script 

5079 # Precalculate and store the SHA1 hex digest of the script. 

5080 

5081 if isinstance(script, str): 

5082 # We need the encoding from the client in order to generate an 

5083 # accurate byte representation of the script 

5084 try: 

5085 encoder = registered_client.connection_pool.get_encoder() 

5086 except AttributeError: 

5087 # Cluster 

5088 encoder = registered_client.get_encoder() 

5089 script = encoder.encode(script) 

5090 self.sha = hashlib.sha1(script).hexdigest() 

5091 

5092 async def __call__( 

5093 self, 

5094 keys: Union[Sequence[KeyT], None] = None, 

5095 args: Union[Iterable[EncodableT], None] = None, 

5096 client: Union["AsyncRedis", None] = None, 

5097 ): 

5098 """Execute the script, passing any required ``args``""" 

5099 keys = keys or [] 

5100 args = args or [] 

5101 if client is None: 

5102 client = self.registered_client 

5103 args = tuple(keys) + tuple(args) 

5104 # make sure the Redis server knows about the script 

5105 from redis.asyncio.client import Pipeline 

5106 

5107 if isinstance(client, Pipeline): 

5108 # Make sure the pipeline can register the script before executing. 

5109 client.scripts.add(self) 

5110 try: 

5111 return await client.evalsha(self.sha, len(keys), *args) 

5112 except NoScriptError: 

5113 # Maybe the client is pointed to a different server than the client 

5114 # that created this instance? 

5115 # Overwrite the sha just in case there was a discrepancy. 

5116 self.sha = await client.script_load(self.script) 

5117 return await client.evalsha(self.sha, len(keys), *args) 

5118 

5119 

5120class PubSubCommands(CommandsProtocol): 

5121 """ 

5122 Redis PubSub commands. 

5123 see https://redis.io/topics/pubsub 

5124 """ 

5125 

5126 def publish(self, channel: ChannelT, message: EncodableT, **kwargs) -> ResponseT: 

5127 """ 

5128 Publish ``message`` on ``channel``. 

5129 Returns the number of subscribers the message was delivered to. 

5130 

5131 For more information see https://redis.io/commands/publish 

5132 """ 

5133 return self.execute_command("PUBLISH", channel, message, **kwargs) 

5134 

5135 def pubsub_channels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

5136 """ 

5137 Return a list of channels that have at least one subscriber 

5138 

5139 For more information see https://redis.io/commands/pubsub-channels 

5140 """ 

5141 return self.execute_command("PUBSUB CHANNELS", pattern, **kwargs) 

5142 

5143 def pubsub_numpat(self, **kwargs) -> ResponseT: 

5144 """ 

5145 Returns the number of subscriptions to patterns 

5146 

5147 For more information see https://redis.io/commands/pubsub-numpat 

5148 """ 

5149 return self.execute_command("PUBSUB NUMPAT", **kwargs) 

5150 

5151 def pubsub_numsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

5152 """ 

5153 Return a list of (channel, number of subscribers) tuples 

5154 for each channel given in ``*args`` 

5155 

5156 For more information see https://redis.io/commands/pubsub-numsub 

5157 """ 

5158 return self.execute_command("PUBSUB NUMSUB", *args, **kwargs) 

5159 

5160 

5161AsyncPubSubCommands = PubSubCommands 

5162 

5163 

5164class ScriptCommands(CommandsProtocol): 

5165 """ 

5166 Redis Lua script commands. see: 

5167 https://redis.com/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/ 

5168 """ 

5169 

5170 def _eval( 

5171 self, command: str, script: str, numkeys: int, *keys_and_args: list 

5172 ) -> Union[Awaitable[str], str]: 

5173 return self.execute_command(command, script, numkeys, *keys_and_args) 

5174 

5175 def eval( 

5176 self, script: str, numkeys: int, *keys_and_args: list 

5177 ) -> Union[Awaitable[str], str]: 

5178 """ 

5179 Execute the Lua ``script``, specifying the ``numkeys`` the script 

5180 will touch and the key names and argument values in ``keys_and_args``. 

5181 Returns the result of the script. 

5182 

5183 In practice, use the object returned by ``register_script``. This 

5184 function exists purely for Redis API completion. 

5185 

5186 For more information see https://redis.io/commands/eval 

5187 """ 

5188 return self._eval("EVAL", script, numkeys, *keys_and_args) 

5189 

5190 def eval_ro( 

5191 self, script: str, numkeys: int, *keys_and_args: list 

5192 ) -> Union[Awaitable[str], str]: 

5193 """ 

5194 The read-only variant of the EVAL command 

5195 

5196 Execute the read-only Lua ``script`` specifying the ``numkeys`` the script 

5197 will touch and the key names and argument values in ``keys_and_args``. 

5198 Returns the result of the script. 

5199 

5200 For more information see https://redis.io/commands/eval_ro 

5201 """ 

5202 return self._eval("EVAL_RO", script, numkeys, *keys_and_args) 

5203 

5204 def _evalsha( 

5205 self, command: str, sha: str, numkeys: int, *keys_and_args: list 

5206 ) -> Union[Awaitable[str], str]: 

5207 return self.execute_command(command, sha, numkeys, *keys_and_args) 

5208 

5209 def evalsha( 

5210 self, sha: str, numkeys: int, *keys_and_args: list 

5211 ) -> Union[Awaitable[str], str]: 

5212 """ 

5213 Use the ``sha`` to execute a Lua script already registered via EVAL 

5214 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

5215 key names and argument values in ``keys_and_args``. Returns the result 

5216 of the script. 

5217 

5218 In practice, use the object returned by ``register_script``. This 

5219 function exists purely for Redis API completion. 

5220 

5221 For more information see https://redis.io/commands/evalsha 

5222 """ 

5223 return self._evalsha("EVALSHA", sha, numkeys, *keys_and_args) 

5224 

5225 def evalsha_ro( 

5226 self, sha: str, numkeys: int, *keys_and_args: list 

5227 ) -> Union[Awaitable[str], str]: 

5228 """ 

5229 The read-only variant of the EVALSHA command 

5230 

5231 Use the ``sha`` to execute a read-only Lua script already registered via EVAL 

5232 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

5233 key names and argument values in ``keys_and_args``. Returns the result 

5234 of the script. 

5235 

5236 For more information see https://redis.io/commands/evalsha_ro 

5237 """ 

5238 return self._evalsha("EVALSHA_RO", sha, numkeys, *keys_and_args) 

5239 

5240 def script_exists(self, *args: str) -> ResponseT: 

5241 """ 

5242 Check if a script exists in the script cache by specifying the SHAs of 

5243 each script as ``args``. Returns a list of boolean values indicating if 

5244 if each already script exists in the cache. 

5245 

5246 For more information see https://redis.io/commands/script-exists 

5247 """ 

5248 return self.execute_command("SCRIPT EXISTS", *args) 

5249 

5250 def script_debug(self, *args) -> None: 

5251 raise NotImplementedError( 

5252 "SCRIPT DEBUG is intentionally not implemented in the client." 

5253 ) 

5254 

5255 def script_flush( 

5256 self, sync_type: Union[Literal["SYNC"], Literal["ASYNC"]] = None 

5257 ) -> ResponseT: 

5258 """Flush all scripts from the script cache. 

5259 ``sync_type`` is by default SYNC (synchronous) but it can also be 

5260 ASYNC. 

5261 For more information see https://redis.io/commands/script-flush 

5262 """ 

5263 

5264 # Redis pre 6 had no sync_type. 

5265 if sync_type not in ["SYNC", "ASYNC", None]: 

5266 raise DataError( 

5267 "SCRIPT FLUSH defaults to SYNC in redis > 6.2, or " 

5268 "accepts SYNC/ASYNC. For older versions, " 

5269 "of redis leave as None." 

5270 ) 

5271 if sync_type is None: 

5272 pieces = [] 

5273 else: 

5274 pieces = [sync_type] 

5275 return self.execute_command("SCRIPT FLUSH", *pieces) 

5276 

5277 def script_kill(self) -> ResponseT: 

5278 """ 

5279 Kill the currently executing Lua script 

5280 

5281 For more information see https://redis.io/commands/script-kill 

5282 """ 

5283 return self.execute_command("SCRIPT KILL") 

5284 

5285 def script_load(self, script: ScriptTextT) -> ResponseT: 

5286 """ 

5287 Load a Lua ``script`` into the script cache. Returns the SHA. 

5288 

5289 For more information see https://redis.io/commands/script-load 

5290 """ 

5291 return self.execute_command("SCRIPT LOAD", script) 

5292 

5293 def register_script(self: "Redis", script: ScriptTextT) -> Script: 

5294 """ 

5295 Register a Lua ``script`` specifying the ``keys`` it will touch. 

5296 Returns a Script object that is callable and hides the complexity of 

5297 deal with scripts, keys, and shas. This is the preferred way to work 

5298 with Lua scripts. 

5299 """ 

5300 return Script(self, script) 

5301 

5302 

5303class AsyncScriptCommands(ScriptCommands): 

5304 async def script_debug(self, *args) -> None: 

5305 return super().script_debug() 

5306 

5307 def register_script(self: "AsyncRedis", script: ScriptTextT) -> AsyncScript: 

5308 """ 

5309 Register a Lua ``script`` specifying the ``keys`` it will touch. 

5310 Returns a Script object that is callable and hides the complexity of 

5311 deal with scripts, keys, and shas. This is the preferred way to work 

5312 with Lua scripts. 

5313 """ 

5314 return AsyncScript(self, script) 

5315 

5316 

5317class GeoCommands(CommandsProtocol): 

5318 """ 

5319 Redis Geospatial commands. 

5320 see: https://redis.com/redis-best-practices/indexing-patterns/geospatial/ 

5321 """ 

5322 

5323 def geoadd( 

5324 self, 

5325 name: KeyT, 

5326 values: Sequence[EncodableT], 

5327 nx: bool = False, 

5328 xx: bool = False, 

5329 ch: bool = False, 

5330 ) -> ResponseT: 

5331 """ 

5332 Add the specified geospatial items to the specified key identified 

5333 by the ``name`` argument. The Geospatial items are given as ordered 

5334 members of the ``values`` argument, each item or place is formed by 

5335 the triad longitude, latitude and name. 

5336 

5337 Note: You can use ZREM to remove elements. 

5338 

5339 ``nx`` forces ZADD to only create new elements and not to update 

5340 scores for elements that already exist. 

5341 

5342 ``xx`` forces ZADD to only update scores of elements that already 

5343 exist. New elements will not be added. 

5344 

5345 ``ch`` modifies the return value to be the numbers of elements changed. 

5346 Changed elements include new elements that were added and elements 

5347 whose scores changed. 

5348 

5349 For more information see https://redis.io/commands/geoadd 

5350 """ 

5351 if nx and xx: 

5352 raise DataError("GEOADD allows either 'nx' or 'xx', not both") 

5353 if len(values) % 3 != 0: 

5354 raise DataError("GEOADD requires places with lon, lat and name values") 

5355 pieces = [name] 

5356 if nx: 

5357 pieces.append("NX") 

5358 if xx: 

5359 pieces.append("XX") 

5360 if ch: 

5361 pieces.append("CH") 

5362 pieces.extend(values) 

5363 return self.execute_command("GEOADD", *pieces) 

5364 

5365 def geodist( 

5366 self, name: KeyT, place1: FieldT, place2: FieldT, unit: Union[str, None] = None 

5367 ) -> ResponseT: 

5368 """ 

5369 Return the distance between ``place1`` and ``place2`` members of the 

5370 ``name`` key. 

5371 The units must be one of the following : m, km mi, ft. By default 

5372 meters are used. 

5373 

5374 For more information see https://redis.io/commands/geodist 

5375 """ 

5376 pieces: list[EncodableT] = [name, place1, place2] 

5377 if unit and unit not in ("m", "km", "mi", "ft"): 

5378 raise DataError("GEODIST invalid unit") 

5379 elif unit: 

5380 pieces.append(unit) 

5381 return self.execute_command("GEODIST", *pieces) 

5382 

5383 def geohash(self, name: KeyT, *values: FieldT) -> ResponseT: 

5384 """ 

5385 Return the geo hash string for each item of ``values`` members of 

5386 the specified key identified by the ``name`` argument. 

5387 

5388 For more information see https://redis.io/commands/geohash 

5389 """ 

5390 return self.execute_command("GEOHASH", name, *values) 

5391 

5392 def geopos(self, name: KeyT, *values: FieldT) -> ResponseT: 

5393 """ 

5394 Return the positions of each item of ``values`` as members of 

5395 the specified key identified by the ``name`` argument. Each position 

5396 is represented by the pairs lon and lat. 

5397 

5398 For more information see https://redis.io/commands/geopos 

5399 """ 

5400 return self.execute_command("GEOPOS", name, *values) 

5401 

5402 def georadius( 

5403 self, 

5404 name: KeyT, 

5405 longitude: float, 

5406 latitude: float, 

5407 radius: float, 

5408 unit: Union[str, None] = None, 

5409 withdist: bool = False, 

5410 withcoord: bool = False, 

5411 withhash: bool = False, 

5412 count: Union[int, None] = None, 

5413 sort: Union[str, None] = None, 

5414 store: Union[KeyT, None] = None, 

5415 store_dist: Union[KeyT, None] = None, 

5416 any: bool = False, 

5417 ) -> ResponseT: 

5418 """ 

5419 Return the members of the specified key identified by the 

5420 ``name`` argument which are within the borders of the area specified 

5421 with the ``latitude`` and ``longitude`` location and the maximum 

5422 distance from the center specified by the ``radius`` value. 

5423 

5424 The units must be one of the following : m, km mi, ft. By default 

5425 

5426 ``withdist`` indicates to return the distances of each place. 

5427 

5428 ``withcoord`` indicates to return the latitude and longitude of 

5429 each place. 

5430 

5431 ``withhash`` indicates to return the geohash string of each place. 

5432 

5433 ``count`` indicates to return the number of elements up to N. 

5434 

5435 ``sort`` indicates to return the places in a sorted way, ASC for 

5436 nearest to fairest and DESC for fairest to nearest. 

5437 

5438 ``store`` indicates to save the places names in a sorted set named 

5439 with a specific key, each element of the destination sorted set is 

5440 populated with the score got from the original geo sorted set. 

5441 

5442 ``store_dist`` indicates to save the places names in a sorted set 

5443 named with a specific key, instead of ``store`` the sorted set 

5444 destination score is set with the distance. 

5445 

5446 For more information see https://redis.io/commands/georadius 

5447 """ 

5448 return self._georadiusgeneric( 

5449 "GEORADIUS", 

5450 name, 

5451 longitude, 

5452 latitude, 

5453 radius, 

5454 unit=unit, 

5455 withdist=withdist, 

5456 withcoord=withcoord, 

5457 withhash=withhash, 

5458 count=count, 

5459 sort=sort, 

5460 store=store, 

5461 store_dist=store_dist, 

5462 any=any, 

5463 ) 

5464 

5465 def georadiusbymember( 

5466 self, 

5467 name: KeyT, 

5468 member: FieldT, 

5469 radius: float, 

5470 unit: Union[str, None] = None, 

5471 withdist: bool = False, 

5472 withcoord: bool = False, 

5473 withhash: bool = False, 

5474 count: Union[int, None] = None, 

5475 sort: Union[str, None] = None, 

5476 store: Union[KeyT, None] = None, 

5477 store_dist: Union[KeyT, None] = None, 

5478 any: bool = False, 

5479 ) -> ResponseT: 

5480 """ 

5481 This command is exactly like ``georadius`` with the sole difference 

5482 that instead of taking, as the center of the area to query, a longitude 

5483 and latitude value, it takes the name of a member already existing 

5484 inside the geospatial index represented by the sorted set. 

5485 

5486 For more information see https://redis.io/commands/georadiusbymember 

5487 """ 

5488 return self._georadiusgeneric( 

5489 "GEORADIUSBYMEMBER", 

5490 name, 

5491 member, 

5492 radius, 

5493 unit=unit, 

5494 withdist=withdist, 

5495 withcoord=withcoord, 

5496 withhash=withhash, 

5497 count=count, 

5498 sort=sort, 

5499 store=store, 

5500 store_dist=store_dist, 

5501 any=any, 

5502 ) 

5503 

5504 def _georadiusgeneric( 

5505 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

5506 ) -> ResponseT: 

5507 pieces = list(args) 

5508 if kwargs["unit"] and kwargs["unit"] not in ("m", "km", "mi", "ft"): 

5509 raise DataError("GEORADIUS invalid unit") 

5510 elif kwargs["unit"]: 

5511 pieces.append(kwargs["unit"]) 

5512 else: 

5513 pieces.append("m") 

5514 

5515 if kwargs["any"] and kwargs["count"] is None: 

5516 raise DataError("``any`` can't be provided without ``count``") 

5517 

5518 for arg_name, byte_repr in ( 

5519 ("withdist", "WITHDIST"), 

5520 ("withcoord", "WITHCOORD"), 

5521 ("withhash", "WITHHASH"), 

5522 ): 

5523 if kwargs[arg_name]: 

5524 pieces.append(byte_repr) 

5525 

5526 if kwargs["count"] is not None: 

5527 pieces.extend(["COUNT", kwargs["count"]]) 

5528 if kwargs["any"]: 

5529 pieces.append("ANY") 

5530 

5531 if kwargs["sort"]: 

5532 if kwargs["sort"] == "ASC": 

5533 pieces.append("ASC") 

5534 elif kwargs["sort"] == "DESC": 

5535 pieces.append("DESC") 

5536 else: 

5537 raise DataError("GEORADIUS invalid sort") 

5538 

5539 if kwargs["store"] and kwargs["store_dist"]: 

5540 raise DataError("GEORADIUS store and store_dist cant be set together") 

5541 

5542 if kwargs["store"]: 

5543 pieces.extend([b"STORE", kwargs["store"]]) 

5544 

5545 if kwargs["store_dist"]: 

5546 pieces.extend([b"STOREDIST", kwargs["store_dist"]]) 

5547 

5548 return self.execute_command(command, *pieces, **kwargs) 

5549 

5550 def geosearch( 

5551 self, 

5552 name: KeyT, 

5553 member: Union[FieldT, None] = None, 

5554 longitude: Union[float, None] = None, 

5555 latitude: Union[float, None] = None, 

5556 unit: str = "m", 

5557 radius: Union[float, None] = None, 

5558 width: Union[float, None] = None, 

5559 height: Union[float, None] = None, 

5560 sort: Union[str, None] = None, 

5561 count: Union[int, None] = None, 

5562 any: bool = False, 

5563 withcoord: bool = False, 

5564 withdist: bool = False, 

5565 withhash: bool = False, 

5566 ) -> ResponseT: 

5567 """ 

5568 Return the members of specified key identified by the 

5569 ``name`` argument, which are within the borders of the 

5570 area specified by a given shape. This command extends the 

5571 GEORADIUS command, so in addition to searching within circular 

5572 areas, it supports searching within rectangular areas. 

5573 This command should be used in place of the deprecated 

5574 GEORADIUS and GEORADIUSBYMEMBER commands. 

5575 ``member`` Use the position of the given existing 

5576 member in the sorted set. Can't be given with ``longitude`` 

5577 and ``latitude``. 

5578 ``longitude`` and ``latitude`` Use the position given by 

5579 this coordinates. Can't be given with ``member`` 

5580 ``radius`` Similar to GEORADIUS, search inside circular 

5581 area according the given radius. Can't be given with 

5582 ``height`` and ``width``. 

5583 ``height`` and ``width`` Search inside an axis-aligned 

5584 rectangle, determined by the given height and width. 

5585 Can't be given with ``radius`` 

5586 ``unit`` must be one of the following : m, km, mi, ft. 

5587 `m` for meters (the default value), `km` for kilometers, 

5588 `mi` for miles and `ft` for feet. 

5589 ``sort`` indicates to return the places in a sorted way, 

5590 ASC for nearest to furthest and DESC for furthest to nearest. 

5591 ``count`` limit the results to the first count matching items. 

5592 ``any`` is set to True, the command will return as soon as 

5593 enough matches are found. Can't be provided without ``count`` 

5594 ``withdist`` indicates to return the distances of each place. 

5595 ``withcoord`` indicates to return the latitude and longitude of 

5596 each place. 

5597 ``withhash`` indicates to return the geohash string of each place. 

5598 

5599 For more information see https://redis.io/commands/geosearch 

5600 """ 

5601 

5602 return self._geosearchgeneric( 

5603 "GEOSEARCH", 

5604 name, 

5605 member=member, 

5606 longitude=longitude, 

5607 latitude=latitude, 

5608 unit=unit, 

5609 radius=radius, 

5610 width=width, 

5611 height=height, 

5612 sort=sort, 

5613 count=count, 

5614 any=any, 

5615 withcoord=withcoord, 

5616 withdist=withdist, 

5617 withhash=withhash, 

5618 store=None, 

5619 store_dist=None, 

5620 ) 

5621 

5622 def geosearchstore( 

5623 self, 

5624 dest: KeyT, 

5625 name: KeyT, 

5626 member: Union[FieldT, None] = None, 

5627 longitude: Union[float, None] = None, 

5628 latitude: Union[float, None] = None, 

5629 unit: str = "m", 

5630 radius: Union[float, None] = None, 

5631 width: Union[float, None] = None, 

5632 height: Union[float, None] = None, 

5633 sort: Union[str, None] = None, 

5634 count: Union[int, None] = None, 

5635 any: bool = False, 

5636 storedist: bool = False, 

5637 ) -> ResponseT: 

5638 """ 

5639 This command is like GEOSEARCH, but stores the result in 

5640 ``dest``. By default, it stores the results in the destination 

5641 sorted set with their geospatial information. 

5642 if ``store_dist`` set to True, the command will stores the 

5643 items in a sorted set populated with their distance from the 

5644 center of the circle or box, as a floating-point number. 

5645 

5646 For more information see https://redis.io/commands/geosearchstore 

5647 """ 

5648 return self._geosearchgeneric( 

5649 "GEOSEARCHSTORE", 

5650 dest, 

5651 name, 

5652 member=member, 

5653 longitude=longitude, 

5654 latitude=latitude, 

5655 unit=unit, 

5656 radius=radius, 

5657 width=width, 

5658 height=height, 

5659 sort=sort, 

5660 count=count, 

5661 any=any, 

5662 withcoord=None, 

5663 withdist=None, 

5664 withhash=None, 

5665 store=None, 

5666 store_dist=storedist, 

5667 ) 

5668 

5669 def _geosearchgeneric( 

5670 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

5671 ) -> ResponseT: 

5672 pieces = list(args) 

5673 

5674 # FROMMEMBER or FROMLONLAT 

5675 if kwargs["member"] is None: 

5676 if kwargs["longitude"] is None or kwargs["latitude"] is None: 

5677 raise DataError("GEOSEARCH must have member or longitude and latitude") 

5678 if kwargs["member"]: 

5679 if kwargs["longitude"] or kwargs["latitude"]: 

5680 raise DataError( 

5681 "GEOSEARCH member and longitude or latitude cant be set together" 

5682 ) 

5683 pieces.extend([b"FROMMEMBER", kwargs["member"]]) 

5684 if kwargs["longitude"] is not None and kwargs["latitude"] is not None: 

5685 pieces.extend([b"FROMLONLAT", kwargs["longitude"], kwargs["latitude"]]) 

5686 

5687 # BYRADIUS or BYBOX 

5688 if kwargs["radius"] is None: 

5689 if kwargs["width"] is None or kwargs["height"] is None: 

5690 raise DataError("GEOSEARCH must have radius or width and height") 

5691 if kwargs["unit"] is None: 

5692 raise DataError("GEOSEARCH must have unit") 

5693 if kwargs["unit"].lower() not in ("m", "km", "mi", "ft"): 

5694 raise DataError("GEOSEARCH invalid unit") 

5695 if kwargs["radius"]: 

5696 if kwargs["width"] or kwargs["height"]: 

5697 raise DataError( 

5698 "GEOSEARCH radius and width or height cant be set together" 

5699 ) 

5700 pieces.extend([b"BYRADIUS", kwargs["radius"], kwargs["unit"]]) 

5701 if kwargs["width"] and kwargs["height"]: 

5702 pieces.extend([b"BYBOX", kwargs["width"], kwargs["height"], kwargs["unit"]]) 

5703 

5704 # sort 

5705 if kwargs["sort"]: 

5706 if kwargs["sort"].upper() == "ASC": 

5707 pieces.append(b"ASC") 

5708 elif kwargs["sort"].upper() == "DESC": 

5709 pieces.append(b"DESC") 

5710 else: 

5711 raise DataError("GEOSEARCH invalid sort") 

5712 

5713 # count any 

5714 if kwargs["count"]: 

5715 pieces.extend([b"COUNT", kwargs["count"]]) 

5716 if kwargs["any"]: 

5717 pieces.append(b"ANY") 

5718 elif kwargs["any"]: 

5719 raise DataError("GEOSEARCH ``any`` can't be provided without count") 

5720 

5721 # other properties 

5722 for arg_name, byte_repr in ( 

5723 ("withdist", b"WITHDIST"), 

5724 ("withcoord", b"WITHCOORD"), 

5725 ("withhash", b"WITHHASH"), 

5726 ("store_dist", b"STOREDIST"), 

5727 ): 

5728 if kwargs[arg_name]: 

5729 pieces.append(byte_repr) 

5730 

5731 return self.execute_command(command, *pieces, **kwargs) 

5732 

5733 

5734AsyncGeoCommands = GeoCommands 

5735 

5736 

5737class ModuleCommands(CommandsProtocol): 

5738 """ 

5739 Redis Module commands. 

5740 see: https://redis.io/topics/modules-intro 

5741 """ 

5742 

5743 def module_load(self, path, *args) -> ResponseT: 

5744 """ 

5745 Loads the module from ``path``. 

5746 Passes all ``*args`` to the module, during loading. 

5747 Raises ``ModuleError`` if a module is not found at ``path``. 

5748 

5749 For more information see https://redis.io/commands/module-load 

5750 """ 

5751 return self.execute_command("MODULE LOAD", path, *args) 

5752 

5753 def module_loadex( 

5754 self, 

5755 path: str, 

5756 options: Optional[List[str]] = None, 

5757 args: Optional[List[str]] = None, 

5758 ) -> ResponseT: 

5759 """ 

5760 Loads a module from a dynamic library at runtime with configuration directives. 

5761 

5762 For more information see https://redis.io/commands/module-loadex 

5763 """ 

5764 pieces = [] 

5765 if options is not None: 

5766 pieces.append("CONFIG") 

5767 pieces.extend(options) 

5768 if args is not None: 

5769 pieces.append("ARGS") 

5770 pieces.extend(args) 

5771 

5772 return self.execute_command("MODULE LOADEX", path, *pieces) 

5773 

5774 def module_unload(self, name) -> ResponseT: 

5775 """ 

5776 Unloads the module ``name``. 

5777 Raises ``ModuleError`` if ``name`` is not in loaded modules. 

5778 

5779 For more information see https://redis.io/commands/module-unload 

5780 """ 

5781 return self.execute_command("MODULE UNLOAD", name) 

5782 

5783 def module_list(self) -> ResponseT: 

5784 """ 

5785 Returns a list of dictionaries containing the name and version of 

5786 all loaded modules. 

5787 

5788 For more information see https://redis.io/commands/module-list 

5789 """ 

5790 return self.execute_command("MODULE LIST") 

5791 

5792 def command_info(self) -> None: 

5793 raise NotImplementedError( 

5794 "COMMAND INFO is intentionally not implemented in the client." 

5795 ) 

5796 

5797 def command_count(self) -> ResponseT: 

5798 return self.execute_command("COMMAND COUNT") 

5799 

5800 def command_getkeys(self, *args) -> ResponseT: 

5801 return self.execute_command("COMMAND GETKEYS", *args) 

5802 

5803 def command(self) -> ResponseT: 

5804 return self.execute_command("COMMAND") 

5805 

5806 

5807class Script: 

5808 """ 

5809 An executable Lua script object returned by ``register_script`` 

5810 """ 

5811 

5812 def __init__(self, registered_client, script): 

5813 self.registered_client = registered_client 

5814 self.script = script 

5815 # Precalculate and store the SHA1 hex digest of the script. 

5816 

5817 if isinstance(script, str): 

5818 # We need the encoding from the client in order to generate an 

5819 # accurate byte representation of the script 

5820 encoder = self.get_encoder() 

5821 script = encoder.encode(script) 

5822 self.sha = hashlib.sha1(script).hexdigest() 

5823 

5824 def __call__(self, keys=[], args=[], client=None): 

5825 "Execute the script, passing any required ``args``" 

5826 if client is None: 

5827 client = self.registered_client 

5828 args = tuple(keys) + tuple(args) 

5829 # make sure the Redis server knows about the script 

5830 from redis.client import Pipeline 

5831 

5832 if isinstance(client, Pipeline): 

5833 # Make sure the pipeline can register the script before executing. 

5834 client.scripts.add(self) 

5835 try: 

5836 return client.evalsha(self.sha, len(keys), *args) 

5837 except NoScriptError: 

5838 # Maybe the client is pointed to a different server than the client 

5839 # that created this instance? 

5840 # Overwrite the sha just in case there was a discrepancy. 

5841 self.sha = client.script_load(self.script) 

5842 return client.evalsha(self.sha, len(keys), *args) 

5843 

5844 def get_encoder(self): 

5845 """Get the encoder to encode string scripts into bytes.""" 

5846 try: 

5847 return self.registered_client.get_encoder() 

5848 except AttributeError: 

5849 # DEPRECATED 

5850 # In version <=4.1.2, this was the code we used to get the encoder. 

5851 # However, after 4.1.2 we added support for scripting in clustered 

5852 # redis. ClusteredRedis doesn't have a `.connection_pool` attribute 

5853 # so we changed the Script class to use 

5854 # `self.registered_client.get_encoder` (see above). 

5855 # However, that is technically a breaking change, as consumers who 

5856 # use Scripts directly might inject a `registered_client` that 

5857 # doesn't have a `.get_encoder` field. This try/except prevents us 

5858 # from breaking backward-compatibility. Ideally, it would be 

5859 # removed in the next major release. 

5860 return self.registered_client.connection_pool.get_encoder() 

5861 

5862 

5863class AsyncModuleCommands(ModuleCommands): 

5864 async def command_info(self) -> None: 

5865 return super().command_info() 

5866 

5867 

5868class ClusterCommands(CommandsProtocol): 

5869 """ 

5870 Class for Redis Cluster commands 

5871 """ 

5872 

5873 def cluster(self, cluster_arg, *args, **kwargs) -> ResponseT: 

5874 return self.execute_command(f"CLUSTER {cluster_arg.upper()}", *args, **kwargs) 

5875 

5876 def readwrite(self, **kwargs) -> ResponseT: 

5877 """ 

5878 Disables read queries for a connection to a Redis Cluster slave node. 

5879 

5880 For more information see https://redis.io/commands/readwrite 

5881 """ 

5882 return self.execute_command("READWRITE", **kwargs) 

5883 

5884 def readonly(self, **kwargs) -> ResponseT: 

5885 """ 

5886 Enables read queries for a connection to a Redis Cluster replica node. 

5887 

5888 For more information see https://redis.io/commands/readonly 

5889 """ 

5890 return self.execute_command("READONLY", **kwargs) 

5891 

5892 

5893AsyncClusterCommands = ClusterCommands 

5894 

5895 

5896class FunctionCommands: 

5897 """ 

5898 Redis Function commands 

5899 """ 

5900 

5901 def function_load( 

5902 self, code: str, replace: Optional[bool] = False 

5903 ) -> Union[Awaitable[str], str]: 

5904 """ 

5905 Load a library to Redis. 

5906 :param code: the source code (must start with 

5907 Shebang statement that provides a metadata about the library) 

5908 :param replace: changes the behavior to overwrite the existing library 

5909 with the new contents. 

5910 Return the library name that was loaded. 

5911 

5912 For more information see https://redis.io/commands/function-load 

5913 """ 

5914 pieces = ["REPLACE"] if replace else [] 

5915 pieces.append(code) 

5916 return self.execute_command("FUNCTION LOAD", *pieces) 

5917 

5918 def function_delete(self, library: str) -> Union[Awaitable[str], str]: 

5919 """ 

5920 Delete the library called ``library`` and all its functions. 

5921 

5922 For more information see https://redis.io/commands/function-delete 

5923 """ 

5924 return self.execute_command("FUNCTION DELETE", library) 

5925 

5926 def function_flush(self, mode: str = "SYNC") -> Union[Awaitable[str], str]: 

5927 """ 

5928 Deletes all the libraries. 

5929 

5930 For more information see https://redis.io/commands/function-flush 

5931 """ 

5932 return self.execute_command("FUNCTION FLUSH", mode) 

5933 

5934 def function_list( 

5935 self, library: Optional[str] = "*", withcode: Optional[bool] = False 

5936 ) -> Union[Awaitable[List], List]: 

5937 """ 

5938 Return information about the functions and libraries. 

5939 :param library: pecify a pattern for matching library names 

5940 :param withcode: cause the server to include the libraries source 

5941 implementation in the reply 

5942 """ 

5943 args = ["LIBRARYNAME", library] 

5944 if withcode: 

5945 args.append("WITHCODE") 

5946 return self.execute_command("FUNCTION LIST", *args) 

5947 

5948 def _fcall( 

5949 self, command: str, function, numkeys: int, *keys_and_args: Optional[List] 

5950 ) -> Union[Awaitable[str], str]: 

5951 return self.execute_command(command, function, numkeys, *keys_and_args) 

5952 

5953 def fcall( 

5954 self, function, numkeys: int, *keys_and_args: Optional[List] 

5955 ) -> Union[Awaitable[str], str]: 

5956 """ 

5957 Invoke a function. 

5958 

5959 For more information see https://redis.io/commands/fcall 

5960 """ 

5961 return self._fcall("FCALL", function, numkeys, *keys_and_args) 

5962 

5963 def fcall_ro( 

5964 self, function, numkeys: int, *keys_and_args: Optional[List] 

5965 ) -> Union[Awaitable[str], str]: 

5966 """ 

5967 This is a read-only variant of the FCALL command that cannot 

5968 execute commands that modify data. 

5969 

5970 For more information see https://redis.io/commands/fcal_ro 

5971 """ 

5972 return self._fcall("FCALL_RO", function, numkeys, *keys_and_args) 

5973 

5974 def function_dump(self) -> Union[Awaitable[str], str]: 

5975 """ 

5976 Return the serialized payload of loaded libraries. 

5977 

5978 For more information see https://redis.io/commands/function-dump 

5979 """ 

5980 from redis.client import NEVER_DECODE 

5981 

5982 options = {} 

5983 options[NEVER_DECODE] = [] 

5984 

5985 return self.execute_command("FUNCTION DUMP", **options) 

5986 

5987 def function_restore( 

5988 self, payload: str, policy: Optional[str] = "APPEND" 

5989 ) -> Union[Awaitable[str], str]: 

5990 """ 

5991 Restore libraries from the serialized ``payload``. 

5992 You can use the optional policy argument to provide a policy 

5993 for handling existing libraries. 

5994 

5995 For more information see https://redis.io/commands/function-restore 

5996 """ 

5997 return self.execute_command("FUNCTION RESTORE", payload, policy) 

5998 

5999 def function_kill(self) -> Union[Awaitable[str], str]: 

6000 """ 

6001 Kill a function that is currently executing. 

6002 

6003 For more information see https://redis.io/commands/function-kill 

6004 """ 

6005 return self.execute_command("FUNCTION KILL") 

6006 

6007 def function_stats(self) -> Union[Awaitable[List], List]: 

6008 """ 

6009 Return information about the function that's currently running 

6010 and information about the available execution engines. 

6011 

6012 For more information see https://redis.io/commands/function-stats 

6013 """ 

6014 return self.execute_command("FUNCTION STATS") 

6015 

6016 

6017AsyncFunctionCommands = FunctionCommands 

6018 

6019 

6020class DataAccessCommands( 

6021 BasicKeyCommands, 

6022 HyperlogCommands, 

6023 HashCommands, 

6024 GeoCommands, 

6025 ListCommands, 

6026 ScanCommands, 

6027 SetCommands, 

6028 StreamCommands, 

6029 SortedSetCommands, 

6030): 

6031 """ 

6032 A class containing all of the implemented data access redis commands. 

6033 This class is to be used as a mixin for synchronous Redis clients. 

6034 """ 

6035 

6036 

6037class AsyncDataAccessCommands( 

6038 AsyncBasicKeyCommands, 

6039 AsyncHyperlogCommands, 

6040 AsyncHashCommands, 

6041 AsyncGeoCommands, 

6042 AsyncListCommands, 

6043 AsyncScanCommands, 

6044 AsyncSetCommands, 

6045 AsyncStreamCommands, 

6046 AsyncSortedSetCommands, 

6047): 

6048 """ 

6049 A class containing all of the implemented data access redis commands. 

6050 This class is to be used as a mixin for asynchronous Redis clients. 

6051 """ 

6052 

6053 

6054class CoreCommands( 

6055 ACLCommands, 

6056 ClusterCommands, 

6057 DataAccessCommands, 

6058 ManagementCommands, 

6059 ModuleCommands, 

6060 PubSubCommands, 

6061 ScriptCommands, 

6062 FunctionCommands, 

6063): 

6064 """ 

6065 A class containing all of the implemented redis commands. This class is 

6066 to be used as a mixin for synchronous Redis clients. 

6067 """ 

6068 

6069 

6070class AsyncCoreCommands( 

6071 AsyncACLCommands, 

6072 AsyncClusterCommands, 

6073 AsyncDataAccessCommands, 

6074 AsyncManagementCommands, 

6075 AsyncModuleCommands, 

6076 AsyncPubSubCommands, 

6077 AsyncScriptCommands, 

6078 AsyncFunctionCommands, 

6079): 

6080 """ 

6081 A class containing all of the implemented redis commands. This class is 

6082 to be used as a mixin for asynchronous Redis clients. 

6083 """