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

1867 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-23 06:16 +0000

1# from __future__ import annotations 

2 

3import datetime 

4import hashlib 

5import warnings 

6from typing import ( 

7 TYPE_CHECKING, 

8 AsyncIterator, 

9 Awaitable, 

10 Callable, 

11 Dict, 

12 Iterable, 

13 Iterator, 

14 List, 

15 Literal, 

16 Mapping, 

17 Optional, 

18 Sequence, 

19 Set, 

20 Tuple, 

21 Union, 

22) 

23 

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

25from redis.typing import ( 

26 AbsExpiryT, 

27 AnyKeyT, 

28 BitfieldOffsetT, 

29 ChannelT, 

30 CommandsProtocol, 

31 ConsumerT, 

32 EncodableT, 

33 ExpiryT, 

34 FieldT, 

35 GroupT, 

36 KeysT, 

37 KeyT, 

38 PatternT, 

39 ResponseT, 

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 

52 

53class ACLCommands(CommandsProtocol): 

54 """ 

55 Redis Access Control List (ACL) commands. 

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

57 """ 

58 

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

60 """ 

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

62 

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

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

65 that category. 

66 

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

68 """ 

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

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

71 

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

73 """ 

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

75 

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

77 """ 

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

79 

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

81 """ 

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

83 

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

85 """ 

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

87 

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

89 """Generate a random password value. 

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

91 the next multiple of 4. 

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

93 """ 

94 pieces = [] 

95 if bits is not None: 

96 try: 

97 b = int(bits) 

98 if b < 0 or b > 4096: 

99 raise ValueError 

100 pieces.append(b) 

101 except ValueError: 

102 raise DataError( 

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

104 ) 

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

106 

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

108 """ 

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

110 

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

112 

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

114 """ 

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

116 

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

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

119 the different subcommands. 

120 

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

122 """ 

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

124 

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

126 """ 

127 Return a list of all ACLs on the server 

128 

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

130 """ 

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

132 

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

134 """ 

135 Get ACL logs as a list. 

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

137 :rtype: List. 

138 

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

140 """ 

141 args = [] 

142 if count is not None: 

143 if not isinstance(count, int): 

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

145 args.append(count) 

146 

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

148 

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

150 """ 

151 Reset ACL logs. 

152 :rtype: Boolean. 

153 

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

155 """ 

156 args = [b"RESET"] 

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

158 

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

160 """ 

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

162 

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

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

165 

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

167 """ 

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

169 

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

171 """ 

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

173 

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

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

176 

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

178 """ 

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

180 

181 def acl_setuser( 

182 self, 

183 username: str, 

184 enabled: bool = False, 

185 nopass: bool = False, 

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

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

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

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

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

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

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

193 reset: bool = False, 

194 reset_keys: bool = False, 

195 reset_channels: bool = False, 

196 reset_passwords: bool = False, 

197 **kwargs, 

198 ) -> ResponseT: 

199 """ 

200 Create or update an ACL user. 

201 

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

203 the existing ACL is completely overwritten and replaced with the 

204 specified values. 

205 

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

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

208 

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

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

211 

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

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

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

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

216 removing a single password. 

217 

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

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

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

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

222 adding or removing a single password. 

223 

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

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

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

227 

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

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

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

231 

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

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

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

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

236 prefixed with a '~'. 

237 

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

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

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

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

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

243 rules will be applied on top. 

244 

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

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

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

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

249 will be applied on top. 

250 

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

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

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

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

255 will be applied on top. 

256 

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

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

259 applying any new passwords specified in 'passwords' or 

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

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

262 or hashed_passwords will be applied on top. 

263 

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

265 """ 

266 encoder = self.get_encoder() 

267 pieces: List[EncodableT] = [username] 

268 

269 if reset: 

270 pieces.append(b"reset") 

271 

272 if reset_keys: 

273 pieces.append(b"resetkeys") 

274 

275 if reset_channels: 

276 pieces.append(b"resetchannels") 

277 

278 if reset_passwords: 

279 pieces.append(b"resetpass") 

280 

281 if enabled: 

282 pieces.append(b"on") 

283 else: 

284 pieces.append(b"off") 

285 

286 if (passwords or hashed_passwords) and nopass: 

287 raise DataError( 

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

289 ) 

290 

291 if passwords: 

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

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

294 passwords = list_or_args(passwords, []) 

295 for i, password in enumerate(passwords): 

296 password = encoder.encode(password) 

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

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

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

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

301 else: 

302 raise DataError( 

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

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

305 ) 

306 

307 if hashed_passwords: 

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

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

310 hashed_passwords = list_or_args(hashed_passwords, []) 

311 for i, hashed_password in enumerate(hashed_passwords): 

312 hashed_password = encoder.encode(hashed_password) 

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

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

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

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

317 else: 

318 raise DataError( 

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

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

321 ) 

322 

323 if nopass: 

324 pieces.append(b"nopass") 

325 

326 if categories: 

327 for category in categories: 

328 category = encoder.encode(category) 

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

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

331 pieces.append(category) 

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

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

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

335 pieces.append(category) 

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

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

338 else: 

339 raise DataError( 

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

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

342 ) 

343 if commands: 

344 for cmd in commands: 

345 cmd = encoder.encode(cmd) 

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

347 raise DataError( 

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

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

350 ) 

351 pieces.append(cmd) 

352 

353 if keys: 

354 for key in keys: 

355 key = encoder.encode(key) 

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

357 key = b"~%s" % key 

358 pieces.append(key) 

359 

360 if channels: 

361 for channel in channels: 

362 channel = encoder.encode(channel) 

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

364 

365 if selectors: 

366 for cmd, key in selectors: 

367 cmd = encoder.encode(cmd) 

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

369 raise DataError( 

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

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

372 ) 

373 

374 key = encoder.encode(key) 

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

376 key = b"~%s" % key 

377 

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

379 

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

381 

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

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

384 

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

386 """ 

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

388 

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

390 """Get the username for the current connection 

391 

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

393 """ 

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

395 

396 

397AsyncACLCommands = ACLCommands 

398 

399 

400class ManagementCommands(CommandsProtocol): 

401 """ 

402 Redis management commands 

403 """ 

404 

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

406 """ 

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

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

409 authenticate for the given user. 

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

411 """ 

412 pieces = [] 

413 if username is not None: 

414 pieces.append(username) 

415 pieces.append(password) 

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

417 

418 def bgrewriteaof(self, **kwargs): 

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

420 

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

422 """ 

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

424 

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

426 """ 

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

428 this method is asynchronous and returns immediately. 

429 

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

431 """ 

432 pieces = [] 

433 if schedule: 

434 pieces.append("SCHEDULE") 

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

436 

437 def role(self) -> ResponseT: 

438 """ 

439 Provide information on the role of a Redis instance in 

440 the context of replication, by returning if the instance 

441 is currently a master, slave, or sentinel. 

442 

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

444 """ 

445 return self.execute_command("ROLE") 

446 

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

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

449 

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

451 """ 

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

453 

454 def client_kill_filter( 

455 self, 

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

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

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

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

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

461 user: str = None, 

462 **kwargs, 

463 ) -> ResponseT: 

464 """ 

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

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

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

468 'master', 'slave' or 'pubsub' 

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

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

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

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

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

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

475 """ 

476 args = [] 

477 if _type is not None: 

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

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

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

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

482 if skipme is not None: 

483 if not isinstance(skipme, bool): 

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

485 if skipme: 

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

487 else: 

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

489 if _id is not None: 

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

491 if addr is not None: 

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

493 if laddr is not None: 

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

495 if user is not None: 

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

497 if not args: 

498 raise DataError( 

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

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

501 ) 

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

503 

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

505 """ 

506 Returns information and statistics about the current 

507 client connection. 

508 

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

510 """ 

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

512 

513 def client_list( 

514 self, _type: Union[str, None] = None, client_id: List[EncodableT] = [], **kwargs 

515 ) -> ResponseT: 

516 """ 

517 Returns a list of currently connected clients. 

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

519 

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 

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

564 ON - The default most with server replies to commands 

565 OFF - Disable server responses to commands 

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

567 

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

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

570 TimeoutError. 

571 The test_client_reply unit test illustrates this, and 

572 conftest.py has a client with a timeout. 

573 

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

575 """ 

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

577 if reply not in replies: 

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

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

580 

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

582 """ 

583 Returns the current connection id 

584 

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

586 """ 

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

588 

589 def client_tracking_on( 

590 self, 

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

592 prefix: Sequence[KeyT] = [], 

593 bcast: bool = False, 

594 optin: bool = False, 

595 optout: bool = False, 

596 noloop: bool = False, 

597 ) -> ResponseT: 

598 """ 

599 Turn on the tracking mode. 

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

601 

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

603 """ 

604 return self.client_tracking( 

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

606 ) 

607 

608 def client_tracking_off( 

609 self, 

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

611 prefix: Sequence[KeyT] = [], 

612 bcast: bool = False, 

613 optin: bool = False, 

614 optout: bool = False, 

615 noloop: bool = False, 

616 ) -> ResponseT: 

617 """ 

618 Turn off the tracking mode. 

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

620 

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

622 """ 

623 return self.client_tracking( 

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

625 ) 

626 

627 def client_tracking( 

628 self, 

629 on: bool = True, 

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

631 prefix: Sequence[KeyT] = [], 

632 bcast: bool = False, 

633 optin: bool = False, 

634 optout: bool = False, 

635 noloop: bool = False, 

636 **kwargs, 

637 ) -> ResponseT: 

638 """ 

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

640 for server assisted client side caching. 

641 

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

643 

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

645 the specified ID. 

646 

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

648 invalidation messages are reported for all the prefixes 

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

650 

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

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

653 after a CLIENT CACHING yes command. 

654 

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

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

657 CLIENT CACHING no command. 

658 

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

660 connection itself. 

661 

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

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

664 

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

666 """ 

667 

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

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

670 

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

672 if clientid is not None: 

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

674 for p in prefix: 

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

676 if bcast: 

677 pieces.append("BCAST") 

678 if optin: 

679 pieces.append("OPTIN") 

680 if optout: 

681 pieces.append("OPTOUT") 

682 if noloop: 

683 pieces.append("NOLOOP") 

684 

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

686 

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

688 """ 

689 Returns the information about the current client connection's 

690 use of the server assisted client side cache. 

691 

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

693 """ 

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

695 

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

697 """ 

698 Sets the current connection name 

699 

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

701 

702 .. note:: 

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

704 

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

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

707 """ 

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

709 

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

711 """ 

712 Sets the current connection library name or version 

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

714 """ 

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

716 

717 def client_unblock( 

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

719 ) -> ResponseT: 

720 """ 

721 Unblocks a connection by its client id. 

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

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

724 regular timeout mechanism. 

725 

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

727 """ 

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

729 if error: 

730 args.append(b"ERROR") 

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

732 

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

734 """ 

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

736 

737 

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

739 

740 :param timeout: milliseconds to pause clients 

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

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

743 a write command. 

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

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

746 PUBLISH: Will block client. 

747 PFCOUNT: Will block client. 

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

749 appear blocked. 

750 """ 

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

752 if not isinstance(timeout, int): 

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

754 if not all: 

755 args.append("WRITE") 

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

757 

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

759 """ 

760 Unpause all redis clients 

761 

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

763 """ 

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

765 

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

767 """ 

768 Sets the client eviction mode for the current connection. 

769 

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

771 """ 

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

773 

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

775 """ 

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

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

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

779 # unless it sends the TOUCH command. 

780 

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

782 """ 

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

784 

785 def command(self, **kwargs): 

786 """ 

787 Returns dict reply of details about all Redis commands. 

788 

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

790 """ 

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

792 

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

794 raise NotImplementedError( 

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

796 ) 

797 

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

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

800 

801 def command_list( 

802 self, 

803 module: Optional[str] = None, 

804 category: Optional[str] = None, 

805 pattern: Optional[str] = None, 

806 ) -> ResponseT: 

807 """ 

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

809 You can use one of the following filters: 

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

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

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

813 

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

815 """ 

816 pieces = [] 

817 if module is not None: 

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

819 if category is not None: 

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

821 if pattern is not None: 

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

823 

824 if pieces: 

825 pieces.insert(0, "FILTERBY") 

826 

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

828 

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

830 """ 

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

832 

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

834 """ 

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

836 

837 def command_docs(self, *args): 

838 """ 

839 This function throws a NotImplementedError since it is intentionally 

840 not supported. 

841 """ 

842 raise NotImplementedError( 

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

844 ) 

845 

846 def config_get( 

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

848 ) -> ResponseT: 

849 """ 

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

851 

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

853 """ 

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

855 

856 def config_set( 

857 self, 

858 name: KeyT, 

859 value: EncodableT, 

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

861 **kwargs, 

862 ) -> ResponseT: 

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

864 

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

866 """ 

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

868 

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

870 """ 

871 Reset runtime statistics 

872 

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

874 """ 

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

876 

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

878 """ 

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

880 

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

882 """ 

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

884 

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

886 """ 

887 Returns the number of keys in the current database 

888 

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

890 """ 

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

892 

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

894 """ 

895 Returns version specific meta information about a given key 

896 

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

898 """ 

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

900 

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

902 raise NotImplementedError( 

903 """ 

904 DEBUG SEGFAULT is intentionally not implemented in the client. 

905 

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

907 """ 

908 ) 

909 

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

911 """ 

912 Echo the string back from the server 

913 

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

915 """ 

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

917 

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

919 """ 

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

921 

922 ``asynchronous`` indicates whether the operation is 

923 executed asynchronously by the server. 

924 

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

926 """ 

927 args = [] 

928 if asynchronous: 

929 args.append(b"ASYNC") 

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

931 

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

933 """ 

934 Delete all keys in the current database. 

935 

936 ``asynchronous`` indicates whether the operation is 

937 executed asynchronously by the server. 

938 

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

940 """ 

941 args = [] 

942 if asynchronous: 

943 args.append(b"ASYNC") 

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

945 

946 def sync(self) -> ResponseT: 

947 """ 

948 Initiates a replication stream from the master. 

949 

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

951 """ 

952 from redis.client import NEVER_DECODE 

953 

954 options = {} 

955 options[NEVER_DECODE] = [] 

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

957 

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

959 """ 

960 Initiates a replication stream from the master. 

961 Newer version for `sync`. 

962 

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

964 """ 

965 from redis.client import NEVER_DECODE 

966 

967 options = {} 

968 options[NEVER_DECODE] = [] 

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

970 

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

972 """ 

973 Swap two databases 

974 

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

976 """ 

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

978 

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

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

981 

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

983 """ 

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

985 

986 def info( 

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

988 ) -> ResponseT: 

989 """ 

990 Returns a dictionary containing information about the Redis server 

991 

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

993 of information 

994 

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

996 and will generate ResponseError 

997 

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

999 """ 

1000 if section is None: 

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

1002 else: 

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

1004 

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

1006 """ 

1007 Return a Python datetime object representing the last time the 

1008 Redis database was saved to disk 

1009 

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

1011 """ 

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

1013 

1014 def latency_doctor(self): 

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

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

1017 

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

1019 """ 

1020 raise NotImplementedError( 

1021 """ 

1022 LATENCY DOCTOR is intentionally not implemented in the client. 

1023 

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

1025 """ 

1026 ) 

1027 

1028 def latency_graph(self): 

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

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

1031 

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

1033 """ 

1034 raise NotImplementedError( 

1035 """ 

1036 LATENCY GRAPH is intentionally not implemented in the client. 

1037 

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

1039 """ 

1040 ) 

1041 

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

1043 """ 

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

1045 

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

1047 """ 

1048 if version_numbers: 

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

1050 else: 

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

1052 

1053 def reset(self) -> ResponseT: 

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

1055 

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

1057 """ 

1058 return self.execute_command("RESET") 

1059 

1060 def migrate( 

1061 self, 

1062 host: str, 

1063 port: int, 

1064 keys: KeysT, 

1065 destination_db: int, 

1066 timeout: int, 

1067 copy: bool = False, 

1068 replace: bool = False, 

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

1070 **kwargs, 

1071 ) -> ResponseT: 

1072 """ 

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

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

1075 

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

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

1078 command is interrupted. 

1079 

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

1081 the source server. 

1082 

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

1084 on the destination server if they exist. 

1085 

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

1087 the password provided. 

1088 

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

1090 """ 

1091 keys = list_or_args(keys, []) 

1092 if not keys: 

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

1094 pieces = [] 

1095 if copy: 

1096 pieces.append(b"COPY") 

1097 if replace: 

1098 pieces.append(b"REPLACE") 

1099 if auth: 

1100 pieces.append(b"AUTH") 

1101 pieces.append(auth) 

1102 pieces.append(b"KEYS") 

1103 pieces.extend(keys) 

1104 return self.execute_command( 

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

1106 ) 

1107 

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

1109 """ 

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

1111 """ 

1112 return self.execute_command( 

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

1114 ) 

1115 

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

1117 raise NotImplementedError( 

1118 """ 

1119 MEMORY DOCTOR is intentionally not implemented in the client. 

1120 

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

1122 """ 

1123 ) 

1124 

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

1126 raise NotImplementedError( 

1127 """ 

1128 MEMORY HELP is intentionally not implemented in the client. 

1129 

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

1131 """ 

1132 ) 

1133 

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

1135 """ 

1136 Return a dictionary of memory stats 

1137 

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

1139 """ 

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

1141 

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

1143 """ 

1144 Return an internal statistics report from the memory allocator. 

1145 

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

1147 """ 

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

1149 

1150 def memory_usage( 

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

1152 ) -> ResponseT: 

1153 """ 

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

1155 administrative overheads. 

1156 

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

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

1159 all elements. 

1160 

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

1162 """ 

1163 args = [] 

1164 if isinstance(samples, int): 

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

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

1167 

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

1169 """ 

1170 Attempts to purge dirty pages for reclamation by allocator 

1171 

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

1173 """ 

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

1175 

1176 def latency_histogram(self, *args): 

1177 """ 

1178 This function throws a NotImplementedError since it is intentionally 

1179 not supported. 

1180 """ 

1181 raise NotImplementedError( 

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

1183 ) 

1184 

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

1186 """ 

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

1188 

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

1190 """ 

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

1192 

1193 def latency_latest(self) -> ResponseT: 

1194 """ 

1195 Reports the latest latency events logged. 

1196 

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

1198 """ 

1199 return self.execute_command("LATENCY LATEST") 

1200 

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

1202 """ 

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

1204 

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

1206 """ 

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

1208 

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

1210 """ 

1211 Ping the Redis server 

1212 

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

1214 """ 

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

1216 

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

1218 """ 

1219 Ask the server to close the connection. 

1220 

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

1222 """ 

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

1224 

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

1226 """ 

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

1228 

1229 Examples of valid arguments include: 

1230 

1231 NO ONE (set no replication) 

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

1233 

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

1235 """ 

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

1237 

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

1239 """ 

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

1241 blocking until the save is complete 

1242 

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

1244 """ 

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

1246 

1247 def shutdown( 

1248 self, 

1249 save: bool = False, 

1250 nosave: bool = False, 

1251 now: bool = False, 

1252 force: bool = False, 

1253 abort: bool = False, 

1254 **kwargs, 

1255 ) -> None: 

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

1257 data will be flushed before shutdown. 

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

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

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

1261 are configured. 

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

1263 the shutdown sequence. 

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

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

1266 

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

1268 """ 

1269 if save and nosave: 

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

1271 args = ["SHUTDOWN"] 

1272 if save: 

1273 args.append("SAVE") 

1274 if nosave: 

1275 args.append("NOSAVE") 

1276 if now: 

1277 args.append("NOW") 

1278 if force: 

1279 args.append("FORCE") 

1280 if abort: 

1281 args.append("ABORT") 

1282 try: 

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

1284 except ConnectionError: 

1285 # a ConnectionError here is expected 

1286 return 

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

1288 

1289 def slaveof( 

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

1291 ) -> ResponseT: 

1292 """ 

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

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

1295 instance is promoted to a master instead. 

1296 

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

1298 """ 

1299 if host is None and port is None: 

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

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

1302 

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

1304 """ 

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

1306 most recent ``num`` items. 

1307 

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

1309 """ 

1310 from redis.client import NEVER_DECODE 

1311 

1312 args = ["SLOWLOG GET"] 

1313 if num is not None: 

1314 args.append(num) 

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

1316 if decode_responses is True: 

1317 kwargs[NEVER_DECODE] = [] 

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

1319 

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

1321 """ 

1322 Get the number of items in the slowlog 

1323 

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

1325 """ 

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

1327 

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

1329 """ 

1330 Remove all items in the slowlog 

1331 

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

1333 """ 

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

1335 

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

1337 """ 

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

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

1340 

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

1342 """ 

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

1344 

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

1346 """ 

1347 Redis synchronous replication 

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

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

1350 reached. 

1351 

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

1353 """ 

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

1355 

1356 def waitaof( 

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

1358 ) -> ResponseT: 

1359 """ 

1360 This command blocks the current client until all previous write 

1361 commands by that client are acknowledged as having been fsynced 

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

1363 of replicas. 

1364 

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

1366 """ 

1367 return self.execute_command( 

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

1369 ) 

1370 

1371 def hello(self): 

1372 """ 

1373 This function throws a NotImplementedError since it is intentionally 

1374 not supported. 

1375 """ 

1376 raise NotImplementedError( 

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

1378 ) 

1379 

1380 def failover(self): 

1381 """ 

1382 This function throws a NotImplementedError since it is intentionally 

1383 not supported. 

1384 """ 

1385 raise NotImplementedError( 

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

1387 ) 

1388 

1389 

1390AsyncManagementCommands = ManagementCommands 

1391 

1392 

1393class AsyncManagementCommands(ManagementCommands): 

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

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

1396 

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

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

1399 

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

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

1402 

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

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

1405 

1406 async def shutdown( 

1407 self, 

1408 save: bool = False, 

1409 nosave: bool = False, 

1410 now: bool = False, 

1411 force: bool = False, 

1412 abort: bool = False, 

1413 **kwargs, 

1414 ) -> None: 

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

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

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

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

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

1420 

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

1422 """ 

1423 if save and nosave: 

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

1425 args = ["SHUTDOWN"] 

1426 if save: 

1427 args.append("SAVE") 

1428 if nosave: 

1429 args.append("NOSAVE") 

1430 if now: 

1431 args.append("NOW") 

1432 if force: 

1433 args.append("FORCE") 

1434 if abort: 

1435 args.append("ABORT") 

1436 try: 

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

1438 except ConnectionError: 

1439 # a ConnectionError here is expected 

1440 return 

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

1442 

1443 

1444class BitFieldOperation: 

1445 """ 

1446 Command builder for BITFIELD commands. 

1447 """ 

1448 

1449 def __init__( 

1450 self, 

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

1452 key: str, 

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

1454 ): 

1455 self.client = client 

1456 self.key = key 

1457 self._default_overflow = default_overflow 

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

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

1460 self._last_overflow = "WRAP" 

1461 self.reset() 

1462 

1463 def reset(self): 

1464 """ 

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

1466 """ 

1467 self.operations = [] 

1468 self._last_overflow = "WRAP" 

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

1470 

1471 def overflow(self, overflow: str): 

1472 """ 

1473 Update the overflow algorithm of successive INCRBY operations 

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

1475 Redis docs for descriptions of these algorithmsself. 

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

1477 """ 

1478 overflow = overflow.upper() 

1479 if overflow != self._last_overflow: 

1480 self._last_overflow = overflow 

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

1482 return self 

1483 

1484 def incrby( 

1485 self, 

1486 fmt: str, 

1487 offset: BitfieldOffsetT, 

1488 increment: int, 

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

1490 ): 

1491 """ 

1492 Increment a bitfield by a given amount. 

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

1494 for an unsigned 8-bit integer. 

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

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

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

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

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

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

1501 descriptions of these algorithms. 

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

1503 """ 

1504 if overflow is not None: 

1505 self.overflow(overflow) 

1506 

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

1508 return self 

1509 

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

1511 """ 

1512 Get the value of a given bitfield. 

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

1514 an unsigned 8-bit integer. 

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

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

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

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

1519 """ 

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

1521 return self 

1522 

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

1524 """ 

1525 Set the value of a given bitfield. 

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

1527 an unsigned 8-bit integer. 

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

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

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

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

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

1533 """ 

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

1535 return self 

1536 

1537 @property 

1538 def command(self): 

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

1540 for ops in self.operations: 

1541 cmd.extend(ops) 

1542 return cmd 

1543 

1544 def execute(self) -> ResponseT: 

1545 """ 

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

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

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

1549 will be present within the pipeline's execute. 

1550 """ 

1551 command = self.command 

1552 self.reset() 

1553 return self.client.execute_command(*command) 

1554 

1555 

1556class BasicKeyCommands(CommandsProtocol): 

1557 """ 

1558 Redis basic key-based commands 

1559 """ 

1560 

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

1562 """ 

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

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

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

1566 

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

1568 """ 

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

1570 

1571 def bitcount( 

1572 self, 

1573 key: KeyT, 

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

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

1576 mode: Optional[str] = None, 

1577 ) -> ResponseT: 

1578 """ 

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

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

1581 

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

1583 """ 

1584 params = [key] 

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

1586 params.append(start) 

1587 params.append(end) 

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

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

1590 if mode is not None: 

1591 params.append(mode) 

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

1593 

1594 def bitfield( 

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

1596 key: KeyT, 

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

1598 ) -> BitFieldOperation: 

1599 """ 

1600 Return a BitFieldOperation instance to conveniently construct one or 

1601 more bitfield operations on ``key``. 

1602 

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

1604 """ 

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

1606 

1607 def bitfield_ro( 

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

1609 key: KeyT, 

1610 encoding: str, 

1611 offset: BitfieldOffsetT, 

1612 items: Optional[list] = None, 

1613 ) -> ResponseT: 

1614 """ 

1615 Return an array of the specified bitfield values 

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

1617 parameters and remaining values are result of corresponding 

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

1619 Read-only variant of the BITFIELD command. 

1620 

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

1622 """ 

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

1624 

1625 items = items or [] 

1626 for encoding, offset in items: 

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

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

1629 

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

1631 """ 

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

1633 store the result in ``dest``. 

1634 

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

1636 """ 

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

1638 

1639 def bitpos( 

1640 self, 

1641 key: KeyT, 

1642 bit: int, 

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

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

1645 mode: Optional[str] = None, 

1646 ) -> ResponseT: 

1647 """ 

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

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

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

1651 means to look at the first three bytes. 

1652 

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

1654 """ 

1655 if bit not in (0, 1): 

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

1657 params = [key, bit] 

1658 

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

1660 

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

1662 params.append(end) 

1663 elif start is None and end is not None: 

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

1665 

1666 if mode is not None: 

1667 params.append(mode) 

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

1669 

1670 def copy( 

1671 self, 

1672 source: str, 

1673 destination: str, 

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

1675 replace: bool = False, 

1676 ) -> ResponseT: 

1677 """ 

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

1679 

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

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

1682 

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

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

1685 the ``destination`` key already exists. 

1686 

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

1688 """ 

1689 params = [source, destination] 

1690 if destination_db is not None: 

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

1692 if replace: 

1693 params.append("REPLACE") 

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

1695 

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

1697 """ 

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

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

1700 

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

1702 """ 

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

1704 

1705 decr = decrby 

1706 

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

1708 """ 

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

1710 """ 

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

1712 

1713 def __delitem__(self, name: KeyT): 

1714 self.delete(name) 

1715 

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

1717 """ 

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

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

1720 

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

1722 """ 

1723 from redis.client import NEVER_DECODE 

1724 

1725 options = {} 

1726 options[NEVER_DECODE] = [] 

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

1728 

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

1730 """ 

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

1732 

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

1734 """ 

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

1736 

1737 __contains__ = exists 

1738 

1739 def expire( 

1740 self, 

1741 name: KeyT, 

1742 time: ExpiryT, 

1743 nx: bool = False, 

1744 xx: bool = False, 

1745 gt: bool = False, 

1746 lt: bool = False, 

1747 ) -> ResponseT: 

1748 """ 

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

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

1751 object. 

1752 

1753 Valid options are: 

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

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

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

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

1758 

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

1760 """ 

1761 if isinstance(time, datetime.timedelta): 

1762 time = int(time.total_seconds()) 

1763 

1764 exp_option = list() 

1765 if nx: 

1766 exp_option.append("NX") 

1767 if xx: 

1768 exp_option.append("XX") 

1769 if gt: 

1770 exp_option.append("GT") 

1771 if lt: 

1772 exp_option.append("LT") 

1773 

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

1775 

1776 def expireat( 

1777 self, 

1778 name: KeyT, 

1779 when: AbsExpiryT, 

1780 nx: bool = False, 

1781 xx: bool = False, 

1782 gt: bool = False, 

1783 lt: bool = False, 

1784 ) -> ResponseT: 

1785 """ 

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

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

1788 datetime object. 

1789 

1790 Valid options are: 

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

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

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

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

1795 

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

1797 """ 

1798 if isinstance(when, datetime.datetime): 

1799 when = int(when.timestamp()) 

1800 

1801 exp_option = list() 

1802 if nx: 

1803 exp_option.append("NX") 

1804 if xx: 

1805 exp_option.append("XX") 

1806 if gt: 

1807 exp_option.append("GT") 

1808 if lt: 

1809 exp_option.append("LT") 

1810 

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

1812 

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

1814 """ 

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

1816 at which the given key will expire. 

1817 

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

1819 """ 

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

1821 

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

1823 """ 

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

1825 

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

1827 """ 

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

1829 

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

1831 """ 

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

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

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

1835 is a string). 

1836 

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

1838 """ 

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

1840 

1841 def getex( 

1842 self, 

1843 name: KeyT, 

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

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

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

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

1848 persist: bool = False, 

1849 ) -> ResponseT: 

1850 """ 

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

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

1853 additional options. All time parameters can be given as 

1854 datetime.timedelta or integers. 

1855 

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

1857 

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

1859 

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

1861 specified in unix time. 

1862 

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

1864 specified in unix time. 

1865 

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

1867 

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

1869 """ 

1870 

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

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

1873 raise DataError( 

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

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

1876 ) 

1877 

1878 pieces: list[EncodableT] = [] 

1879 # similar to set command 

1880 if ex is not None: 

1881 pieces.append("EX") 

1882 if isinstance(ex, datetime.timedelta): 

1883 ex = int(ex.total_seconds()) 

1884 pieces.append(ex) 

1885 if px is not None: 

1886 pieces.append("PX") 

1887 if isinstance(px, datetime.timedelta): 

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

1889 pieces.append(px) 

1890 # similar to pexpireat command 

1891 if exat is not None: 

1892 pieces.append("EXAT") 

1893 if isinstance(exat, datetime.datetime): 

1894 exat = int(exat.timestamp()) 

1895 pieces.append(exat) 

1896 if pxat is not None: 

1897 pieces.append("PXAT") 

1898 if isinstance(pxat, datetime.datetime): 

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

1900 pieces.append(pxat) 

1901 if persist: 

1902 pieces.append("PERSIST") 

1903 

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

1905 

1906 def __getitem__(self, name: KeyT): 

1907 """ 

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

1909 doesn't exist. 

1910 """ 

1911 value = self.get(name) 

1912 if value is not None: 

1913 return value 

1914 raise KeyError(name) 

1915 

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

1917 """ 

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

1919 

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

1921 """ 

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

1923 

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

1925 """ 

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

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

1928 

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

1930 """ 

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

1932 

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

1934 """ 

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

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

1937 

1938 As per Redis 6.2, GETSET is considered deprecated. 

1939 Please use SET with GET parameter in new code. 

1940 

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

1942 """ 

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

1944 

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

1946 """ 

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

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

1949 

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

1951 """ 

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

1953 

1954 incr = incrby 

1955 

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

1957 """ 

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

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

1960 

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

1962 """ 

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

1964 

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

1966 """ 

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

1968 

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

1970 """ 

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

1972 

1973 def lmove( 

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

1975 ) -> ResponseT: 

1976 """ 

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

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

1979 Returns the element being popped and pushed. 

1980 

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

1982 """ 

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

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

1985 

1986 def blmove( 

1987 self, 

1988 first_list: str, 

1989 second_list: str, 

1990 timeout: int, 

1991 src: str = "LEFT", 

1992 dest: str = "RIGHT", 

1993 ) -> ResponseT: 

1994 """ 

1995 Blocking version of lmove. 

1996 

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

1998 """ 

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

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

2001 

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

2003 """ 

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

2005 

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

2007 """ 

2008 from redis.client import EMPTY_RESPONSE 

2009 

2010 args = list_or_args(keys, args) 

2011 options = {} 

2012 if not args: 

2013 options[EMPTY_RESPONSE] = [] 

2014 options["keys"] = args 

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

2016 

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

2018 """ 

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

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

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

2022 

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

2024 """ 

2025 items = [] 

2026 for pair in mapping.items(): 

2027 items.extend(pair) 

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

2029 

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

2031 """ 

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

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

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

2035 Returns a boolean indicating if the operation was successful. 

2036 

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

2038 """ 

2039 items = [] 

2040 for pair in mapping.items(): 

2041 items.extend(pair) 

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

2043 

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

2045 """ 

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

2047 

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

2049 """ 

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

2051 

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

2053 """ 

2054 Removes an expiration on ``name`` 

2055 

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

2057 """ 

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

2059 

2060 def pexpire( 

2061 self, 

2062 name: KeyT, 

2063 time: ExpiryT, 

2064 nx: bool = False, 

2065 xx: bool = False, 

2066 gt: bool = False, 

2067 lt: bool = False, 

2068 ) -> ResponseT: 

2069 """ 

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

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

2072 integer or a Python timedelta object. 

2073 

2074 Valid options are: 

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

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

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

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

2079 

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

2081 """ 

2082 if isinstance(time, datetime.timedelta): 

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

2084 

2085 exp_option = list() 

2086 if nx: 

2087 exp_option.append("NX") 

2088 if xx: 

2089 exp_option.append("XX") 

2090 if gt: 

2091 exp_option.append("GT") 

2092 if lt: 

2093 exp_option.append("LT") 

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

2095 

2096 def pexpireat( 

2097 self, 

2098 name: KeyT, 

2099 when: AbsExpiryT, 

2100 nx: bool = False, 

2101 xx: bool = False, 

2102 gt: bool = False, 

2103 lt: bool = False, 

2104 ) -> ResponseT: 

2105 """ 

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

2107 can be represented as an integer representing unix time in 

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

2109 

2110 Valid options are: 

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

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

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

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

2115 

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

2117 """ 

2118 if isinstance(when, datetime.datetime): 

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

2120 exp_option = list() 

2121 if nx: 

2122 exp_option.append("NX") 

2123 if xx: 

2124 exp_option.append("XX") 

2125 if gt: 

2126 exp_option.append("GT") 

2127 if lt: 

2128 exp_option.append("LT") 

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

2130 

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

2132 """ 

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

2134 at which the given key will expire. 

2135 

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

2137 """ 

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

2139 

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

2141 """ 

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

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

2144 timedelta object 

2145 

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

2147 """ 

2148 if isinstance(time_ms, datetime.timedelta): 

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

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

2151 

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

2153 """ 

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

2155 

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

2157 """ 

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

2159 

2160 def hrandfield( 

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

2162 ) -> ResponseT: 

2163 """ 

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

2165 

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

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

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

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

2170 specified count. 

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

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

2173 

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

2175 """ 

2176 params = [] 

2177 if count is not None: 

2178 params.append(count) 

2179 if withvalues: 

2180 params.append("WITHVALUES") 

2181 

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

2183 

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

2185 """ 

2186 Returns the name of a random key 

2187 

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

2189 """ 

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

2191 

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

2193 """ 

2194 Rename key ``src`` to ``dst`` 

2195 

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

2197 """ 

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

2199 

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

2201 """ 

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

2203 

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

2205 """ 

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

2207 

2208 def restore( 

2209 self, 

2210 name: KeyT, 

2211 ttl: float, 

2212 value: EncodableT, 

2213 replace: bool = False, 

2214 absttl: bool = False, 

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

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

2217 ) -> ResponseT: 

2218 """ 

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

2220 using DUMP. 

2221 

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

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

2224 

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

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

2227 greater). 

2228 

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

2230 key must be idle, prior to execution. 

2231 

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

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

2234 

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

2236 """ 

2237 params = [name, ttl, value] 

2238 if replace: 

2239 params.append("REPLACE") 

2240 if absttl: 

2241 params.append("ABSTTL") 

2242 if idletime is not None: 

2243 params.append("IDLETIME") 

2244 try: 

2245 params.append(int(idletime)) 

2246 except ValueError: 

2247 raise DataError("idletimemust be an integer") 

2248 

2249 if frequency is not None: 

2250 params.append("FREQ") 

2251 try: 

2252 params.append(int(frequency)) 

2253 except ValueError: 

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

2255 

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

2257 

2258 def set( 

2259 self, 

2260 name: KeyT, 

2261 value: EncodableT, 

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

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

2264 nx: bool = False, 

2265 xx: bool = False, 

2266 keepttl: bool = False, 

2267 get: bool = False, 

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

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

2270 ) -> ResponseT: 

2271 """ 

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

2273 

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

2275 

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

2277 

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

2279 if it does not exist. 

2280 

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

2282 if it already exists. 

2283 

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

2285 (Available since Redis 6.0) 

2286 

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

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

2289 (Available since Redis 6.2) 

2290 

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

2292 specified in unix time. 

2293 

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

2295 specified in unix time. 

2296 

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

2298 """ 

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

2300 options = {} 

2301 if ex is not None: 

2302 pieces.append("EX") 

2303 if isinstance(ex, datetime.timedelta): 

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

2305 elif isinstance(ex, int): 

2306 pieces.append(ex) 

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

2308 pieces.append(int(ex)) 

2309 else: 

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

2311 if px is not None: 

2312 pieces.append("PX") 

2313 if isinstance(px, datetime.timedelta): 

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

2315 elif isinstance(px, int): 

2316 pieces.append(px) 

2317 else: 

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

2319 if exat is not None: 

2320 pieces.append("EXAT") 

2321 if isinstance(exat, datetime.datetime): 

2322 exat = int(exat.timestamp()) 

2323 pieces.append(exat) 

2324 if pxat is not None: 

2325 pieces.append("PXAT") 

2326 if isinstance(pxat, datetime.datetime): 

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

2328 pieces.append(pxat) 

2329 if keepttl: 

2330 pieces.append("KEEPTTL") 

2331 

2332 if nx: 

2333 pieces.append("NX") 

2334 if xx: 

2335 pieces.append("XX") 

2336 

2337 if get: 

2338 pieces.append("GET") 

2339 options["get"] = True 

2340 

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

2342 

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

2344 self.set(name, value) 

2345 

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

2347 """ 

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

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

2350 

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

2352 """ 

2353 value = value and 1 or 0 

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

2355 

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

2357 """ 

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

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

2360 timedelta object. 

2361 

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

2363 """ 

2364 if isinstance(time, datetime.timedelta): 

2365 time = int(time.total_seconds()) 

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

2367 

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

2369 """ 

2370 Set the value of key ``name`` to ``value`` if key doesn't exist 

2371 

2372 For more information see https://redis.io/commands/setnx 

2373 """ 

2374 return self.execute_command("SETNX", name, value) 

2375 

2376 def setrange(self, name: KeyT, offset: int, value: EncodableT) -> ResponseT: 

2377 """ 

2378 Overwrite bytes in the value of ``name`` starting at ``offset`` with 

2379 ``value``. If ``offset`` plus the length of ``value`` exceeds the 

2380 length of the original value, the new value will be larger than before. 

2381 If ``offset`` exceeds the length of the original value, null bytes 

2382 will be used to pad between the end of the previous value and the start 

2383 of what's being injected. 

2384 

2385 Returns the length of the new string. 

2386 

2387 For more information see https://redis.io/commands/setrange 

2388 """ 

2389 return self.execute_command("SETRANGE", name, offset, value) 

2390 

2391 def stralgo( 

2392 self, 

2393 algo: Literal["LCS"], 

2394 value1: KeyT, 

2395 value2: KeyT, 

2396 specific_argument: Union[Literal["strings"], Literal["keys"]] = "strings", 

2397 len: bool = False, 

2398 idx: bool = False, 

2399 minmatchlen: Union[int, None] = None, 

2400 withmatchlen: bool = False, 

2401 **kwargs, 

2402 ) -> ResponseT: 

2403 """ 

2404 Implements complex algorithms that operate on strings. 

2405 Right now the only algorithm implemented is the LCS algorithm 

2406 (longest common substring). However new algorithms could be 

2407 implemented in the future. 

2408 

2409 ``algo`` Right now must be LCS 

2410 ``value1`` and ``value2`` Can be two strings or two keys 

2411 ``specific_argument`` Specifying if the arguments to the algorithm 

2412 will be keys or strings. strings is the default. 

2413 ``len`` Returns just the len of the match. 

2414 ``idx`` Returns the match positions in each string. 

2415 ``minmatchlen`` Restrict the list of matches to the ones of a given 

2416 minimal length. Can be provided only when ``idx`` set to True. 

2417 ``withmatchlen`` Returns the matches with the len of the match. 

2418 Can be provided only when ``idx`` set to True. 

2419 

2420 For more information see https://redis.io/commands/stralgo 

2421 """ 

2422 # check validity 

2423 supported_algo = ["LCS"] 

2424 if algo not in supported_algo: 

2425 supported_algos_str = ", ".join(supported_algo) 

2426 raise DataError(f"The supported algorithms are: {supported_algos_str}") 

2427 if specific_argument not in ["keys", "strings"]: 

2428 raise DataError("specific_argument can be only keys or strings") 

2429 if len and idx: 

2430 raise DataError("len and idx cannot be provided together.") 

2431 

2432 pieces: list[EncodableT] = [algo, specific_argument.upper(), value1, value2] 

2433 if len: 

2434 pieces.append(b"LEN") 

2435 if idx: 

2436 pieces.append(b"IDX") 

2437 try: 

2438 int(minmatchlen) 

2439 pieces.extend([b"MINMATCHLEN", minmatchlen]) 

2440 except TypeError: 

2441 pass 

2442 if withmatchlen: 

2443 pieces.append(b"WITHMATCHLEN") 

2444 

2445 return self.execute_command( 

2446 "STRALGO", 

2447 *pieces, 

2448 len=len, 

2449 idx=idx, 

2450 minmatchlen=minmatchlen, 

2451 withmatchlen=withmatchlen, 

2452 **kwargs, 

2453 ) 

2454 

2455 def strlen(self, name: KeyT) -> ResponseT: 

2456 """ 

2457 Return the number of bytes stored in the value of ``name`` 

2458 

2459 For more information see https://redis.io/commands/strlen 

2460 """ 

2461 return self.execute_command("STRLEN", name, keys=[name]) 

2462 

2463 def substr(self, name: KeyT, start: int, end: int = -1) -> ResponseT: 

2464 """ 

2465 Return a substring of the string at key ``name``. ``start`` and ``end`` 

2466 are 0-based integers specifying the portion of the string to return. 

2467 """ 

2468 return self.execute_command("SUBSTR", name, start, end, keys=[name]) 

2469 

2470 def touch(self, *args: KeyT) -> ResponseT: 

2471 """ 

2472 Alters the last access time of a key(s) ``*args``. A key is ignored 

2473 if it does not exist. 

2474 

2475 For more information see https://redis.io/commands/touch 

2476 """ 

2477 return self.execute_command("TOUCH", *args) 

2478 

2479 def ttl(self, name: KeyT) -> ResponseT: 

2480 """ 

2481 Returns the number of seconds until the key ``name`` will expire 

2482 

2483 For more information see https://redis.io/commands/ttl 

2484 """ 

2485 return self.execute_command("TTL", name) 

2486 

2487 def type(self, name: KeyT) -> ResponseT: 

2488 """ 

2489 Returns the type of key ``name`` 

2490 

2491 For more information see https://redis.io/commands/type 

2492 """ 

2493 return self.execute_command("TYPE", name, keys=[name]) 

2494 

2495 def watch(self, *names: KeyT) -> None: 

2496 """ 

2497 Watches the values at keys ``names``, or None if the key doesn't exist 

2498 

2499 For more information see https://redis.io/commands/watch 

2500 """ 

2501 warnings.warn(DeprecationWarning("Call WATCH from a Pipeline object")) 

2502 

2503 def unwatch(self) -> None: 

2504 """ 

2505 Unwatches the value at key ``name``, or None of the key doesn't exist 

2506 

2507 For more information see https://redis.io/commands/unwatch 

2508 """ 

2509 warnings.warn(DeprecationWarning("Call UNWATCH from a Pipeline object")) 

2510 

2511 def unlink(self, *names: KeyT) -> ResponseT: 

2512 """ 

2513 Unlink one or more keys specified by ``names`` 

2514 

2515 For more information see https://redis.io/commands/unlink 

2516 """ 

2517 return self.execute_command("UNLINK", *names) 

2518 

2519 def lcs( 

2520 self, 

2521 key1: str, 

2522 key2: str, 

2523 len: Optional[bool] = False, 

2524 idx: Optional[bool] = False, 

2525 minmatchlen: Optional[int] = 0, 

2526 withmatchlen: Optional[bool] = False, 

2527 ) -> Union[str, int, list]: 

2528 """ 

2529 Find the longest common subsequence between ``key1`` and ``key2``. 

2530 If ``len`` is true the length of the match will will be returned. 

2531 If ``idx`` is true the match position in each strings will be returned. 

2532 ``minmatchlen`` restrict the list of matches to the ones of 

2533 the given ``minmatchlen``. 

2534 If ``withmatchlen`` the length of the match also will be returned. 

2535 For more information see https://redis.io/commands/lcs 

2536 """ 

2537 pieces = [key1, key2] 

2538 if len: 

2539 pieces.append("LEN") 

2540 if idx: 

2541 pieces.append("IDX") 

2542 if minmatchlen != 0: 

2543 pieces.extend(["MINMATCHLEN", minmatchlen]) 

2544 if withmatchlen: 

2545 pieces.append("WITHMATCHLEN") 

2546 return self.execute_command("LCS", *pieces, keys=[key1, key2]) 

2547 

2548 

2549class AsyncBasicKeyCommands(BasicKeyCommands): 

2550 def __delitem__(self, name: KeyT): 

2551 raise TypeError("Async Redis client does not support class deletion") 

2552 

2553 def __contains__(self, name: KeyT): 

2554 raise TypeError("Async Redis client does not support class inclusion") 

2555 

2556 def __getitem__(self, name: KeyT): 

2557 raise TypeError("Async Redis client does not support class retrieval") 

2558 

2559 def __setitem__(self, name: KeyT, value: EncodableT): 

2560 raise TypeError("Async Redis client does not support class assignment") 

2561 

2562 async def watch(self, *names: KeyT) -> None: 

2563 return super().watch(*names) 

2564 

2565 async def unwatch(self) -> None: 

2566 return super().unwatch() 

2567 

2568 

2569class ListCommands(CommandsProtocol): 

2570 """ 

2571 Redis commands for List data type. 

2572 see: https://redis.io/topics/data-types#lists 

2573 """ 

2574 

2575 def blpop( 

2576 self, keys: List, timeout: Optional[int] = 0 

2577 ) -> Union[Awaitable[list], list]: 

2578 """ 

2579 LPOP a value off of the first non-empty list 

2580 named in the ``keys`` list. 

2581 

2582 If none of the lists in ``keys`` has a value to LPOP, then block 

2583 for ``timeout`` seconds, or until a value gets pushed on to one 

2584 of the lists. 

2585 

2586 If timeout is 0, then block indefinitely. 

2587 

2588 For more information see https://redis.io/commands/blpop 

2589 """ 

2590 if timeout is None: 

2591 timeout = 0 

2592 keys = list_or_args(keys, None) 

2593 keys.append(timeout) 

2594 return self.execute_command("BLPOP", *keys) 

2595 

2596 def brpop( 

2597 self, keys: List, timeout: Optional[int] = 0 

2598 ) -> Union[Awaitable[list], list]: 

2599 """ 

2600 RPOP a value off of the first non-empty list 

2601 named in the ``keys`` list. 

2602 

2603 If none of the lists in ``keys`` has a value to RPOP, then block 

2604 for ``timeout`` seconds, or until a value gets pushed on to one 

2605 of the lists. 

2606 

2607 If timeout is 0, then block indefinitely. 

2608 

2609 For more information see https://redis.io/commands/brpop 

2610 """ 

2611 if timeout is None: 

2612 timeout = 0 

2613 keys = list_or_args(keys, None) 

2614 keys.append(timeout) 

2615 return self.execute_command("BRPOP", *keys) 

2616 

2617 def brpoplpush( 

2618 self, src: str, dst: str, timeout: Optional[int] = 0 

2619 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

2620 """ 

2621 Pop a value off the tail of ``src``, push it on the head of ``dst`` 

2622 and then return it. 

2623 

2624 This command blocks until a value is in ``src`` or until ``timeout`` 

2625 seconds elapse, whichever is first. A ``timeout`` value of 0 blocks 

2626 forever. 

2627 

2628 For more information see https://redis.io/commands/brpoplpush 

2629 """ 

2630 if timeout is None: 

2631 timeout = 0 

2632 return self.execute_command("BRPOPLPUSH", src, dst, timeout) 

2633 

2634 def blmpop( 

2635 self, 

2636 timeout: float, 

2637 numkeys: int, 

2638 *args: List[str], 

2639 direction: str, 

2640 count: Optional[int] = 1, 

2641 ) -> Optional[list]: 

2642 """ 

2643 Pop ``count`` values (default 1) from first non-empty in the list 

2644 of provided key names. 

2645 

2646 When all lists are empty this command blocks the connection until another 

2647 client pushes to it or until the timeout, timeout of 0 blocks indefinitely 

2648 

2649 For more information see https://redis.io/commands/blmpop 

2650 """ 

2651 args = [timeout, numkeys, *args, direction, "COUNT", count] 

2652 

2653 return self.execute_command("BLMPOP", *args) 

2654 

2655 def lmpop( 

2656 self, 

2657 num_keys: int, 

2658 *args: List[str], 

2659 direction: str, 

2660 count: Optional[int] = 1, 

2661 ) -> Union[Awaitable[list], list]: 

2662 """ 

2663 Pop ``count`` values (default 1) first non-empty list key from the list 

2664 of args provided key names. 

2665 

2666 For more information see https://redis.io/commands/lmpop 

2667 """ 

2668 args = [num_keys] + list(args) + [direction] 

2669 if count != 1: 

2670 args.extend(["COUNT", count]) 

2671 

2672 return self.execute_command("LMPOP", *args) 

2673 

2674 def lindex( 

2675 self, name: str, index: int 

2676 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

2677 """ 

2678 Return the item from list ``name`` at position ``index`` 

2679 

2680 Negative indexes are supported and will return an item at the 

2681 end of the list 

2682 

2683 For more information see https://redis.io/commands/lindex 

2684 """ 

2685 return self.execute_command("LINDEX", name, index, keys=[name]) 

2686 

2687 def linsert( 

2688 self, name: str, where: str, refvalue: str, value: str 

2689 ) -> Union[Awaitable[int], int]: 

2690 """ 

2691 Insert ``value`` in list ``name`` either immediately before or after 

2692 [``where``] ``refvalue`` 

2693 

2694 Returns the new length of the list on success or -1 if ``refvalue`` 

2695 is not in the list. 

2696 

2697 For more information see https://redis.io/commands/linsert 

2698 """ 

2699 return self.execute_command("LINSERT", name, where, refvalue, value) 

2700 

2701 def llen(self, name: str) -> Union[Awaitable[int], int]: 

2702 """ 

2703 Return the length of the list ``name`` 

2704 

2705 For more information see https://redis.io/commands/llen 

2706 """ 

2707 return self.execute_command("LLEN", name, keys=[name]) 

2708 

2709 def lpop( 

2710 self, 

2711 name: str, 

2712 count: Optional[int] = None, 

2713 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]: 

2714 """ 

2715 Removes and returns the first elements of the list ``name``. 

2716 

2717 By default, the command pops a single element from the beginning of 

2718 the list. When provided with the optional ``count`` argument, the reply 

2719 will consist of up to count elements, depending on the list's length. 

2720 

2721 For more information see https://redis.io/commands/lpop 

2722 """ 

2723 if count is not None: 

2724 return self.execute_command("LPOP", name, count) 

2725 else: 

2726 return self.execute_command("LPOP", name) 

2727 

2728 def lpush(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2729 """ 

2730 Push ``values`` onto the head of the list ``name`` 

2731 

2732 For more information see https://redis.io/commands/lpush 

2733 """ 

2734 return self.execute_command("LPUSH", name, *values) 

2735 

2736 def lpushx(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2737 """ 

2738 Push ``value`` onto the head of the list ``name`` if ``name`` exists 

2739 

2740 For more information see https://redis.io/commands/lpushx 

2741 """ 

2742 return self.execute_command("LPUSHX", name, *values) 

2743 

2744 def lrange(self, name: str, start: int, end: int) -> Union[Awaitable[list], list]: 

2745 """ 

2746 Return a slice of the list ``name`` between 

2747 position ``start`` and ``end`` 

2748 

2749 ``start`` and ``end`` can be negative numbers just like 

2750 Python slicing notation 

2751 

2752 For more information see https://redis.io/commands/lrange 

2753 """ 

2754 return self.execute_command("LRANGE", name, start, end, keys=[name]) 

2755 

2756 def lrem(self, name: str, count: int, value: str) -> Union[Awaitable[int], int]: 

2757 """ 

2758 Remove the first ``count`` occurrences of elements equal to ``value`` 

2759 from the list stored at ``name``. 

2760 

2761 The count argument influences the operation in the following ways: 

2762 count > 0: Remove elements equal to value moving from head to tail. 

2763 count < 0: Remove elements equal to value moving from tail to head. 

2764 count = 0: Remove all elements equal to value. 

2765 

2766 For more information see https://redis.io/commands/lrem 

2767 """ 

2768 return self.execute_command("LREM", name, count, value) 

2769 

2770 def lset(self, name: str, index: int, value: str) -> Union[Awaitable[str], str]: 

2771 """ 

2772 Set element at ``index`` of list ``name`` to ``value`` 

2773 

2774 For more information see https://redis.io/commands/lset 

2775 """ 

2776 return self.execute_command("LSET", name, index, value) 

2777 

2778 def ltrim(self, name: str, start: int, end: int) -> Union[Awaitable[str], str]: 

2779 """ 

2780 Trim the list ``name``, removing all values not within the slice 

2781 between ``start`` and ``end`` 

2782 

2783 ``start`` and ``end`` can be negative numbers just like 

2784 Python slicing notation 

2785 

2786 For more information see https://redis.io/commands/ltrim 

2787 """ 

2788 return self.execute_command("LTRIM", name, start, end) 

2789 

2790 def rpop( 

2791 self, 

2792 name: str, 

2793 count: Optional[int] = None, 

2794 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]: 

2795 """ 

2796 Removes and returns the last elements of the list ``name``. 

2797 

2798 By default, the command pops a single element from the end of the list. 

2799 When provided with the optional ``count`` argument, the reply will 

2800 consist of up to count elements, depending on the list's length. 

2801 

2802 For more information see https://redis.io/commands/rpop 

2803 """ 

2804 if count is not None: 

2805 return self.execute_command("RPOP", name, count) 

2806 else: 

2807 return self.execute_command("RPOP", name) 

2808 

2809 def rpoplpush(self, src: str, dst: str) -> Union[Awaitable[str], str]: 

2810 """ 

2811 RPOP a value off of the ``src`` list and atomically LPUSH it 

2812 on to the ``dst`` list. Returns the value. 

2813 

2814 For more information see https://redis.io/commands/rpoplpush 

2815 """ 

2816 return self.execute_command("RPOPLPUSH", src, dst) 

2817 

2818 def rpush(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2819 """ 

2820 Push ``values`` onto the tail of the list ``name`` 

2821 

2822 For more information see https://redis.io/commands/rpush 

2823 """ 

2824 return self.execute_command("RPUSH", name, *values) 

2825 

2826 def rpushx(self, name: str, *values: str) -> Union[Awaitable[int], int]: 

2827 """ 

2828 Push ``value`` onto the tail of the list ``name`` if ``name`` exists 

2829 

2830 For more information see https://redis.io/commands/rpushx 

2831 """ 

2832 return self.execute_command("RPUSHX", name, *values) 

2833 

2834 def lpos( 

2835 self, 

2836 name: str, 

2837 value: str, 

2838 rank: Optional[int] = None, 

2839 count: Optional[int] = None, 

2840 maxlen: Optional[int] = None, 

2841 ) -> Union[str, List, None]: 

2842 """ 

2843 Get position of ``value`` within the list ``name`` 

2844 

2845 If specified, ``rank`` indicates the "rank" of the first element to 

2846 return in case there are multiple copies of ``value`` in the list. 

2847 By default, LPOS returns the position of the first occurrence of 

2848 ``value`` in the list. When ``rank`` 2, LPOS returns the position of 

2849 the second ``value`` in the list. If ``rank`` is negative, LPOS 

2850 searches the list in reverse. For example, -1 would return the 

2851 position of the last occurrence of ``value`` and -2 would return the 

2852 position of the next to last occurrence of ``value``. 

2853 

2854 If specified, ``count`` indicates that LPOS should return a list of 

2855 up to ``count`` positions. A ``count`` of 2 would return a list of 

2856 up to 2 positions. A ``count`` of 0 returns a list of all positions 

2857 matching ``value``. When ``count`` is specified and but ``value`` 

2858 does not exist in the list, an empty list is returned. 

2859 

2860 If specified, ``maxlen`` indicates the maximum number of list 

2861 elements to scan. A ``maxlen`` of 1000 will only return the 

2862 position(s) of items within the first 1000 entries in the list. 

2863 A ``maxlen`` of 0 (the default) will scan the entire list. 

2864 

2865 For more information see https://redis.io/commands/lpos 

2866 """ 

2867 pieces: list[EncodableT] = [name, value] 

2868 if rank is not None: 

2869 pieces.extend(["RANK", rank]) 

2870 

2871 if count is not None: 

2872 pieces.extend(["COUNT", count]) 

2873 

2874 if maxlen is not None: 

2875 pieces.extend(["MAXLEN", maxlen]) 

2876 

2877 return self.execute_command("LPOS", *pieces, keys=[name]) 

2878 

2879 def sort( 

2880 self, 

2881 name: str, 

2882 start: Optional[int] = None, 

2883 num: Optional[int] = None, 

2884 by: Optional[str] = None, 

2885 get: Optional[List[str]] = None, 

2886 desc: bool = False, 

2887 alpha: bool = False, 

2888 store: Optional[str] = None, 

2889 groups: Optional[bool] = False, 

2890 ) -> Union[List, int]: 

2891 """ 

2892 Sort and return the list, set or sorted set at ``name``. 

2893 

2894 ``start`` and ``num`` allow for paging through the sorted data 

2895 

2896 ``by`` allows using an external key to weight and sort the items. 

2897 Use an "*" to indicate where in the key the item value is located 

2898 

2899 ``get`` allows for returning items from external keys rather than the 

2900 sorted data itself. Use an "*" to indicate where in the key 

2901 the item value is located 

2902 

2903 ``desc`` allows for reversing the sort 

2904 

2905 ``alpha`` allows for sorting lexicographically rather than numerically 

2906 

2907 ``store`` allows for storing the result of the sort into 

2908 the key ``store`` 

2909 

2910 ``groups`` if set to True and if ``get`` contains at least two 

2911 elements, sort will return a list of tuples, each containing the 

2912 values fetched from the arguments to ``get``. 

2913 

2914 For more information see https://redis.io/commands/sort 

2915 """ 

2916 if (start is not None and num is None) or (num is not None and start is None): 

2917 raise DataError("``start`` and ``num`` must both be specified") 

2918 

2919 pieces: list[EncodableT] = [name] 

2920 if by is not None: 

2921 pieces.extend([b"BY", by]) 

2922 if start is not None and num is not None: 

2923 pieces.extend([b"LIMIT", start, num]) 

2924 if get is not None: 

2925 # If get is a string assume we want to get a single value. 

2926 # Otherwise assume it's an interable and we want to get multiple 

2927 # values. We can't just iterate blindly because strings are 

2928 # iterable. 

2929 if isinstance(get, (bytes, str)): 

2930 pieces.extend([b"GET", get]) 

2931 else: 

2932 for g in get: 

2933 pieces.extend([b"GET", g]) 

2934 if desc: 

2935 pieces.append(b"DESC") 

2936 if alpha: 

2937 pieces.append(b"ALPHA") 

2938 if store is not None: 

2939 pieces.extend([b"STORE", store]) 

2940 if groups: 

2941 if not get or isinstance(get, (bytes, str)) or len(get) < 2: 

2942 raise DataError( 

2943 'when using "groups" the "get" argument ' 

2944 "must be specified and contain at least " 

2945 "two keys" 

2946 ) 

2947 

2948 options = {"groups": len(get) if groups else None} 

2949 options["keys"] = [name] 

2950 return self.execute_command("SORT", *pieces, **options) 

2951 

2952 def sort_ro( 

2953 self, 

2954 key: str, 

2955 start: Optional[int] = None, 

2956 num: Optional[int] = None, 

2957 by: Optional[str] = None, 

2958 get: Optional[List[str]] = None, 

2959 desc: bool = False, 

2960 alpha: bool = False, 

2961 ) -> list: 

2962 """ 

2963 Returns the elements contained in the list, set or sorted set at key. 

2964 (read-only variant of the SORT command) 

2965 

2966 ``start`` and ``num`` allow for paging through the sorted data 

2967 

2968 ``by`` allows using an external key to weight and sort the items. 

2969 Use an "*" to indicate where in the key the item value is located 

2970 

2971 ``get`` allows for returning items from external keys rather than the 

2972 sorted data itself. Use an "*" to indicate where in the key 

2973 the item value is located 

2974 

2975 ``desc`` allows for reversing the sort 

2976 

2977 ``alpha`` allows for sorting lexicographically rather than numerically 

2978 

2979 For more information see https://redis.io/commands/sort_ro 

2980 """ 

2981 return self.sort( 

2982 key, start=start, num=num, by=by, get=get, desc=desc, alpha=alpha 

2983 ) 

2984 

2985 

2986AsyncListCommands = ListCommands 

2987 

2988 

2989class ScanCommands(CommandsProtocol): 

2990 """ 

2991 Redis SCAN commands. 

2992 see: https://redis.io/commands/scan 

2993 """ 

2994 

2995 def scan( 

2996 self, 

2997 cursor: int = 0, 

2998 match: Union[PatternT, None] = None, 

2999 count: Union[int, None] = None, 

3000 _type: Union[str, None] = None, 

3001 **kwargs, 

3002 ) -> ResponseT: 

3003 """ 

3004 Incrementally return lists of key names. Also return a cursor 

3005 indicating the scan position. 

3006 

3007 ``match`` allows for filtering the keys by pattern 

3008 

3009 ``count`` provides a hint to Redis about the number of keys to 

3010 return per batch. 

3011 

3012 ``_type`` filters the returned values by a particular Redis type. 

3013 Stock Redis instances allow for the following types: 

3014 HASH, LIST, SET, STREAM, STRING, ZSET 

3015 Additionally, Redis modules can expose other types as well. 

3016 

3017 For more information see https://redis.io/commands/scan 

3018 """ 

3019 pieces: list[EncodableT] = [cursor] 

3020 if match is not None: 

3021 pieces.extend([b"MATCH", match]) 

3022 if count is not None: 

3023 pieces.extend([b"COUNT", count]) 

3024 if _type is not None: 

3025 pieces.extend([b"TYPE", _type]) 

3026 return self.execute_command("SCAN", *pieces, **kwargs) 

3027 

3028 def scan_iter( 

3029 self, 

3030 match: Union[PatternT, None] = None, 

3031 count: Union[int, None] = None, 

3032 _type: Union[str, None] = None, 

3033 **kwargs, 

3034 ) -> Iterator: 

3035 """ 

3036 Make an iterator using the SCAN command so that the client doesn't 

3037 need to remember the cursor position. 

3038 

3039 ``match`` allows for filtering the keys by pattern 

3040 

3041 ``count`` provides a hint to Redis about the number of keys to 

3042 return per batch. 

3043 

3044 ``_type`` filters the returned values by a particular Redis type. 

3045 Stock Redis instances allow for the following types: 

3046 HASH, LIST, SET, STREAM, STRING, ZSET 

3047 Additionally, Redis modules can expose other types as well. 

3048 """ 

3049 cursor = "0" 

3050 while cursor != 0: 

3051 cursor, data = self.scan( 

3052 cursor=cursor, match=match, count=count, _type=_type, **kwargs 

3053 ) 

3054 yield from data 

3055 

3056 def sscan( 

3057 self, 

3058 name: KeyT, 

3059 cursor: int = 0, 

3060 match: Union[PatternT, None] = None, 

3061 count: Union[int, None] = None, 

3062 ) -> ResponseT: 

3063 """ 

3064 Incrementally return lists of elements in a set. Also return a cursor 

3065 indicating the scan position. 

3066 

3067 ``match`` allows for filtering the keys by pattern 

3068 

3069 ``count`` allows for hint the minimum number of returns 

3070 

3071 For more information see https://redis.io/commands/sscan 

3072 """ 

3073 pieces: list[EncodableT] = [name, cursor] 

3074 if match is not None: 

3075 pieces.extend([b"MATCH", match]) 

3076 if count is not None: 

3077 pieces.extend([b"COUNT", count]) 

3078 return self.execute_command("SSCAN", *pieces) 

3079 

3080 def sscan_iter( 

3081 self, 

3082 name: KeyT, 

3083 match: Union[PatternT, None] = None, 

3084 count: Union[int, None] = None, 

3085 ) -> Iterator: 

3086 """ 

3087 Make an iterator using the SSCAN command so that the client doesn't 

3088 need to remember the cursor position. 

3089 

3090 ``match`` allows for filtering the keys by pattern 

3091 

3092 ``count`` allows for hint the minimum number of returns 

3093 """ 

3094 cursor = "0" 

3095 while cursor != 0: 

3096 cursor, data = self.sscan(name, cursor=cursor, match=match, count=count) 

3097 yield from data 

3098 

3099 def hscan( 

3100 self, 

3101 name: KeyT, 

3102 cursor: int = 0, 

3103 match: Union[PatternT, None] = None, 

3104 count: Union[int, None] = None, 

3105 ) -> ResponseT: 

3106 """ 

3107 Incrementally return key/value slices in a hash. Also return a cursor 

3108 indicating the scan position. 

3109 

3110 ``match`` allows for filtering the keys by pattern 

3111 

3112 ``count`` allows for hint the minimum number of returns 

3113 

3114 For more information see https://redis.io/commands/hscan 

3115 """ 

3116 pieces: list[EncodableT] = [name, cursor] 

3117 if match is not None: 

3118 pieces.extend([b"MATCH", match]) 

3119 if count is not None: 

3120 pieces.extend([b"COUNT", count]) 

3121 return self.execute_command("HSCAN", *pieces) 

3122 

3123 def hscan_iter( 

3124 self, 

3125 name: str, 

3126 match: Union[PatternT, None] = None, 

3127 count: Union[int, None] = None, 

3128 ) -> Iterator: 

3129 """ 

3130 Make an iterator using the HSCAN command so that the client doesn't 

3131 need to remember the cursor position. 

3132 

3133 ``match`` allows for filtering the keys by pattern 

3134 

3135 ``count`` allows for hint the minimum number of returns 

3136 """ 

3137 cursor = "0" 

3138 while cursor != 0: 

3139 cursor, data = self.hscan(name, cursor=cursor, match=match, count=count) 

3140 yield from data.items() 

3141 

3142 def zscan( 

3143 self, 

3144 name: KeyT, 

3145 cursor: int = 0, 

3146 match: Union[PatternT, None] = None, 

3147 count: Union[int, None] = None, 

3148 score_cast_func: Union[type, Callable] = float, 

3149 ) -> ResponseT: 

3150 """ 

3151 Incrementally return lists of elements in a sorted set. Also return a 

3152 cursor indicating the scan position. 

3153 

3154 ``match`` allows for filtering the keys by pattern 

3155 

3156 ``count`` allows for hint the minimum number of returns 

3157 

3158 ``score_cast_func`` a callable used to cast the score return value 

3159 

3160 For more information see https://redis.io/commands/zscan 

3161 """ 

3162 pieces = [name, cursor] 

3163 if match is not None: 

3164 pieces.extend([b"MATCH", match]) 

3165 if count is not None: 

3166 pieces.extend([b"COUNT", count]) 

3167 options = {"score_cast_func": score_cast_func} 

3168 return self.execute_command("ZSCAN", *pieces, **options) 

3169 

3170 def zscan_iter( 

3171 self, 

3172 name: KeyT, 

3173 match: Union[PatternT, None] = None, 

3174 count: Union[int, None] = None, 

3175 score_cast_func: Union[type, Callable] = float, 

3176 ) -> Iterator: 

3177 """ 

3178 Make an iterator using the ZSCAN command so that the client doesn't 

3179 need to remember the cursor position. 

3180 

3181 ``match`` allows for filtering the keys by pattern 

3182 

3183 ``count`` allows for hint the minimum number of returns 

3184 

3185 ``score_cast_func`` a callable used to cast the score return value 

3186 """ 

3187 cursor = "0" 

3188 while cursor != 0: 

3189 cursor, data = self.zscan( 

3190 name, 

3191 cursor=cursor, 

3192 match=match, 

3193 count=count, 

3194 score_cast_func=score_cast_func, 

3195 ) 

3196 yield from data 

3197 

3198 

3199class AsyncScanCommands(ScanCommands): 

3200 async def scan_iter( 

3201 self, 

3202 match: Union[PatternT, None] = None, 

3203 count: Union[int, None] = None, 

3204 _type: Union[str, None] = None, 

3205 **kwargs, 

3206 ) -> AsyncIterator: 

3207 """ 

3208 Make an iterator using the SCAN command so that the client doesn't 

3209 need to remember the cursor position. 

3210 

3211 ``match`` allows for filtering the keys by pattern 

3212 

3213 ``count`` provides a hint to Redis about the number of keys to 

3214 return per batch. 

3215 

3216 ``_type`` filters the returned values by a particular Redis type. 

3217 Stock Redis instances allow for the following types: 

3218 HASH, LIST, SET, STREAM, STRING, ZSET 

3219 Additionally, Redis modules can expose other types as well. 

3220 """ 

3221 cursor = "0" 

3222 while cursor != 0: 

3223 cursor, data = await self.scan( 

3224 cursor=cursor, match=match, count=count, _type=_type, **kwargs 

3225 ) 

3226 for d in data: 

3227 yield d 

3228 

3229 async def sscan_iter( 

3230 self, 

3231 name: KeyT, 

3232 match: Union[PatternT, None] = None, 

3233 count: Union[int, None] = None, 

3234 ) -> AsyncIterator: 

3235 """ 

3236 Make an iterator using the SSCAN command so that the client doesn't 

3237 need to remember the cursor position. 

3238 

3239 ``match`` allows for filtering the keys by pattern 

3240 

3241 ``count`` allows for hint the minimum number of returns 

3242 """ 

3243 cursor = "0" 

3244 while cursor != 0: 

3245 cursor, data = await self.sscan( 

3246 name, cursor=cursor, match=match, count=count 

3247 ) 

3248 for d in data: 

3249 yield d 

3250 

3251 async def hscan_iter( 

3252 self, 

3253 name: str, 

3254 match: Union[PatternT, None] = None, 

3255 count: Union[int, None] = None, 

3256 ) -> AsyncIterator: 

3257 """ 

3258 Make an iterator using the HSCAN command so that the client doesn't 

3259 need to remember the cursor position. 

3260 

3261 ``match`` allows for filtering the keys by pattern 

3262 

3263 ``count`` allows for hint the minimum number of returns 

3264 """ 

3265 cursor = "0" 

3266 while cursor != 0: 

3267 cursor, data = await self.hscan( 

3268 name, cursor=cursor, match=match, count=count 

3269 ) 

3270 for it in data.items(): 

3271 yield it 

3272 

3273 async def zscan_iter( 

3274 self, 

3275 name: KeyT, 

3276 match: Union[PatternT, None] = None, 

3277 count: Union[int, None] = None, 

3278 score_cast_func: Union[type, Callable] = float, 

3279 ) -> AsyncIterator: 

3280 """ 

3281 Make an iterator using the ZSCAN command so that the client doesn't 

3282 need to remember the cursor position. 

3283 

3284 ``match`` allows for filtering the keys by pattern 

3285 

3286 ``count`` allows for hint the minimum number of returns 

3287 

3288 ``score_cast_func`` a callable used to cast the score return value 

3289 """ 

3290 cursor = "0" 

3291 while cursor != 0: 

3292 cursor, data = await self.zscan( 

3293 name, 

3294 cursor=cursor, 

3295 match=match, 

3296 count=count, 

3297 score_cast_func=score_cast_func, 

3298 ) 

3299 for d in data: 

3300 yield d 

3301 

3302 

3303class SetCommands(CommandsProtocol): 

3304 """ 

3305 Redis commands for Set data type. 

3306 see: https://redis.io/topics/data-types#sets 

3307 """ 

3308 

3309 def sadd(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

3310 """ 

3311 Add ``value(s)`` to set ``name`` 

3312 

3313 For more information see https://redis.io/commands/sadd 

3314 """ 

3315 return self.execute_command("SADD", name, *values) 

3316 

3317 def scard(self, name: str) -> Union[Awaitable[int], int]: 

3318 """ 

3319 Return the number of elements in set ``name`` 

3320 

3321 For more information see https://redis.io/commands/scard 

3322 """ 

3323 return self.execute_command("SCARD", name, keys=[name]) 

3324 

3325 def sdiff(self, keys: List, *args: List) -> Union[Awaitable[list], list]: 

3326 """ 

3327 Return the difference of sets specified by ``keys`` 

3328 

3329 For more information see https://redis.io/commands/sdiff 

3330 """ 

3331 args = list_or_args(keys, args) 

3332 return self.execute_command("SDIFF", *args, keys=args) 

3333 

3334 def sdiffstore( 

3335 self, dest: str, keys: List, *args: List 

3336 ) -> Union[Awaitable[int], int]: 

3337 """ 

3338 Store the difference of sets specified by ``keys`` into a new 

3339 set named ``dest``. Returns the number of keys in the new set. 

3340 

3341 For more information see https://redis.io/commands/sdiffstore 

3342 """ 

3343 args = list_or_args(keys, args) 

3344 return self.execute_command("SDIFFSTORE", dest, *args) 

3345 

3346 def sinter(self, keys: List, *args: List) -> Union[Awaitable[list], list]: 

3347 """ 

3348 Return the intersection of sets specified by ``keys`` 

3349 

3350 For more information see https://redis.io/commands/sinter 

3351 """ 

3352 args = list_or_args(keys, args) 

3353 return self.execute_command("SINTER", *args, keys=args) 

3354 

3355 def sintercard( 

3356 self, numkeys: int, keys: List[str], limit: int = 0 

3357 ) -> Union[Awaitable[int], int]: 

3358 """ 

3359 Return the cardinality of the intersect of multiple sets specified by ``keys`. 

3360 

3361 When LIMIT provided (defaults to 0 and means unlimited), if the intersection 

3362 cardinality reaches limit partway through the computation, the algorithm will 

3363 exit and yield limit as the cardinality 

3364 

3365 For more information see https://redis.io/commands/sintercard 

3366 """ 

3367 args = [numkeys, *keys, "LIMIT", limit] 

3368 return self.execute_command("SINTERCARD", *args, keys=keys) 

3369 

3370 def sinterstore( 

3371 self, dest: str, keys: List, *args: List 

3372 ) -> Union[Awaitable[int], int]: 

3373 """ 

3374 Store the intersection of sets specified by ``keys`` into a new 

3375 set named ``dest``. Returns the number of keys in the new set. 

3376 

3377 For more information see https://redis.io/commands/sinterstore 

3378 """ 

3379 args = list_or_args(keys, args) 

3380 return self.execute_command("SINTERSTORE", dest, *args) 

3381 

3382 def sismember( 

3383 self, name: str, value: str 

3384 ) -> Union[Awaitable[Union[Literal[0], Literal[1]]], Union[Literal[0], Literal[1]]]: 

3385 """ 

3386 Return whether ``value`` is a member of set ``name``: 

3387 - 1 if the value is a member of the set. 

3388 - 0 if the value is not a member of the set or if key does not exist. 

3389 

3390 For more information see https://redis.io/commands/sismember 

3391 """ 

3392 return self.execute_command("SISMEMBER", name, value, keys=[name]) 

3393 

3394 def smembers(self, name: str) -> Union[Awaitable[Set], Set]: 

3395 """ 

3396 Return all members of the set ``name`` 

3397 

3398 For more information see https://redis.io/commands/smembers 

3399 """ 

3400 return self.execute_command("SMEMBERS", name, keys=[name]) 

3401 

3402 def smismember(self, name: str, values: List, *args: List) -> Union[ 

3403 Awaitable[List[Union[Literal[0], Literal[1]]]], 

3404 List[Union[Literal[0], Literal[1]]], 

3405 ]: 

3406 """ 

3407 Return whether each value in ``values`` is a member of the set ``name`` 

3408 as a list of ``int`` in the order of ``values``: 

3409 - 1 if the value is a member of the set. 

3410 - 0 if the value is not a member of the set or if key does not exist. 

3411 

3412 For more information see https://redis.io/commands/smismember 

3413 """ 

3414 args = list_or_args(values, args) 

3415 return self.execute_command("SMISMEMBER", name, *args, keys=[name]) 

3416 

3417 def smove(self, src: str, dst: str, value: str) -> Union[Awaitable[bool], bool]: 

3418 """ 

3419 Move ``value`` from set ``src`` to set ``dst`` atomically 

3420 

3421 For more information see https://redis.io/commands/smove 

3422 """ 

3423 return self.execute_command("SMOVE", src, dst, value) 

3424 

3425 def spop(self, name: str, count: Optional[int] = None) -> Union[str, List, None]: 

3426 """ 

3427 Remove and return a random member of set ``name`` 

3428 

3429 For more information see https://redis.io/commands/spop 

3430 """ 

3431 args = (count is not None) and [count] or [] 

3432 return self.execute_command("SPOP", name, *args) 

3433 

3434 def srandmember( 

3435 self, name: str, number: Optional[int] = None 

3436 ) -> Union[str, List, None]: 

3437 """ 

3438 If ``number`` is None, returns a random member of set ``name``. 

3439 

3440 If ``number`` is supplied, returns a list of ``number`` random 

3441 members of set ``name``. Note this is only available when running 

3442 Redis 2.6+. 

3443 

3444 For more information see https://redis.io/commands/srandmember 

3445 """ 

3446 args = (number is not None) and [number] or [] 

3447 return self.execute_command("SRANDMEMBER", name, *args) 

3448 

3449 def srem(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

3450 """ 

3451 Remove ``values`` from set ``name`` 

3452 

3453 For more information see https://redis.io/commands/srem 

3454 """ 

3455 return self.execute_command("SREM", name, *values) 

3456 

3457 def sunion(self, keys: List, *args: List) -> Union[Awaitable[List], List]: 

3458 """ 

3459 Return the union of sets specified by ``keys`` 

3460 

3461 For more information see https://redis.io/commands/sunion 

3462 """ 

3463 args = list_or_args(keys, args) 

3464 return self.execute_command("SUNION", *args, keys=args) 

3465 

3466 def sunionstore( 

3467 self, dest: str, keys: List, *args: List 

3468 ) -> Union[Awaitable[int], int]: 

3469 """ 

3470 Store the union of sets specified by ``keys`` into a new 

3471 set named ``dest``. Returns the number of keys in the new set. 

3472 

3473 For more information see https://redis.io/commands/sunionstore 

3474 """ 

3475 args = list_or_args(keys, args) 

3476 return self.execute_command("SUNIONSTORE", dest, *args) 

3477 

3478 

3479AsyncSetCommands = SetCommands 

3480 

3481 

3482class StreamCommands(CommandsProtocol): 

3483 """ 

3484 Redis commands for Stream data type. 

3485 see: https://redis.io/topics/streams-intro 

3486 """ 

3487 

3488 def xack(self, name: KeyT, groupname: GroupT, *ids: StreamIdT) -> ResponseT: 

3489 """ 

3490 Acknowledges the successful processing of one or more messages. 

3491 name: name of the stream. 

3492 groupname: name of the consumer group. 

3493 *ids: message ids to acknowledge. 

3494 

3495 For more information see https://redis.io/commands/xack 

3496 """ 

3497 return self.execute_command("XACK", name, groupname, *ids) 

3498 

3499 def xadd( 

3500 self, 

3501 name: KeyT, 

3502 fields: Dict[FieldT, EncodableT], 

3503 id: StreamIdT = "*", 

3504 maxlen: Union[int, None] = None, 

3505 approximate: bool = True, 

3506 nomkstream: bool = False, 

3507 minid: Union[StreamIdT, None] = None, 

3508 limit: Union[int, None] = None, 

3509 ) -> ResponseT: 

3510 """ 

3511 Add to a stream. 

3512 name: name of the stream 

3513 fields: dict of field/value pairs to insert into the stream 

3514 id: Location to insert this record. By default it is appended. 

3515 maxlen: truncate old stream members beyond this size. 

3516 Can't be specified with minid. 

3517 approximate: actual stream length may be slightly more than maxlen 

3518 nomkstream: When set to true, do not make a stream 

3519 minid: the minimum id in the stream to query. 

3520 Can't be specified with maxlen. 

3521 limit: specifies the maximum number of entries to retrieve 

3522 

3523 For more information see https://redis.io/commands/xadd 

3524 """ 

3525 pieces: list[EncodableT] = [] 

3526 if maxlen is not None and minid is not None: 

3527 raise DataError("Only one of ```maxlen``` or ```minid``` may be specified") 

3528 

3529 if maxlen is not None: 

3530 if not isinstance(maxlen, int) or maxlen < 0: 

3531 raise DataError("XADD maxlen must be non-negative integer") 

3532 pieces.append(b"MAXLEN") 

3533 if approximate: 

3534 pieces.append(b"~") 

3535 pieces.append(str(maxlen)) 

3536 if minid is not None: 

3537 pieces.append(b"MINID") 

3538 if approximate: 

3539 pieces.append(b"~") 

3540 pieces.append(minid) 

3541 if limit is not None: 

3542 pieces.extend([b"LIMIT", limit]) 

3543 if nomkstream: 

3544 pieces.append(b"NOMKSTREAM") 

3545 pieces.append(id) 

3546 if not isinstance(fields, dict) or len(fields) == 0: 

3547 raise DataError("XADD fields must be a non-empty dict") 

3548 for pair in fields.items(): 

3549 pieces.extend(pair) 

3550 return self.execute_command("XADD", name, *pieces) 

3551 

3552 def xautoclaim( 

3553 self, 

3554 name: KeyT, 

3555 groupname: GroupT, 

3556 consumername: ConsumerT, 

3557 min_idle_time: int, 

3558 start_id: StreamIdT = "0-0", 

3559 count: Union[int, None] = None, 

3560 justid: bool = False, 

3561 ) -> ResponseT: 

3562 """ 

3563 Transfers ownership of pending stream entries that match the specified 

3564 criteria. Conceptually, equivalent to calling XPENDING and then XCLAIM, 

3565 but provides a more straightforward way to deal with message delivery 

3566 failures via SCAN-like semantics. 

3567 name: name of the stream. 

3568 groupname: name of the consumer group. 

3569 consumername: name of a consumer that claims the message. 

3570 min_idle_time: filter messages that were idle less than this amount of 

3571 milliseconds. 

3572 start_id: filter messages with equal or greater ID. 

3573 count: optional integer, upper limit of the number of entries that the 

3574 command attempts to claim. Set to 100 by default. 

3575 justid: optional boolean, false by default. Return just an array of IDs 

3576 of messages successfully claimed, without returning the actual message 

3577 

3578 For more information see https://redis.io/commands/xautoclaim 

3579 """ 

3580 try: 

3581 if int(min_idle_time) < 0: 

3582 raise DataError( 

3583 "XAUTOCLAIM min_idle_time must be a nonnegative integer" 

3584 ) 

3585 except TypeError: 

3586 pass 

3587 

3588 kwargs = {} 

3589 pieces = [name, groupname, consumername, min_idle_time, start_id] 

3590 

3591 try: 

3592 if int(count) < 0: 

3593 raise DataError("XPENDING count must be a integer >= 0") 

3594 pieces.extend([b"COUNT", count]) 

3595 except TypeError: 

3596 pass 

3597 if justid: 

3598 pieces.append(b"JUSTID") 

3599 kwargs["parse_justid"] = True 

3600 

3601 return self.execute_command("XAUTOCLAIM", *pieces, **kwargs) 

3602 

3603 def xclaim( 

3604 self, 

3605 name: KeyT, 

3606 groupname: GroupT, 

3607 consumername: ConsumerT, 

3608 min_idle_time: int, 

3609 message_ids: Union[List[StreamIdT], Tuple[StreamIdT]], 

3610 idle: Union[int, None] = None, 

3611 time: Union[int, None] = None, 

3612 retrycount: Union[int, None] = None, 

3613 force: bool = False, 

3614 justid: bool = False, 

3615 ) -> ResponseT: 

3616 """ 

3617 Changes the ownership of a pending message. 

3618 

3619 name: name of the stream. 

3620 

3621 groupname: name of the consumer group. 

3622 

3623 consumername: name of a consumer that claims the message. 

3624 

3625 min_idle_time: filter messages that were idle less than this amount of 

3626 milliseconds 

3627 

3628 message_ids: non-empty list or tuple of message IDs to claim 

3629 

3630 idle: optional. Set the idle time (last time it was delivered) of the 

3631 message in ms 

3632 

3633 time: optional integer. This is the same as idle but instead of a 

3634 relative amount of milliseconds, it sets the idle time to a specific 

3635 Unix time (in milliseconds). 

3636 

3637 retrycount: optional integer. set the retry counter to the specified 

3638 value. This counter is incremented every time a message is delivered 

3639 again. 

3640 

3641 force: optional boolean, false by default. Creates the pending message 

3642 entry in the PEL even if certain specified IDs are not already in the 

3643 PEL assigned to a different client. 

3644 

3645 justid: optional boolean, false by default. Return just an array of IDs 

3646 of messages successfully claimed, without returning the actual message 

3647 

3648 For more information see https://redis.io/commands/xclaim 

3649 """ 

3650 if not isinstance(min_idle_time, int) or min_idle_time < 0: 

3651 raise DataError("XCLAIM min_idle_time must be a non negative integer") 

3652 if not isinstance(message_ids, (list, tuple)) or not message_ids: 

3653 raise DataError( 

3654 "XCLAIM message_ids must be a non empty list or " 

3655 "tuple of message IDs to claim" 

3656 ) 

3657 

3658 kwargs = {} 

3659 pieces: list[EncodableT] = [name, groupname, consumername, str(min_idle_time)] 

3660 pieces.extend(list(message_ids)) 

3661 

3662 if idle is not None: 

3663 if not isinstance(idle, int): 

3664 raise DataError("XCLAIM idle must be an integer") 

3665 pieces.extend((b"IDLE", str(idle))) 

3666 if time is not None: 

3667 if not isinstance(time, int): 

3668 raise DataError("XCLAIM time must be an integer") 

3669 pieces.extend((b"TIME", str(time))) 

3670 if retrycount is not None: 

3671 if not isinstance(retrycount, int): 

3672 raise DataError("XCLAIM retrycount must be an integer") 

3673 pieces.extend((b"RETRYCOUNT", str(retrycount))) 

3674 

3675 if force: 

3676 if not isinstance(force, bool): 

3677 raise DataError("XCLAIM force must be a boolean") 

3678 pieces.append(b"FORCE") 

3679 if justid: 

3680 if not isinstance(justid, bool): 

3681 raise DataError("XCLAIM justid must be a boolean") 

3682 pieces.append(b"JUSTID") 

3683 kwargs["parse_justid"] = True 

3684 return self.execute_command("XCLAIM", *pieces, **kwargs) 

3685 

3686 def xdel(self, name: KeyT, *ids: StreamIdT) -> ResponseT: 

3687 """ 

3688 Deletes one or more messages from a stream. 

3689 name: name of the stream. 

3690 *ids: message ids to delete. 

3691 

3692 For more information see https://redis.io/commands/xdel 

3693 """ 

3694 return self.execute_command("XDEL", name, *ids) 

3695 

3696 def xgroup_create( 

3697 self, 

3698 name: KeyT, 

3699 groupname: GroupT, 

3700 id: StreamIdT = "$", 

3701 mkstream: bool = False, 

3702 entries_read: Optional[int] = None, 

3703 ) -> ResponseT: 

3704 """ 

3705 Create a new consumer group associated with a stream. 

3706 name: name of the stream. 

3707 groupname: name of the consumer group. 

3708 id: ID of the last item in the stream to consider already delivered. 

3709 

3710 For more information see https://redis.io/commands/xgroup-create 

3711 """ 

3712 pieces: list[EncodableT] = ["XGROUP CREATE", name, groupname, id] 

3713 if mkstream: 

3714 pieces.append(b"MKSTREAM") 

3715 if entries_read is not None: 

3716 pieces.extend(["ENTRIESREAD", entries_read]) 

3717 

3718 return self.execute_command(*pieces) 

3719 

3720 def xgroup_delconsumer( 

3721 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

3722 ) -> ResponseT: 

3723 """ 

3724 Remove a specific consumer from a consumer group. 

3725 Returns the number of pending messages that the consumer had before it 

3726 was deleted. 

3727 name: name of the stream. 

3728 groupname: name of the consumer group. 

3729 consumername: name of consumer to delete 

3730 

3731 For more information see https://redis.io/commands/xgroup-delconsumer 

3732 """ 

3733 return self.execute_command("XGROUP DELCONSUMER", name, groupname, consumername) 

3734 

3735 def xgroup_destroy(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3736 """ 

3737 Destroy a consumer group. 

3738 name: name of the stream. 

3739 groupname: name of the consumer group. 

3740 

3741 For more information see https://redis.io/commands/xgroup-destroy 

3742 """ 

3743 return self.execute_command("XGROUP DESTROY", name, groupname) 

3744 

3745 def xgroup_createconsumer( 

3746 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

3747 ) -> ResponseT: 

3748 """ 

3749 Consumers in a consumer group are auto-created every time a new 

3750 consumer name is mentioned by some command. 

3751 They can be explicitly created by using this command. 

3752 name: name of the stream. 

3753 groupname: name of the consumer group. 

3754 consumername: name of consumer to create. 

3755 

3756 See: https://redis.io/commands/xgroup-createconsumer 

3757 """ 

3758 return self.execute_command( 

3759 "XGROUP CREATECONSUMER", name, groupname, consumername 

3760 ) 

3761 

3762 def xgroup_setid( 

3763 self, 

3764 name: KeyT, 

3765 groupname: GroupT, 

3766 id: StreamIdT, 

3767 entries_read: Optional[int] = None, 

3768 ) -> ResponseT: 

3769 """ 

3770 Set the consumer group last delivered ID to something else. 

3771 name: name of the stream. 

3772 groupname: name of the consumer group. 

3773 id: ID of the last item in the stream to consider already delivered. 

3774 

3775 For more information see https://redis.io/commands/xgroup-setid 

3776 """ 

3777 pieces = [name, groupname, id] 

3778 if entries_read is not None: 

3779 pieces.extend(["ENTRIESREAD", entries_read]) 

3780 return self.execute_command("XGROUP SETID", *pieces) 

3781 

3782 def xinfo_consumers(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3783 """ 

3784 Returns general information about the consumers in the group. 

3785 name: name of the stream. 

3786 groupname: name of the consumer group. 

3787 

3788 For more information see https://redis.io/commands/xinfo-consumers 

3789 """ 

3790 return self.execute_command("XINFO CONSUMERS", name, groupname) 

3791 

3792 def xinfo_groups(self, name: KeyT) -> ResponseT: 

3793 """ 

3794 Returns general information about the consumer groups of the stream. 

3795 name: name of the stream. 

3796 

3797 For more information see https://redis.io/commands/xinfo-groups 

3798 """ 

3799 return self.execute_command("XINFO GROUPS", name) 

3800 

3801 def xinfo_stream(self, name: KeyT, full: bool = False) -> ResponseT: 

3802 """ 

3803 Returns general information about the stream. 

3804 name: name of the stream. 

3805 full: optional boolean, false by default. Return full summary 

3806 

3807 For more information see https://redis.io/commands/xinfo-stream 

3808 """ 

3809 pieces = [name] 

3810 options = {} 

3811 if full: 

3812 pieces.append(b"FULL") 

3813 options = {"full": full} 

3814 return self.execute_command("XINFO STREAM", *pieces, **options) 

3815 

3816 def xlen(self, name: KeyT) -> ResponseT: 

3817 """ 

3818 Returns the number of elements in a given stream. 

3819 

3820 For more information see https://redis.io/commands/xlen 

3821 """ 

3822 return self.execute_command("XLEN", name, keys=[name]) 

3823 

3824 def xpending(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3825 """ 

3826 Returns information about pending messages of a group. 

3827 name: name of the stream. 

3828 groupname: name of the consumer group. 

3829 

3830 For more information see https://redis.io/commands/xpending 

3831 """ 

3832 return self.execute_command("XPENDING", name, groupname, keys=[name]) 

3833 

3834 def xpending_range( 

3835 self, 

3836 name: KeyT, 

3837 groupname: GroupT, 

3838 min: StreamIdT, 

3839 max: StreamIdT, 

3840 count: int, 

3841 consumername: Union[ConsumerT, None] = None, 

3842 idle: Union[int, None] = None, 

3843 ) -> ResponseT: 

3844 """ 

3845 Returns information about pending messages, in a range. 

3846 

3847 name: name of the stream. 

3848 groupname: name of the consumer group. 

3849 idle: available from version 6.2. filter entries by their 

3850 idle-time, given in milliseconds (optional). 

3851 min: minimum stream ID. 

3852 max: maximum stream ID. 

3853 count: number of messages to return 

3854 consumername: name of a consumer to filter by (optional). 

3855 """ 

3856 if {min, max, count} == {None}: 

3857 if idle is not None or consumername is not None: 

3858 raise DataError( 

3859 "if XPENDING is provided with idle time" 

3860 " or consumername, it must be provided" 

3861 " with min, max and count parameters" 

3862 ) 

3863 return self.xpending(name, groupname) 

3864 

3865 pieces = [name, groupname] 

3866 if min is None or max is None or count is None: 

3867 raise DataError( 

3868 "XPENDING must be provided with min, max " 

3869 "and count parameters, or none of them." 

3870 ) 

3871 # idle 

3872 try: 

3873 if int(idle) < 0: 

3874 raise DataError("XPENDING idle must be a integer >= 0") 

3875 pieces.extend(["IDLE", idle]) 

3876 except TypeError: 

3877 pass 

3878 # count 

3879 try: 

3880 if int(count) < 0: 

3881 raise DataError("XPENDING count must be a integer >= 0") 

3882 pieces.extend([min, max, count]) 

3883 except TypeError: 

3884 pass 

3885 # consumername 

3886 if consumername: 

3887 pieces.append(consumername) 

3888 

3889 return self.execute_command("XPENDING", *pieces, parse_detail=True) 

3890 

3891 def xrange( 

3892 self, 

3893 name: KeyT, 

3894 min: StreamIdT = "-", 

3895 max: StreamIdT = "+", 

3896 count: Union[int, None] = None, 

3897 ) -> ResponseT: 

3898 """ 

3899 Read stream values within an interval. 

3900 

3901 name: name of the stream. 

3902 

3903 start: first stream ID. defaults to '-', 

3904 meaning the earliest available. 

3905 

3906 finish: last stream ID. defaults to '+', 

3907 meaning the latest available. 

3908 

3909 count: if set, only return this many items, beginning with the 

3910 earliest available. 

3911 

3912 For more information see https://redis.io/commands/xrange 

3913 """ 

3914 pieces = [min, max] 

3915 if count is not None: 

3916 if not isinstance(count, int) or count < 1: 

3917 raise DataError("XRANGE count must be a positive integer") 

3918 pieces.append(b"COUNT") 

3919 pieces.append(str(count)) 

3920 

3921 return self.execute_command("XRANGE", name, *pieces, keys=[name]) 

3922 

3923 def xread( 

3924 self, 

3925 streams: Dict[KeyT, StreamIdT], 

3926 count: Union[int, None] = None, 

3927 block: Union[int, None] = None, 

3928 ) -> ResponseT: 

3929 """ 

3930 Block and monitor multiple streams for new data. 

3931 

3932 streams: a dict of stream names to stream IDs, where 

3933 IDs indicate the last ID already seen. 

3934 

3935 count: if set, only return this many items, beginning with the 

3936 earliest available. 

3937 

3938 block: number of milliseconds to wait, if nothing already present. 

3939 

3940 For more information see https://redis.io/commands/xread 

3941 """ 

3942 pieces = [] 

3943 if block is not None: 

3944 if not isinstance(block, int) or block < 0: 

3945 raise DataError("XREAD block must be a non-negative integer") 

3946 pieces.append(b"BLOCK") 

3947 pieces.append(str(block)) 

3948 if count is not None: 

3949 if not isinstance(count, int) or count < 1: 

3950 raise DataError("XREAD count must be a positive integer") 

3951 pieces.append(b"COUNT") 

3952 pieces.append(str(count)) 

3953 if not isinstance(streams, dict) or len(streams) == 0: 

3954 raise DataError("XREAD streams must be a non empty dict") 

3955 pieces.append(b"STREAMS") 

3956 keys, values = zip(*streams.items()) 

3957 pieces.extend(keys) 

3958 pieces.extend(values) 

3959 return self.execute_command("XREAD", *pieces, keys=keys) 

3960 

3961 def xreadgroup( 

3962 self, 

3963 groupname: str, 

3964 consumername: str, 

3965 streams: Dict[KeyT, StreamIdT], 

3966 count: Union[int, None] = None, 

3967 block: Union[int, None] = None, 

3968 noack: bool = False, 

3969 ) -> ResponseT: 

3970 """ 

3971 Read from a stream via a consumer group. 

3972 

3973 groupname: name of the consumer group. 

3974 

3975 consumername: name of the requesting consumer. 

3976 

3977 streams: a dict of stream names to stream IDs, where 

3978 IDs indicate the last ID already seen. 

3979 

3980 count: if set, only return this many items, beginning with the 

3981 earliest available. 

3982 

3983 block: number of milliseconds to wait, if nothing already present. 

3984 noack: do not add messages to the PEL 

3985 

3986 For more information see https://redis.io/commands/xreadgroup 

3987 """ 

3988 pieces: list[EncodableT] = [b"GROUP", groupname, consumername] 

3989 if count is not None: 

3990 if not isinstance(count, int) or count < 1: 

3991 raise DataError("XREADGROUP count must be a positive integer") 

3992 pieces.append(b"COUNT") 

3993 pieces.append(str(count)) 

3994 if block is not None: 

3995 if not isinstance(block, int) or block < 0: 

3996 raise DataError("XREADGROUP block must be a non-negative integer") 

3997 pieces.append(b"BLOCK") 

3998 pieces.append(str(block)) 

3999 if noack: 

4000 pieces.append(b"NOACK") 

4001 if not isinstance(streams, dict) or len(streams) == 0: 

4002 raise DataError("XREADGROUP streams must be a non empty dict") 

4003 pieces.append(b"STREAMS") 

4004 pieces.extend(streams.keys()) 

4005 pieces.extend(streams.values()) 

4006 return self.execute_command("XREADGROUP", *pieces) 

4007 

4008 def xrevrange( 

4009 self, 

4010 name: KeyT, 

4011 max: StreamIdT = "+", 

4012 min: StreamIdT = "-", 

4013 count: Union[int, None] = None, 

4014 ) -> ResponseT: 

4015 """ 

4016 Read stream values within an interval, in reverse order. 

4017 

4018 name: name of the stream 

4019 

4020 start: first stream ID. defaults to '+', 

4021 meaning the latest available. 

4022 

4023 finish: last stream ID. defaults to '-', 

4024 meaning the earliest available. 

4025 

4026 count: if set, only return this many items, beginning with the 

4027 latest available. 

4028 

4029 For more information see https://redis.io/commands/xrevrange 

4030 """ 

4031 pieces: list[EncodableT] = [max, min] 

4032 if count is not None: 

4033 if not isinstance(count, int) or count < 1: 

4034 raise DataError("XREVRANGE count must be a positive integer") 

4035 pieces.append(b"COUNT") 

4036 pieces.append(str(count)) 

4037 

4038 return self.execute_command("XREVRANGE", name, *pieces, keys=[name]) 

4039 

4040 def xtrim( 

4041 self, 

4042 name: KeyT, 

4043 maxlen: Union[int, None] = None, 

4044 approximate: bool = True, 

4045 minid: Union[StreamIdT, None] = None, 

4046 limit: Union[int, None] = None, 

4047 ) -> ResponseT: 

4048 """ 

4049 Trims old messages from a stream. 

4050 name: name of the stream. 

4051 maxlen: truncate old stream messages beyond this size 

4052 Can't be specified with minid. 

4053 approximate: actual stream length may be slightly more than maxlen 

4054 minid: the minimum id in the stream to query 

4055 Can't be specified with maxlen. 

4056 limit: specifies the maximum number of entries to retrieve 

4057 

4058 For more information see https://redis.io/commands/xtrim 

4059 """ 

4060 pieces: list[EncodableT] = [] 

4061 if maxlen is not None and minid is not None: 

4062 raise DataError("Only one of ``maxlen`` or ``minid`` may be specified") 

4063 

4064 if maxlen is None and minid is None: 

4065 raise DataError("One of ``maxlen`` or ``minid`` must be specified") 

4066 

4067 if maxlen is not None: 

4068 pieces.append(b"MAXLEN") 

4069 if minid is not None: 

4070 pieces.append(b"MINID") 

4071 if approximate: 

4072 pieces.append(b"~") 

4073 if maxlen is not None: 

4074 pieces.append(maxlen) 

4075 if minid is not None: 

4076 pieces.append(minid) 

4077 if limit is not None: 

4078 pieces.append(b"LIMIT") 

4079 pieces.append(limit) 

4080 

4081 return self.execute_command("XTRIM", name, *pieces) 

4082 

4083 

4084AsyncStreamCommands = StreamCommands 

4085 

4086 

4087class SortedSetCommands(CommandsProtocol): 

4088 """ 

4089 Redis commands for Sorted Sets data type. 

4090 see: https://redis.io/topics/data-types-intro#redis-sorted-sets 

4091 """ 

4092 

4093 def zadd( 

4094 self, 

4095 name: KeyT, 

4096 mapping: Mapping[AnyKeyT, EncodableT], 

4097 nx: bool = False, 

4098 xx: bool = False, 

4099 ch: bool = False, 

4100 incr: bool = False, 

4101 gt: bool = False, 

4102 lt: bool = False, 

4103 ) -> ResponseT: 

4104 """ 

4105 Set any number of element-name, score pairs to the key ``name``. Pairs 

4106 are specified as a dict of element-names keys to score values. 

4107 

4108 ``nx`` forces ZADD to only create new elements and not to update 

4109 scores for elements that already exist. 

4110 

4111 ``xx`` forces ZADD to only update scores of elements that already 

4112 exist. New elements will not be added. 

4113 

4114 ``ch`` modifies the return value to be the numbers of elements changed. 

4115 Changed elements include new elements that were added and elements 

4116 whose scores changed. 

4117 

4118 ``incr`` modifies ZADD to behave like ZINCRBY. In this mode only a 

4119 single element/score pair can be specified and the score is the amount 

4120 the existing score will be incremented by. When using this mode the 

4121 return value of ZADD will be the new score of the element. 

4122 

4123 ``LT`` Only update existing elements if the new score is less than 

4124 the current score. This flag doesn't prevent adding new elements. 

4125 

4126 ``GT`` Only update existing elements if the new score is greater than 

4127 the current score. This flag doesn't prevent adding new elements. 

4128 

4129 The return value of ZADD varies based on the mode specified. With no 

4130 options, ZADD returns the number of new elements added to the sorted 

4131 set. 

4132 

4133 ``NX``, ``LT``, and ``GT`` are mutually exclusive options. 

4134 

4135 See: https://redis.io/commands/ZADD 

4136 """ 

4137 if not mapping: 

4138 raise DataError("ZADD requires at least one element/score pair") 

4139 if nx and xx: 

4140 raise DataError("ZADD allows either 'nx' or 'xx', not both") 

4141 if gt and lt: 

4142 raise DataError("ZADD allows either 'gt' or 'lt', not both") 

4143 if incr and len(mapping) != 1: 

4144 raise DataError( 

4145 "ZADD option 'incr' only works when passing a " 

4146 "single element/score pair" 

4147 ) 

4148 if nx and (gt or lt): 

4149 raise DataError("Only one of 'nx', 'lt', or 'gr' may be defined.") 

4150 

4151 pieces: list[EncodableT] = [] 

4152 options = {} 

4153 if nx: 

4154 pieces.append(b"NX") 

4155 if xx: 

4156 pieces.append(b"XX") 

4157 if ch: 

4158 pieces.append(b"CH") 

4159 if incr: 

4160 pieces.append(b"INCR") 

4161 options["as_score"] = True 

4162 if gt: 

4163 pieces.append(b"GT") 

4164 if lt: 

4165 pieces.append(b"LT") 

4166 for pair in mapping.items(): 

4167 pieces.append(pair[1]) 

4168 pieces.append(pair[0]) 

4169 return self.execute_command("ZADD", name, *pieces, **options) 

4170 

4171 def zcard(self, name: KeyT) -> ResponseT: 

4172 """ 

4173 Return the number of elements in the sorted set ``name`` 

4174 

4175 For more information see https://redis.io/commands/zcard 

4176 """ 

4177 return self.execute_command("ZCARD", name, keys=[name]) 

4178 

4179 def zcount(self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT) -> ResponseT: 

4180 """ 

4181 Returns the number of elements in the sorted set at key ``name`` with 

4182 a score between ``min`` and ``max``. 

4183 

4184 For more information see https://redis.io/commands/zcount 

4185 """ 

4186 return self.execute_command("ZCOUNT", name, min, max, keys=[name]) 

4187 

4188 def zdiff(self, keys: KeysT, withscores: bool = False) -> ResponseT: 

4189 """ 

4190 Returns the difference between the first and all successive input 

4191 sorted sets provided in ``keys``. 

4192 

4193 For more information see https://redis.io/commands/zdiff 

4194 """ 

4195 pieces = [len(keys), *keys] 

4196 if withscores: 

4197 pieces.append("WITHSCORES") 

4198 return self.execute_command("ZDIFF", *pieces, keys=keys) 

4199 

4200 def zdiffstore(self, dest: KeyT, keys: KeysT) -> ResponseT: 

4201 """ 

4202 Computes the difference between the first and all successive input 

4203 sorted sets provided in ``keys`` and stores the result in ``dest``. 

4204 

4205 For more information see https://redis.io/commands/zdiffstore 

4206 """ 

4207 pieces = [len(keys), *keys] 

4208 return self.execute_command("ZDIFFSTORE", dest, *pieces) 

4209 

4210 def zincrby(self, name: KeyT, amount: float, value: EncodableT) -> ResponseT: 

4211 """ 

4212 Increment the score of ``value`` in sorted set ``name`` by ``amount`` 

4213 

4214 For more information see https://redis.io/commands/zincrby 

4215 """ 

4216 return self.execute_command("ZINCRBY", name, amount, value) 

4217 

4218 def zinter( 

4219 self, keys: KeysT, aggregate: Union[str, None] = None, withscores: bool = False 

4220 ) -> ResponseT: 

4221 """ 

4222 Return the intersect of multiple sorted sets specified by ``keys``. 

4223 With the ``aggregate`` option, it is possible to specify how the 

4224 results of the union are aggregated. This option defaults to SUM, 

4225 where the score of an element is summed across the inputs where it 

4226 exists. When this option is set to either MIN or MAX, the resulting 

4227 set will contain the minimum or maximum score of an element across 

4228 the inputs where it exists. 

4229 

4230 For more information see https://redis.io/commands/zinter 

4231 """ 

4232 return self._zaggregate("ZINTER", None, keys, aggregate, withscores=withscores) 

4233 

4234 def zinterstore( 

4235 self, 

4236 dest: KeyT, 

4237 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4238 aggregate: Union[str, None] = None, 

4239 ) -> ResponseT: 

4240 """ 

4241 Intersect multiple sorted sets specified by ``keys`` into a new 

4242 sorted set, ``dest``. Scores in the destination will be aggregated 

4243 based on the ``aggregate``. This option defaults to SUM, where the 

4244 score of an element is summed across the inputs where it exists. 

4245 When this option is set to either MIN or MAX, the resulting set will 

4246 contain the minimum or maximum score of an element across the inputs 

4247 where it exists. 

4248 

4249 For more information see https://redis.io/commands/zinterstore 

4250 """ 

4251 return self._zaggregate("ZINTERSTORE", dest, keys, aggregate) 

4252 

4253 def zintercard( 

4254 self, numkeys: int, keys: List[str], limit: int = 0 

4255 ) -> Union[Awaitable[int], int]: 

4256 """ 

4257 Return the cardinality of the intersect of multiple sorted sets 

4258 specified by ``keys`. 

4259 When LIMIT provided (defaults to 0 and means unlimited), if the intersection 

4260 cardinality reaches limit partway through the computation, the algorithm will 

4261 exit and yield limit as the cardinality 

4262 

4263 For more information see https://redis.io/commands/zintercard 

4264 """ 

4265 args = [numkeys, *keys, "LIMIT", limit] 

4266 return self.execute_command("ZINTERCARD", *args, keys=keys) 

4267 

4268 def zlexcount(self, name, min, max): 

4269 """ 

4270 Return the number of items in the sorted set ``name`` between the 

4271 lexicographical range ``min`` and ``max``. 

4272 

4273 For more information see https://redis.io/commands/zlexcount 

4274 """ 

4275 return self.execute_command("ZLEXCOUNT", name, min, max, keys=[name]) 

4276 

4277 def zpopmax(self, name: KeyT, count: Union[int, None] = None) -> ResponseT: 

4278 """ 

4279 Remove and return up to ``count`` members with the highest scores 

4280 from the sorted set ``name``. 

4281 

4282 For more information see https://redis.io/commands/zpopmax 

4283 """ 

4284 args = (count is not None) and [count] or [] 

4285 options = {"withscores": True} 

4286 return self.execute_command("ZPOPMAX", name, *args, **options) 

4287 

4288 def zpopmin(self, name: KeyT, count: Union[int, None] = None) -> ResponseT: 

4289 """ 

4290 Remove and return up to ``count`` members with the lowest scores 

4291 from the sorted set ``name``. 

4292 

4293 For more information see https://redis.io/commands/zpopmin 

4294 """ 

4295 args = (count is not None) and [count] or [] 

4296 options = {"withscores": True} 

4297 return self.execute_command("ZPOPMIN", name, *args, **options) 

4298 

4299 def zrandmember( 

4300 self, key: KeyT, count: int = None, withscores: bool = False 

4301 ) -> ResponseT: 

4302 """ 

4303 Return a random element from the sorted set value stored at key. 

4304 

4305 ``count`` if the argument is positive, return an array of distinct 

4306 fields. If called with a negative count, the behavior changes and 

4307 the command is allowed to return the same field multiple times. 

4308 In this case, the number of returned fields is the absolute value 

4309 of the specified count. 

4310 

4311 ``withscores`` The optional WITHSCORES modifier changes the reply so it 

4312 includes the respective scores of the randomly selected elements from 

4313 the sorted set. 

4314 

4315 For more information see https://redis.io/commands/zrandmember 

4316 """ 

4317 params = [] 

4318 if count is not None: 

4319 params.append(count) 

4320 if withscores: 

4321 params.append("WITHSCORES") 

4322 

4323 return self.execute_command("ZRANDMEMBER", key, *params) 

4324 

4325 def bzpopmax(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4326 """ 

4327 ZPOPMAX a value off of the first non-empty sorted set 

4328 named in the ``keys`` list. 

4329 

4330 If none of the sorted sets in ``keys`` has a value to ZPOPMAX, 

4331 then block for ``timeout`` seconds, or until a member gets added 

4332 to one of the sorted sets. 

4333 

4334 If timeout is 0, then block indefinitely. 

4335 

4336 For more information see https://redis.io/commands/bzpopmax 

4337 """ 

4338 if timeout is None: 

4339 timeout = 0 

4340 keys = list_or_args(keys, None) 

4341 keys.append(timeout) 

4342 return self.execute_command("BZPOPMAX", *keys) 

4343 

4344 def bzpopmin(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4345 """ 

4346 ZPOPMIN a value off of the first non-empty sorted set 

4347 named in the ``keys`` list. 

4348 

4349 If none of the sorted sets in ``keys`` has a value to ZPOPMIN, 

4350 then block for ``timeout`` seconds, or until a member gets added 

4351 to one of the sorted sets. 

4352 

4353 If timeout is 0, then block indefinitely. 

4354 

4355 For more information see https://redis.io/commands/bzpopmin 

4356 """ 

4357 if timeout is None: 

4358 timeout = 0 

4359 keys: list[EncodableT] = list_or_args(keys, None) 

4360 keys.append(timeout) 

4361 return self.execute_command("BZPOPMIN", *keys) 

4362 

4363 def zmpop( 

4364 self, 

4365 num_keys: int, 

4366 keys: List[str], 

4367 min: Optional[bool] = False, 

4368 max: Optional[bool] = False, 

4369 count: Optional[int] = 1, 

4370 ) -> Union[Awaitable[list], list]: 

4371 """ 

4372 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4373 named in the ``keys`` list. 

4374 For more information see https://redis.io/commands/zmpop 

4375 """ 

4376 args = [num_keys] + keys 

4377 if (min and max) or (not min and not max): 

4378 raise DataError 

4379 elif min: 

4380 args.append("MIN") 

4381 else: 

4382 args.append("MAX") 

4383 if count != 1: 

4384 args.extend(["COUNT", count]) 

4385 

4386 return self.execute_command("ZMPOP", *args) 

4387 

4388 def bzmpop( 

4389 self, 

4390 timeout: float, 

4391 numkeys: int, 

4392 keys: List[str], 

4393 min: Optional[bool] = False, 

4394 max: Optional[bool] = False, 

4395 count: Optional[int] = 1, 

4396 ) -> Optional[list]: 

4397 """ 

4398 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4399 named in the ``keys`` list. 

4400 

4401 If none of the sorted sets in ``keys`` has a value to pop, 

4402 then block for ``timeout`` seconds, or until a member gets added 

4403 to one of the sorted sets. 

4404 

4405 If timeout is 0, then block indefinitely. 

4406 

4407 For more information see https://redis.io/commands/bzmpop 

4408 """ 

4409 args = [timeout, numkeys, *keys] 

4410 if (min and max) or (not min and not max): 

4411 raise DataError("Either min or max, but not both must be set") 

4412 elif min: 

4413 args.append("MIN") 

4414 else: 

4415 args.append("MAX") 

4416 args.extend(["COUNT", count]) 

4417 

4418 return self.execute_command("BZMPOP", *args) 

4419 

4420 def _zrange( 

4421 self, 

4422 command, 

4423 dest: Union[KeyT, None], 

4424 name: KeyT, 

4425 start: int, 

4426 end: int, 

4427 desc: bool = False, 

4428 byscore: bool = False, 

4429 bylex: bool = False, 

4430 withscores: bool = False, 

4431 score_cast_func: Union[type, Callable, None] = float, 

4432 offset: Union[int, None] = None, 

4433 num: Union[int, None] = None, 

4434 ) -> ResponseT: 

4435 if byscore and bylex: 

4436 raise DataError("``byscore`` and ``bylex`` can not be specified together.") 

4437 if (offset is not None and num is None) or (num is not None and offset is None): 

4438 raise DataError("``offset`` and ``num`` must both be specified.") 

4439 if bylex and withscores: 

4440 raise DataError( 

4441 "``withscores`` not supported in combination with ``bylex``." 

4442 ) 

4443 pieces = [command] 

4444 if dest: 

4445 pieces.append(dest) 

4446 pieces.extend([name, start, end]) 

4447 if byscore: 

4448 pieces.append("BYSCORE") 

4449 if bylex: 

4450 pieces.append("BYLEX") 

4451 if desc: 

4452 pieces.append("REV") 

4453 if offset is not None and num is not None: 

4454 pieces.extend(["LIMIT", offset, num]) 

4455 if withscores: 

4456 pieces.append("WITHSCORES") 

4457 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4458 options["keys"] = [name] 

4459 return self.execute_command(*pieces, **options) 

4460 

4461 def zrange( 

4462 self, 

4463 name: KeyT, 

4464 start: int, 

4465 end: int, 

4466 desc: bool = False, 

4467 withscores: bool = False, 

4468 score_cast_func: Union[type, Callable] = float, 

4469 byscore: bool = False, 

4470 bylex: bool = False, 

4471 offset: int = None, 

4472 num: int = None, 

4473 ) -> ResponseT: 

4474 """ 

4475 Return a range of values from sorted set ``name`` between 

4476 ``start`` and ``end`` sorted in ascending order. 

4477 

4478 ``start`` and ``end`` can be negative, indicating the end of the range. 

4479 

4480 ``desc`` a boolean indicating whether to sort the results in reversed 

4481 order. 

4482 

4483 ``withscores`` indicates to return the scores along with the values. 

4484 The return type is a list of (value, score) pairs. 

4485 

4486 ``score_cast_func`` a callable used to cast the score return value. 

4487 

4488 ``byscore`` when set to True, returns the range of elements from the 

4489 sorted set having scores equal or between ``start`` and ``end``. 

4490 

4491 ``bylex`` when set to True, returns the range of elements from the 

4492 sorted set between the ``start`` and ``end`` lexicographical closed 

4493 range intervals. 

4494 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

4495 whether the range interval is exclusive or inclusive, respectively. 

4496 

4497 ``offset`` and ``num`` are specified, then return a slice of the range. 

4498 Can't be provided when using ``bylex``. 

4499 

4500 For more information see https://redis.io/commands/zrange 

4501 """ 

4502 # Need to support ``desc`` also when using old redis version 

4503 # because it was supported in 3.5.3 (of redis-py) 

4504 if not byscore and not bylex and (offset is None and num is None) and desc: 

4505 return self.zrevrange(name, start, end, withscores, score_cast_func) 

4506 

4507 return self._zrange( 

4508 "ZRANGE", 

4509 None, 

4510 name, 

4511 start, 

4512 end, 

4513 desc, 

4514 byscore, 

4515 bylex, 

4516 withscores, 

4517 score_cast_func, 

4518 offset, 

4519 num, 

4520 ) 

4521 

4522 def zrevrange( 

4523 self, 

4524 name: KeyT, 

4525 start: int, 

4526 end: int, 

4527 withscores: bool = False, 

4528 score_cast_func: Union[type, Callable] = float, 

4529 ) -> ResponseT: 

4530 """ 

4531 Return a range of values from sorted set ``name`` between 

4532 ``start`` and ``end`` sorted in descending order. 

4533 

4534 ``start`` and ``end`` can be negative, indicating the end of the range. 

4535 

4536 ``withscores`` indicates to return the scores along with the values 

4537 The return type is a list of (value, score) pairs 

4538 

4539 ``score_cast_func`` a callable used to cast the score return value 

4540 

4541 For more information see https://redis.io/commands/zrevrange 

4542 """ 

4543 pieces = ["ZREVRANGE", name, start, end] 

4544 if withscores: 

4545 pieces.append(b"WITHSCORES") 

4546 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4547 options["keys"] = name 

4548 return self.execute_command(*pieces, **options) 

4549 

4550 def zrangestore( 

4551 self, 

4552 dest: KeyT, 

4553 name: KeyT, 

4554 start: int, 

4555 end: int, 

4556 byscore: bool = False, 

4557 bylex: bool = False, 

4558 desc: bool = False, 

4559 offset: Union[int, None] = None, 

4560 num: Union[int, None] = None, 

4561 ) -> ResponseT: 

4562 """ 

4563 Stores in ``dest`` the result of a range of values from sorted set 

4564 ``name`` between ``start`` and ``end`` sorted in ascending order. 

4565 

4566 ``start`` and ``end`` can be negative, indicating the end of the range. 

4567 

4568 ``byscore`` when set to True, returns the range of elements from the 

4569 sorted set having scores equal or between ``start`` and ``end``. 

4570 

4571 ``bylex`` when set to True, returns the range of elements from the 

4572 sorted set between the ``start`` and ``end`` lexicographical closed 

4573 range intervals. 

4574 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

4575 whether the range interval is exclusive or inclusive, respectively. 

4576 

4577 ``desc`` a boolean indicating whether to sort the results in reversed 

4578 order. 

4579 

4580 ``offset`` and ``num`` are specified, then return a slice of the range. 

4581 Can't be provided when using ``bylex``. 

4582 

4583 For more information see https://redis.io/commands/zrangestore 

4584 """ 

4585 return self._zrange( 

4586 "ZRANGESTORE", 

4587 dest, 

4588 name, 

4589 start, 

4590 end, 

4591 desc, 

4592 byscore, 

4593 bylex, 

4594 False, 

4595 None, 

4596 offset, 

4597 num, 

4598 ) 

4599 

4600 def zrangebylex( 

4601 self, 

4602 name: KeyT, 

4603 min: EncodableT, 

4604 max: EncodableT, 

4605 start: Union[int, None] = None, 

4606 num: Union[int, None] = None, 

4607 ) -> ResponseT: 

4608 """ 

4609 Return the lexicographical range of values from sorted set ``name`` 

4610 between ``min`` and ``max``. 

4611 

4612 If ``start`` and ``num`` are specified, then return a slice of the 

4613 range. 

4614 

4615 For more information see https://redis.io/commands/zrangebylex 

4616 """ 

4617 if (start is not None and num is None) or (num is not None and start is None): 

4618 raise DataError("``start`` and ``num`` must both be specified") 

4619 pieces = ["ZRANGEBYLEX", name, min, max] 

4620 if start is not None and num is not None: 

4621 pieces.extend([b"LIMIT", start, num]) 

4622 return self.execute_command(*pieces, keys=[name]) 

4623 

4624 def zrevrangebylex( 

4625 self, 

4626 name: KeyT, 

4627 max: EncodableT, 

4628 min: EncodableT, 

4629 start: Union[int, None] = None, 

4630 num: Union[int, None] = None, 

4631 ) -> ResponseT: 

4632 """ 

4633 Return the reversed lexicographical range of values from sorted set 

4634 ``name`` between ``max`` and ``min``. 

4635 

4636 If ``start`` and ``num`` are specified, then return a slice of the 

4637 range. 

4638 

4639 For more information see https://redis.io/commands/zrevrangebylex 

4640 """ 

4641 if (start is not None and num is None) or (num is not None and start is None): 

4642 raise DataError("``start`` and ``num`` must both be specified") 

4643 pieces = ["ZREVRANGEBYLEX", name, max, min] 

4644 if start is not None and num is not None: 

4645 pieces.extend(["LIMIT", start, num]) 

4646 return self.execute_command(*pieces, keys=[name]) 

4647 

4648 def zrangebyscore( 

4649 self, 

4650 name: KeyT, 

4651 min: ZScoreBoundT, 

4652 max: ZScoreBoundT, 

4653 start: Union[int, None] = None, 

4654 num: Union[int, None] = None, 

4655 withscores: bool = False, 

4656 score_cast_func: Union[type, Callable] = float, 

4657 ) -> ResponseT: 

4658 """ 

4659 Return a range of values from the sorted set ``name`` with scores 

4660 between ``min`` and ``max``. 

4661 

4662 If ``start`` and ``num`` are specified, then return a slice 

4663 of the range. 

4664 

4665 ``withscores`` indicates to return the scores along with the values. 

4666 The return type is a list of (value, score) pairs 

4667 

4668 `score_cast_func`` a callable used to cast the score return value 

4669 

4670 For more information see https://redis.io/commands/zrangebyscore 

4671 """ 

4672 if (start is not None and num is None) or (num is not None and start is None): 

4673 raise DataError("``start`` and ``num`` must both be specified") 

4674 pieces = ["ZRANGEBYSCORE", name, min, max] 

4675 if start is not None and num is not None: 

4676 pieces.extend(["LIMIT", start, num]) 

4677 if withscores: 

4678 pieces.append("WITHSCORES") 

4679 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4680 options["keys"] = [name] 

4681 return self.execute_command(*pieces, **options) 

4682 

4683 def zrevrangebyscore( 

4684 self, 

4685 name: KeyT, 

4686 max: ZScoreBoundT, 

4687 min: ZScoreBoundT, 

4688 start: Union[int, None] = None, 

4689 num: Union[int, None] = None, 

4690 withscores: bool = False, 

4691 score_cast_func: Union[type, Callable] = float, 

4692 ): 

4693 """ 

4694 Return a range of values from the sorted set ``name`` with scores 

4695 between ``min`` and ``max`` in descending order. 

4696 

4697 If ``start`` and ``num`` are specified, then return a slice 

4698 of the range. 

4699 

4700 ``withscores`` indicates to return the scores along with the values. 

4701 The return type is a list of (value, score) pairs 

4702 

4703 ``score_cast_func`` a callable used to cast the score return value 

4704 

4705 For more information see https://redis.io/commands/zrevrangebyscore 

4706 """ 

4707 if (start is not None and num is None) or (num is not None and start is None): 

4708 raise DataError("``start`` and ``num`` must both be specified") 

4709 pieces = ["ZREVRANGEBYSCORE", name, max, min] 

4710 if start is not None and num is not None: 

4711 pieces.extend(["LIMIT", start, num]) 

4712 if withscores: 

4713 pieces.append("WITHSCORES") 

4714 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4715 options["keys"] = [name] 

4716 return self.execute_command(*pieces, **options) 

4717 

4718 def zrank( 

4719 self, 

4720 name: KeyT, 

4721 value: EncodableT, 

4722 withscore: bool = False, 

4723 ) -> ResponseT: 

4724 """ 

4725 Returns a 0-based value indicating the rank of ``value`` in sorted set 

4726 ``name``. 

4727 The optional WITHSCORE argument supplements the command's 

4728 reply with the score of the element returned. 

4729 

4730 For more information see https://redis.io/commands/zrank 

4731 """ 

4732 if withscore: 

4733 return self.execute_command("ZRANK", name, value, "WITHSCORE", keys=[name]) 

4734 return self.execute_command("ZRANK", name, value, keys=[name]) 

4735 

4736 def zrem(self, name: KeyT, *values: FieldT) -> ResponseT: 

4737 """ 

4738 Remove member ``values`` from sorted set ``name`` 

4739 

4740 For more information see https://redis.io/commands/zrem 

4741 """ 

4742 return self.execute_command("ZREM", name, *values) 

4743 

4744 def zremrangebylex(self, name: KeyT, min: EncodableT, max: EncodableT) -> ResponseT: 

4745 """ 

4746 Remove all elements in the sorted set ``name`` between the 

4747 lexicographical range specified by ``min`` and ``max``. 

4748 

4749 Returns the number of elements removed. 

4750 

4751 For more information see https://redis.io/commands/zremrangebylex 

4752 """ 

4753 return self.execute_command("ZREMRANGEBYLEX", name, min, max) 

4754 

4755 def zremrangebyrank(self, name: KeyT, min: int, max: int) -> ResponseT: 

4756 """ 

4757 Remove all elements in the sorted set ``name`` with ranks between 

4758 ``min`` and ``max``. Values are 0-based, ordered from smallest score 

4759 to largest. Values can be negative indicating the highest scores. 

4760 Returns the number of elements removed 

4761 

4762 For more information see https://redis.io/commands/zremrangebyrank 

4763 """ 

4764 return self.execute_command("ZREMRANGEBYRANK", name, min, max) 

4765 

4766 def zremrangebyscore( 

4767 self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT 

4768 ) -> ResponseT: 

4769 """ 

4770 Remove all elements in the sorted set ``name`` with scores 

4771 between ``min`` and ``max``. Returns the number of elements removed. 

4772 

4773 For more information see https://redis.io/commands/zremrangebyscore 

4774 """ 

4775 return self.execute_command("ZREMRANGEBYSCORE", name, min, max) 

4776 

4777 def zrevrank( 

4778 self, 

4779 name: KeyT, 

4780 value: EncodableT, 

4781 withscore: bool = False, 

4782 ) -> ResponseT: 

4783 """ 

4784 Returns a 0-based value indicating the descending rank of 

4785 ``value`` in sorted set ``name``. 

4786 The optional ``withscore`` argument supplements the command's 

4787 reply with the score of the element returned. 

4788 

4789 For more information see https://redis.io/commands/zrevrank 

4790 """ 

4791 if withscore: 

4792 return self.execute_command( 

4793 "ZREVRANK", name, value, "WITHSCORE", keys=[name] 

4794 ) 

4795 return self.execute_command("ZREVRANK", name, value, keys=[name]) 

4796 

4797 def zscore(self, name: KeyT, value: EncodableT) -> ResponseT: 

4798 """ 

4799 Return the score of element ``value`` in sorted set ``name`` 

4800 

4801 For more information see https://redis.io/commands/zscore 

4802 """ 

4803 return self.execute_command("ZSCORE", name, value, keys=[name]) 

4804 

4805 def zunion( 

4806 self, 

4807 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4808 aggregate: Union[str, None] = None, 

4809 withscores: bool = False, 

4810 ) -> ResponseT: 

4811 """ 

4812 Return the union of multiple sorted sets specified by ``keys``. 

4813 ``keys`` can be provided as dictionary of keys and their weights. 

4814 Scores will be aggregated based on the ``aggregate``, or SUM if 

4815 none is provided. 

4816 

4817 For more information see https://redis.io/commands/zunion 

4818 """ 

4819 return self._zaggregate("ZUNION", None, keys, aggregate, withscores=withscores) 

4820 

4821 def zunionstore( 

4822 self, 

4823 dest: KeyT, 

4824 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4825 aggregate: Union[str, None] = None, 

4826 ) -> ResponseT: 

4827 """ 

4828 Union multiple sorted sets specified by ``keys`` into 

4829 a new sorted set, ``dest``. Scores in the destination will be 

4830 aggregated based on the ``aggregate``, or SUM if none is provided. 

4831 

4832 For more information see https://redis.io/commands/zunionstore 

4833 """ 

4834 return self._zaggregate("ZUNIONSTORE", dest, keys, aggregate) 

4835 

4836 def zmscore(self, key: KeyT, members: List[str]) -> ResponseT: 

4837 """ 

4838 Returns the scores associated with the specified members 

4839 in the sorted set stored at key. 

4840 ``members`` should be a list of the member name. 

4841 Return type is a list of score. 

4842 If the member does not exist, a None will be returned 

4843 in corresponding position. 

4844 

4845 For more information see https://redis.io/commands/zmscore 

4846 """ 

4847 if not members: 

4848 raise DataError("ZMSCORE members must be a non-empty list") 

4849 pieces = [key] + members 

4850 return self.execute_command("ZMSCORE", *pieces, keys=[key]) 

4851 

4852 def _zaggregate( 

4853 self, 

4854 command: str, 

4855 dest: Union[KeyT, None], 

4856 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4857 aggregate: Union[str, None] = None, 

4858 **options, 

4859 ) -> ResponseT: 

4860 pieces: list[EncodableT] = [command] 

4861 if dest is not None: 

4862 pieces.append(dest) 

4863 pieces.append(len(keys)) 

4864 if isinstance(keys, dict): 

4865 keys, weights = keys.keys(), keys.values() 

4866 else: 

4867 weights = None 

4868 pieces.extend(keys) 

4869 if weights: 

4870 pieces.append(b"WEIGHTS") 

4871 pieces.extend(weights) 

4872 if aggregate: 

4873 if aggregate.upper() in ["SUM", "MIN", "MAX"]: 

4874 pieces.append(b"AGGREGATE") 

4875 pieces.append(aggregate) 

4876 else: 

4877 raise DataError("aggregate can be sum, min or max.") 

4878 if options.get("withscores", False): 

4879 pieces.append(b"WITHSCORES") 

4880 options["keys"] = keys 

4881 return self.execute_command(*pieces, **options) 

4882 

4883 

4884AsyncSortedSetCommands = SortedSetCommands 

4885 

4886 

4887class HyperlogCommands(CommandsProtocol): 

4888 """ 

4889 Redis commands of HyperLogLogs data type. 

4890 see: https://redis.io/topics/data-types-intro#hyperloglogs 

4891 """ 

4892 

4893 def pfadd(self, name: KeyT, *values: FieldT) -> ResponseT: 

4894 """ 

4895 Adds the specified elements to the specified HyperLogLog. 

4896 

4897 For more information see https://redis.io/commands/pfadd 

4898 """ 

4899 return self.execute_command("PFADD", name, *values) 

4900 

4901 def pfcount(self, *sources: KeyT) -> ResponseT: 

4902 """ 

4903 Return the approximated cardinality of 

4904 the set observed by the HyperLogLog at key(s). 

4905 

4906 For more information see https://redis.io/commands/pfcount 

4907 """ 

4908 return self.execute_command("PFCOUNT", *sources) 

4909 

4910 def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT: 

4911 """ 

4912 Merge N different HyperLogLogs into a single one. 

4913 

4914 For more information see https://redis.io/commands/pfmerge 

4915 """ 

4916 return self.execute_command("PFMERGE", dest, *sources) 

4917 

4918 

4919AsyncHyperlogCommands = HyperlogCommands 

4920 

4921 

4922class HashCommands(CommandsProtocol): 

4923 """ 

4924 Redis commands for Hash data type. 

4925 see: https://redis.io/topics/data-types-intro#redis-hashes 

4926 """ 

4927 

4928 def hdel(self, name: str, *keys: str) -> Union[Awaitable[int], int]: 

4929 """ 

4930 Delete ``keys`` from hash ``name`` 

4931 

4932 For more information see https://redis.io/commands/hdel 

4933 """ 

4934 return self.execute_command("HDEL", name, *keys) 

4935 

4936 def hexists(self, name: str, key: str) -> Union[Awaitable[bool], bool]: 

4937 """ 

4938 Returns a boolean indicating if ``key`` exists within hash ``name`` 

4939 

4940 For more information see https://redis.io/commands/hexists 

4941 """ 

4942 return self.execute_command("HEXISTS", name, key, keys=[name]) 

4943 

4944 def hget( 

4945 self, name: str, key: str 

4946 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

4947 """ 

4948 Return the value of ``key`` within the hash ``name`` 

4949 

4950 For more information see https://redis.io/commands/hget 

4951 """ 

4952 return self.execute_command("HGET", name, key, keys=[name]) 

4953 

4954 def hgetall(self, name: str) -> Union[Awaitable[dict], dict]: 

4955 """ 

4956 Return a Python dict of the hash's name/value pairs 

4957 

4958 For more information see https://redis.io/commands/hgetall 

4959 """ 

4960 return self.execute_command("HGETALL", name, keys=[name]) 

4961 

4962 def hincrby( 

4963 self, name: str, key: str, amount: int = 1 

4964 ) -> Union[Awaitable[int], int]: 

4965 """ 

4966 Increment the value of ``key`` in hash ``name`` by ``amount`` 

4967 

4968 For more information see https://redis.io/commands/hincrby 

4969 """ 

4970 return self.execute_command("HINCRBY", name, key, amount) 

4971 

4972 def hincrbyfloat( 

4973 self, name: str, key: str, amount: float = 1.0 

4974 ) -> Union[Awaitable[float], float]: 

4975 """ 

4976 Increment the value of ``key`` in hash ``name`` by floating ``amount`` 

4977 

4978 For more information see https://redis.io/commands/hincrbyfloat 

4979 """ 

4980 return self.execute_command("HINCRBYFLOAT", name, key, amount) 

4981 

4982 def hkeys(self, name: str) -> Union[Awaitable[List], List]: 

4983 """ 

4984 Return the list of keys within hash ``name`` 

4985 

4986 For more information see https://redis.io/commands/hkeys 

4987 """ 

4988 return self.execute_command("HKEYS", name, keys=[name]) 

4989 

4990 def hlen(self, name: str) -> Union[Awaitable[int], int]: 

4991 """ 

4992 Return the number of elements in hash ``name`` 

4993 

4994 For more information see https://redis.io/commands/hlen 

4995 """ 

4996 return self.execute_command("HLEN", name, keys=[name]) 

4997 

4998 def hset( 

4999 self, 

5000 name: str, 

5001 key: Optional[str] = None, 

5002 value: Optional[str] = None, 

5003 mapping: Optional[dict] = None, 

5004 items: Optional[list] = None, 

5005 ) -> Union[Awaitable[int], int]: 

5006 """ 

5007 Set ``key`` to ``value`` within hash ``name``, 

5008 ``mapping`` accepts a dict of key/value pairs that will be 

5009 added to hash ``name``. 

5010 ``items`` accepts a list of key/value pairs that will be 

5011 added to hash ``name``. 

5012 Returns the number of fields that were added. 

5013 

5014 For more information see https://redis.io/commands/hset 

5015 """ 

5016 if key is None and not mapping and not items: 

5017 raise DataError("'hset' with no key value pairs") 

5018 pieces = [] 

5019 if items: 

5020 pieces.extend(items) 

5021 if key is not None: 

5022 pieces.extend((key, value)) 

5023 if mapping: 

5024 for pair in mapping.items(): 

5025 pieces.extend(pair) 

5026 

5027 return self.execute_command("HSET", name, *pieces) 

5028 

5029 def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool]: 

5030 """ 

5031 Set ``key`` to ``value`` within hash ``name`` if ``key`` does not 

5032 exist. Returns 1 if HSETNX created a field, otherwise 0. 

5033 

5034 For more information see https://redis.io/commands/hsetnx 

5035 """ 

5036 return self.execute_command("HSETNX", name, key, value) 

5037 

5038 def hmset(self, name: str, mapping: dict) -> Union[Awaitable[str], str]: 

5039 """ 

5040 Set key to value within hash ``name`` for each corresponding 

5041 key and value from the ``mapping`` dict. 

5042 

5043 For more information see https://redis.io/commands/hmset 

5044 """ 

5045 warnings.warn( 

5046 f"{self.__class__.__name__}.hmset() is deprecated. " 

5047 f"Use {self.__class__.__name__}.hset() instead.", 

5048 DeprecationWarning, 

5049 stacklevel=2, 

5050 ) 

5051 if not mapping: 

5052 raise DataError("'hmset' with 'mapping' of length 0") 

5053 items = [] 

5054 for pair in mapping.items(): 

5055 items.extend(pair) 

5056 return self.execute_command("HMSET", name, *items) 

5057 

5058 def hmget(self, name: str, keys: List, *args: List) -> Union[Awaitable[List], List]: 

5059 """ 

5060 Returns a list of values ordered identically to ``keys`` 

5061 

5062 For more information see https://redis.io/commands/hmget 

5063 """ 

5064 args = list_or_args(keys, args) 

5065 return self.execute_command("HMGET", name, *args, keys=[name]) 

5066 

5067 def hvals(self, name: str) -> Union[Awaitable[List], List]: 

5068 """ 

5069 Return the list of values within hash ``name`` 

5070 

5071 For more information see https://redis.io/commands/hvals 

5072 """ 

5073 return self.execute_command("HVALS", name, keys=[name]) 

5074 

5075 def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]: 

5076 """ 

5077 Return the number of bytes stored in the value of ``key`` 

5078 within hash ``name`` 

5079 

5080 For more information see https://redis.io/commands/hstrlen 

5081 """ 

5082 return self.execute_command("HSTRLEN", name, key, keys=[name]) 

5083 

5084 

5085AsyncHashCommands = HashCommands 

5086 

5087 

5088class Script: 

5089 """ 

5090 An executable Lua script object returned by ``register_script`` 

5091 """ 

5092 

5093 def __init__(self, registered_client: "Redis", script: ScriptTextT): 

5094 self.registered_client = registered_client 

5095 self.script = script 

5096 # Precalculate and store the SHA1 hex digest of the script. 

5097 

5098 if isinstance(script, str): 

5099 # We need the encoding from the client in order to generate an 

5100 # accurate byte representation of the script 

5101 try: 

5102 encoder = registered_client.connection_pool.get_encoder() 

5103 except AttributeError: 

5104 # Cluster 

5105 encoder = registered_client.get_encoder() 

5106 script = encoder.encode(script) 

5107 self.sha = hashlib.sha1(script).hexdigest() 

5108 

5109 def __call__( 

5110 self, 

5111 keys: Union[Sequence[KeyT], None] = None, 

5112 args: Union[Iterable[EncodableT], None] = None, 

5113 client: Union["Redis", None] = None, 

5114 ): 

5115 """Execute the script, passing any required ``args``""" 

5116 keys = keys or [] 

5117 args = args or [] 

5118 if client is None: 

5119 client = self.registered_client 

5120 args = tuple(keys) + tuple(args) 

5121 # make sure the Redis server knows about the script 

5122 from redis.client import Pipeline 

5123 

5124 if isinstance(client, Pipeline): 

5125 # Make sure the pipeline can register the script before executing. 

5126 client.scripts.add(self) 

5127 try: 

5128 return client.evalsha(self.sha, len(keys), *args) 

5129 except NoScriptError: 

5130 # Maybe the client is pointed to a different server than the client 

5131 # that created this instance? 

5132 # Overwrite the sha just in case there was a discrepancy. 

5133 self.sha = client.script_load(self.script) 

5134 return client.evalsha(self.sha, len(keys), *args) 

5135 

5136 

5137class AsyncScript: 

5138 """ 

5139 An executable Lua script object returned by ``register_script`` 

5140 """ 

5141 

5142 def __init__(self, registered_client: "AsyncRedis", script: ScriptTextT): 

5143 self.registered_client = registered_client 

5144 self.script = script 

5145 # Precalculate and store the SHA1 hex digest of the script. 

5146 

5147 if isinstance(script, str): 

5148 # We need the encoding from the client in order to generate an 

5149 # accurate byte representation of the script 

5150 try: 

5151 encoder = registered_client.connection_pool.get_encoder() 

5152 except AttributeError: 

5153 # Cluster 

5154 encoder = registered_client.get_encoder() 

5155 script = encoder.encode(script) 

5156 self.sha = hashlib.sha1(script).hexdigest() 

5157 

5158 async def __call__( 

5159 self, 

5160 keys: Union[Sequence[KeyT], None] = None, 

5161 args: Union[Iterable[EncodableT], None] = None, 

5162 client: Union["AsyncRedis", None] = None, 

5163 ): 

5164 """Execute the script, passing any required ``args``""" 

5165 keys = keys or [] 

5166 args = args or [] 

5167 if client is None: 

5168 client = self.registered_client 

5169 args = tuple(keys) + tuple(args) 

5170 # make sure the Redis server knows about the script 

5171 from redis.asyncio.client import Pipeline 

5172 

5173 if isinstance(client, Pipeline): 

5174 # Make sure the pipeline can register the script before executing. 

5175 client.scripts.add(self) 

5176 try: 

5177 return await client.evalsha(self.sha, len(keys), *args) 

5178 except NoScriptError: 

5179 # Maybe the client is pointed to a different server than the client 

5180 # that created this instance? 

5181 # Overwrite the sha just in case there was a discrepancy. 

5182 self.sha = await client.script_load(self.script) 

5183 return await client.evalsha(self.sha, len(keys), *args) 

5184 

5185 

5186class PubSubCommands(CommandsProtocol): 

5187 """ 

5188 Redis PubSub commands. 

5189 see https://redis.io/topics/pubsub 

5190 """ 

5191 

5192 def publish(self, channel: ChannelT, message: EncodableT, **kwargs) -> ResponseT: 

5193 """ 

5194 Publish ``message`` on ``channel``. 

5195 Returns the number of subscribers the message was delivered to. 

5196 

5197 For more information see https://redis.io/commands/publish 

5198 """ 

5199 return self.execute_command("PUBLISH", channel, message, **kwargs) 

5200 

5201 def spublish(self, shard_channel: ChannelT, message: EncodableT) -> ResponseT: 

5202 """ 

5203 Posts a message to the given shard channel. 

5204 Returns the number of clients that received the message 

5205 

5206 For more information see https://redis.io/commands/spublish 

5207 """ 

5208 return self.execute_command("SPUBLISH", shard_channel, message) 

5209 

5210 def pubsub_channels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

5211 """ 

5212 Return a list of channels that have at least one subscriber 

5213 

5214 For more information see https://redis.io/commands/pubsub-channels 

5215 """ 

5216 return self.execute_command("PUBSUB CHANNELS", pattern, **kwargs) 

5217 

5218 def pubsub_shardchannels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

5219 """ 

5220 Return a list of shard_channels that have at least one subscriber 

5221 

5222 For more information see https://redis.io/commands/pubsub-shardchannels 

5223 """ 

5224 return self.execute_command("PUBSUB SHARDCHANNELS", pattern, **kwargs) 

5225 

5226 def pubsub_numpat(self, **kwargs) -> ResponseT: 

5227 """ 

5228 Returns the number of subscriptions to patterns 

5229 

5230 For more information see https://redis.io/commands/pubsub-numpat 

5231 """ 

5232 return self.execute_command("PUBSUB NUMPAT", **kwargs) 

5233 

5234 def pubsub_numsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

5235 """ 

5236 Return a list of (channel, number of subscribers) tuples 

5237 for each channel given in ``*args`` 

5238 

5239 For more information see https://redis.io/commands/pubsub-numsub 

5240 """ 

5241 return self.execute_command("PUBSUB NUMSUB", *args, **kwargs) 

5242 

5243 def pubsub_shardnumsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

5244 """ 

5245 Return a list of (shard_channel, number of subscribers) tuples 

5246 for each channel given in ``*args`` 

5247 

5248 For more information see https://redis.io/commands/pubsub-shardnumsub 

5249 """ 

5250 return self.execute_command("PUBSUB SHARDNUMSUB", *args, **kwargs) 

5251 

5252 

5253AsyncPubSubCommands = PubSubCommands 

5254 

5255 

5256class ScriptCommands(CommandsProtocol): 

5257 """ 

5258 Redis Lua script commands. see: 

5259 https://redis.com/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/ 

5260 """ 

5261 

5262 def _eval( 

5263 self, command: str, script: str, numkeys: int, *keys_and_args: str 

5264 ) -> Union[Awaitable[str], str]: 

5265 return self.execute_command(command, script, numkeys, *keys_and_args) 

5266 

5267 def eval( 

5268 self, script: str, numkeys: int, *keys_and_args: str 

5269 ) -> Union[Awaitable[str], str]: 

5270 """ 

5271 Execute the Lua ``script``, specifying the ``numkeys`` the script 

5272 will touch and the key names and argument values in ``keys_and_args``. 

5273 Returns the result of the script. 

5274 

5275 In practice, use the object returned by ``register_script``. This 

5276 function exists purely for Redis API completion. 

5277 

5278 For more information see https://redis.io/commands/eval 

5279 """ 

5280 return self._eval("EVAL", script, numkeys, *keys_and_args) 

5281 

5282 def eval_ro( 

5283 self, script: str, numkeys: int, *keys_and_args: str 

5284 ) -> Union[Awaitable[str], str]: 

5285 """ 

5286 The read-only variant of the EVAL command 

5287 

5288 Execute the read-only Lua ``script`` specifying the ``numkeys`` the script 

5289 will touch and the key names and argument values in ``keys_and_args``. 

5290 Returns the result of the script. 

5291 

5292 For more information see https://redis.io/commands/eval_ro 

5293 """ 

5294 return self._eval("EVAL_RO", script, numkeys, *keys_and_args) 

5295 

5296 def _evalsha( 

5297 self, command: str, sha: str, numkeys: int, *keys_and_args: list 

5298 ) -> Union[Awaitable[str], str]: 

5299 return self.execute_command(command, sha, numkeys, *keys_and_args) 

5300 

5301 def evalsha( 

5302 self, sha: str, numkeys: int, *keys_and_args: str 

5303 ) -> Union[Awaitable[str], str]: 

5304 """ 

5305 Use the ``sha`` to execute a Lua script already registered via EVAL 

5306 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

5307 key names and argument values in ``keys_and_args``. Returns the result 

5308 of the script. 

5309 

5310 In practice, use the object returned by ``register_script``. This 

5311 function exists purely for Redis API completion. 

5312 

5313 For more information see https://redis.io/commands/evalsha 

5314 """ 

5315 return self._evalsha("EVALSHA", sha, numkeys, *keys_and_args) 

5316 

5317 def evalsha_ro( 

5318 self, sha: str, numkeys: int, *keys_and_args: str 

5319 ) -> Union[Awaitable[str], str]: 

5320 """ 

5321 The read-only variant of the EVALSHA command 

5322 

5323 Use the ``sha`` to execute a read-only Lua script already registered via EVAL 

5324 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

5325 key names and argument values in ``keys_and_args``. Returns the result 

5326 of the script. 

5327 

5328 For more information see https://redis.io/commands/evalsha_ro 

5329 """ 

5330 return self._evalsha("EVALSHA_RO", sha, numkeys, *keys_and_args) 

5331 

5332 def script_exists(self, *args: str) -> ResponseT: 

5333 """ 

5334 Check if a script exists in the script cache by specifying the SHAs of 

5335 each script as ``args``. Returns a list of boolean values indicating if 

5336 if each already script exists in the cache. 

5337 

5338 For more information see https://redis.io/commands/script-exists 

5339 """ 

5340 return self.execute_command("SCRIPT EXISTS", *args) 

5341 

5342 def script_debug(self, *args) -> None: 

5343 raise NotImplementedError( 

5344 "SCRIPT DEBUG is intentionally not implemented in the client." 

5345 ) 

5346 

5347 def script_flush( 

5348 self, sync_type: Union[Literal["SYNC"], Literal["ASYNC"]] = None 

5349 ) -> ResponseT: 

5350 """Flush all scripts from the script cache. 

5351 

5352 ``sync_type`` is by default SYNC (synchronous) but it can also be 

5353 ASYNC. 

5354 

5355 For more information see https://redis.io/commands/script-flush 

5356 """ 

5357 

5358 # Redis pre 6 had no sync_type. 

5359 if sync_type not in ["SYNC", "ASYNC", None]: 

5360 raise DataError( 

5361 "SCRIPT FLUSH defaults to SYNC in redis > 6.2, or " 

5362 "accepts SYNC/ASYNC. For older versions, " 

5363 "of redis leave as None." 

5364 ) 

5365 if sync_type is None: 

5366 pieces = [] 

5367 else: 

5368 pieces = [sync_type] 

5369 return self.execute_command("SCRIPT FLUSH", *pieces) 

5370 

5371 def script_kill(self) -> ResponseT: 

5372 """ 

5373 Kill the currently executing Lua script 

5374 

5375 For more information see https://redis.io/commands/script-kill 

5376 """ 

5377 return self.execute_command("SCRIPT KILL") 

5378 

5379 def script_load(self, script: ScriptTextT) -> ResponseT: 

5380 """ 

5381 Load a Lua ``script`` into the script cache. Returns the SHA. 

5382 

5383 For more information see https://redis.io/commands/script-load 

5384 """ 

5385 return self.execute_command("SCRIPT LOAD", script) 

5386 

5387 def register_script(self: "Redis", script: ScriptTextT) -> Script: 

5388 """ 

5389 Register a Lua ``script`` specifying the ``keys`` it will touch. 

5390 Returns a Script object that is callable and hides the complexity of 

5391 deal with scripts, keys, and shas. This is the preferred way to work 

5392 with Lua scripts. 

5393 """ 

5394 return Script(self, script) 

5395 

5396 

5397class AsyncScriptCommands(ScriptCommands): 

5398 async def script_debug(self, *args) -> None: 

5399 return super().script_debug() 

5400 

5401 def register_script(self: "AsyncRedis", script: ScriptTextT) -> AsyncScript: 

5402 """ 

5403 Register a Lua ``script`` specifying the ``keys`` it will touch. 

5404 Returns a Script object that is callable and hides the complexity of 

5405 deal with scripts, keys, and shas. This is the preferred way to work 

5406 with Lua scripts. 

5407 """ 

5408 return AsyncScript(self, script) 

5409 

5410 

5411class GeoCommands(CommandsProtocol): 

5412 """ 

5413 Redis Geospatial commands. 

5414 see: https://redis.com/redis-best-practices/indexing-patterns/geospatial/ 

5415 """ 

5416 

5417 def geoadd( 

5418 self, 

5419 name: KeyT, 

5420 values: Sequence[EncodableT], 

5421 nx: bool = False, 

5422 xx: bool = False, 

5423 ch: bool = False, 

5424 ) -> ResponseT: 

5425 """ 

5426 Add the specified geospatial items to the specified key identified 

5427 by the ``name`` argument. The Geospatial items are given as ordered 

5428 members of the ``values`` argument, each item or place is formed by 

5429 the triad longitude, latitude and name. 

5430 

5431 Note: You can use ZREM to remove elements. 

5432 

5433 ``nx`` forces ZADD to only create new elements and not to update 

5434 scores for elements that already exist. 

5435 

5436 ``xx`` forces ZADD to only update scores of elements that already 

5437 exist. New elements will not be added. 

5438 

5439 ``ch`` modifies the return value to be the numbers of elements changed. 

5440 Changed elements include new elements that were added and elements 

5441 whose scores changed. 

5442 

5443 For more information see https://redis.io/commands/geoadd 

5444 """ 

5445 if nx and xx: 

5446 raise DataError("GEOADD allows either 'nx' or 'xx', not both") 

5447 if len(values) % 3 != 0: 

5448 raise DataError("GEOADD requires places with lon, lat and name values") 

5449 pieces = [name] 

5450 if nx: 

5451 pieces.append("NX") 

5452 if xx: 

5453 pieces.append("XX") 

5454 if ch: 

5455 pieces.append("CH") 

5456 pieces.extend(values) 

5457 return self.execute_command("GEOADD", *pieces) 

5458 

5459 def geodist( 

5460 self, name: KeyT, place1: FieldT, place2: FieldT, unit: Union[str, None] = None 

5461 ) -> ResponseT: 

5462 """ 

5463 Return the distance between ``place1`` and ``place2`` members of the 

5464 ``name`` key. 

5465 The units must be one of the following : m, km mi, ft. By default 

5466 meters are used. 

5467 

5468 For more information see https://redis.io/commands/geodist 

5469 """ 

5470 pieces: list[EncodableT] = [name, place1, place2] 

5471 if unit and unit not in ("m", "km", "mi", "ft"): 

5472 raise DataError("GEODIST invalid unit") 

5473 elif unit: 

5474 pieces.append(unit) 

5475 return self.execute_command("GEODIST", *pieces, keys=[name]) 

5476 

5477 def geohash(self, name: KeyT, *values: FieldT) -> ResponseT: 

5478 """ 

5479 Return the geo hash string for each item of ``values`` members of 

5480 the specified key identified by the ``name`` argument. 

5481 

5482 For more information see https://redis.io/commands/geohash 

5483 """ 

5484 return self.execute_command("GEOHASH", name, *values, keys=[name]) 

5485 

5486 def geopos(self, name: KeyT, *values: FieldT) -> ResponseT: 

5487 """ 

5488 Return the positions of each item of ``values`` as members of 

5489 the specified key identified by the ``name`` argument. Each position 

5490 is represented by the pairs lon and lat. 

5491 

5492 For more information see https://redis.io/commands/geopos 

5493 """ 

5494 return self.execute_command("GEOPOS", name, *values, keys=[name]) 

5495 

5496 def georadius( 

5497 self, 

5498 name: KeyT, 

5499 longitude: float, 

5500 latitude: float, 

5501 radius: float, 

5502 unit: Union[str, None] = None, 

5503 withdist: bool = False, 

5504 withcoord: bool = False, 

5505 withhash: bool = False, 

5506 count: Union[int, None] = None, 

5507 sort: Union[str, None] = None, 

5508 store: Union[KeyT, None] = None, 

5509 store_dist: Union[KeyT, None] = None, 

5510 any: bool = False, 

5511 ) -> ResponseT: 

5512 """ 

5513 Return the members of the specified key identified by the 

5514 ``name`` argument which are within the borders of the area specified 

5515 with the ``latitude`` and ``longitude`` location and the maximum 

5516 distance from the center specified by the ``radius`` value. 

5517 

5518 The units must be one of the following : m, km mi, ft. By default 

5519 

5520 ``withdist`` indicates to return the distances of each place. 

5521 

5522 ``withcoord`` indicates to return the latitude and longitude of 

5523 each place. 

5524 

5525 ``withhash`` indicates to return the geohash string of each place. 

5526 

5527 ``count`` indicates to return the number of elements up to N. 

5528 

5529 ``sort`` indicates to return the places in a sorted way, ASC for 

5530 nearest to fairest and DESC for fairest to nearest. 

5531 

5532 ``store`` indicates to save the places names in a sorted set named 

5533 with a specific key, each element of the destination sorted set is 

5534 populated with the score got from the original geo sorted set. 

5535 

5536 ``store_dist`` indicates to save the places names in a sorted set 

5537 named with a specific key, instead of ``store`` the sorted set 

5538 destination score is set with the distance. 

5539 

5540 For more information see https://redis.io/commands/georadius 

5541 """ 

5542 return self._georadiusgeneric( 

5543 "GEORADIUS", 

5544 name, 

5545 longitude, 

5546 latitude, 

5547 radius, 

5548 unit=unit, 

5549 withdist=withdist, 

5550 withcoord=withcoord, 

5551 withhash=withhash, 

5552 count=count, 

5553 sort=sort, 

5554 store=store, 

5555 store_dist=store_dist, 

5556 any=any, 

5557 ) 

5558 

5559 def georadiusbymember( 

5560 self, 

5561 name: KeyT, 

5562 member: FieldT, 

5563 radius: float, 

5564 unit: Union[str, None] = None, 

5565 withdist: bool = False, 

5566 withcoord: bool = False, 

5567 withhash: bool = False, 

5568 count: Union[int, None] = None, 

5569 sort: Union[str, None] = None, 

5570 store: Union[KeyT, None] = None, 

5571 store_dist: Union[KeyT, None] = None, 

5572 any: bool = False, 

5573 ) -> ResponseT: 

5574 """ 

5575 This command is exactly like ``georadius`` with the sole difference 

5576 that instead of taking, as the center of the area to query, a longitude 

5577 and latitude value, it takes the name of a member already existing 

5578 inside the geospatial index represented by the sorted set. 

5579 

5580 For more information see https://redis.io/commands/georadiusbymember 

5581 """ 

5582 return self._georadiusgeneric( 

5583 "GEORADIUSBYMEMBER", 

5584 name, 

5585 member, 

5586 radius, 

5587 unit=unit, 

5588 withdist=withdist, 

5589 withcoord=withcoord, 

5590 withhash=withhash, 

5591 count=count, 

5592 sort=sort, 

5593 store=store, 

5594 store_dist=store_dist, 

5595 any=any, 

5596 ) 

5597 

5598 def _georadiusgeneric( 

5599 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

5600 ) -> ResponseT: 

5601 pieces = list(args) 

5602 if kwargs["unit"] and kwargs["unit"] not in ("m", "km", "mi", "ft"): 

5603 raise DataError("GEORADIUS invalid unit") 

5604 elif kwargs["unit"]: 

5605 pieces.append(kwargs["unit"]) 

5606 else: 

5607 pieces.append("m") 

5608 

5609 if kwargs["any"] and kwargs["count"] is None: 

5610 raise DataError("``any`` can't be provided without ``count``") 

5611 

5612 for arg_name, byte_repr in ( 

5613 ("withdist", "WITHDIST"), 

5614 ("withcoord", "WITHCOORD"), 

5615 ("withhash", "WITHHASH"), 

5616 ): 

5617 if kwargs[arg_name]: 

5618 pieces.append(byte_repr) 

5619 

5620 if kwargs["count"] is not None: 

5621 pieces.extend(["COUNT", kwargs["count"]]) 

5622 if kwargs["any"]: 

5623 pieces.append("ANY") 

5624 

5625 if kwargs["sort"]: 

5626 if kwargs["sort"] == "ASC": 

5627 pieces.append("ASC") 

5628 elif kwargs["sort"] == "DESC": 

5629 pieces.append("DESC") 

5630 else: 

5631 raise DataError("GEORADIUS invalid sort") 

5632 

5633 if kwargs["store"] and kwargs["store_dist"]: 

5634 raise DataError("GEORADIUS store and store_dist cant be set together") 

5635 

5636 if kwargs["store"]: 

5637 pieces.extend([b"STORE", kwargs["store"]]) 

5638 

5639 if kwargs["store_dist"]: 

5640 pieces.extend([b"STOREDIST", kwargs["store_dist"]]) 

5641 

5642 return self.execute_command(command, *pieces, **kwargs) 

5643 

5644 def geosearch( 

5645 self, 

5646 name: KeyT, 

5647 member: Union[FieldT, None] = None, 

5648 longitude: Union[float, None] = None, 

5649 latitude: Union[float, None] = None, 

5650 unit: str = "m", 

5651 radius: Union[float, None] = None, 

5652 width: Union[float, None] = None, 

5653 height: Union[float, None] = None, 

5654 sort: Union[str, None] = None, 

5655 count: Union[int, None] = None, 

5656 any: bool = False, 

5657 withcoord: bool = False, 

5658 withdist: bool = False, 

5659 withhash: bool = False, 

5660 ) -> ResponseT: 

5661 """ 

5662 Return the members of specified key identified by the 

5663 ``name`` argument, which are within the borders of the 

5664 area specified by a given shape. This command extends the 

5665 GEORADIUS command, so in addition to searching within circular 

5666 areas, it supports searching within rectangular areas. 

5667 

5668 This command should be used in place of the deprecated 

5669 GEORADIUS and GEORADIUSBYMEMBER commands. 

5670 

5671 ``member`` Use the position of the given existing 

5672 member in the sorted set. Can't be given with ``longitude`` 

5673 and ``latitude``. 

5674 

5675 ``longitude`` and ``latitude`` Use the position given by 

5676 this coordinates. Can't be given with ``member`` 

5677 ``radius`` Similar to GEORADIUS, search inside circular 

5678 area according the given radius. Can't be given with 

5679 ``height`` and ``width``. 

5680 ``height`` and ``width`` Search inside an axis-aligned 

5681 rectangle, determined by the given height and width. 

5682 Can't be given with ``radius`` 

5683 

5684 ``unit`` must be one of the following : m, km, mi, ft. 

5685 `m` for meters (the default value), `km` for kilometers, 

5686 `mi` for miles and `ft` for feet. 

5687 

5688 ``sort`` indicates to return the places in a sorted way, 

5689 ASC for nearest to furthest and DESC for furthest to nearest. 

5690 

5691 ``count`` limit the results to the first count matching items. 

5692 

5693 ``any`` is set to True, the command will return as soon as 

5694 enough matches are found. Can't be provided without ``count`` 

5695 

5696 ``withdist`` indicates to return the distances of each place. 

5697 ``withcoord`` indicates to return the latitude and longitude of 

5698 each place. 

5699 

5700 ``withhash`` indicates to return the geohash string of each place. 

5701 

5702 For more information see https://redis.io/commands/geosearch 

5703 """ 

5704 

5705 return self._geosearchgeneric( 

5706 "GEOSEARCH", 

5707 name, 

5708 member=member, 

5709 longitude=longitude, 

5710 latitude=latitude, 

5711 unit=unit, 

5712 radius=radius, 

5713 width=width, 

5714 height=height, 

5715 sort=sort, 

5716 count=count, 

5717 any=any, 

5718 withcoord=withcoord, 

5719 withdist=withdist, 

5720 withhash=withhash, 

5721 store=None, 

5722 store_dist=None, 

5723 ) 

5724 

5725 def geosearchstore( 

5726 self, 

5727 dest: KeyT, 

5728 name: KeyT, 

5729 member: Union[FieldT, None] = None, 

5730 longitude: Union[float, None] = None, 

5731 latitude: Union[float, None] = None, 

5732 unit: str = "m", 

5733 radius: Union[float, None] = None, 

5734 width: Union[float, None] = None, 

5735 height: Union[float, None] = None, 

5736 sort: Union[str, None] = None, 

5737 count: Union[int, None] = None, 

5738 any: bool = False, 

5739 storedist: bool = False, 

5740 ) -> ResponseT: 

5741 """ 

5742 This command is like GEOSEARCH, but stores the result in 

5743 ``dest``. By default, it stores the results in the destination 

5744 sorted set with their geospatial information. 

5745 if ``store_dist`` set to True, the command will stores the 

5746 items in a sorted set populated with their distance from the 

5747 center of the circle or box, as a floating-point number. 

5748 

5749 For more information see https://redis.io/commands/geosearchstore 

5750 """ 

5751 return self._geosearchgeneric( 

5752 "GEOSEARCHSTORE", 

5753 dest, 

5754 name, 

5755 member=member, 

5756 longitude=longitude, 

5757 latitude=latitude, 

5758 unit=unit, 

5759 radius=radius, 

5760 width=width, 

5761 height=height, 

5762 sort=sort, 

5763 count=count, 

5764 any=any, 

5765 withcoord=None, 

5766 withdist=None, 

5767 withhash=None, 

5768 store=None, 

5769 store_dist=storedist, 

5770 ) 

5771 

5772 def _geosearchgeneric( 

5773 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

5774 ) -> ResponseT: 

5775 pieces = list(args) 

5776 

5777 # FROMMEMBER or FROMLONLAT 

5778 if kwargs["member"] is None: 

5779 if kwargs["longitude"] is None or kwargs["latitude"] is None: 

5780 raise DataError("GEOSEARCH must have member or longitude and latitude") 

5781 if kwargs["member"]: 

5782 if kwargs["longitude"] or kwargs["latitude"]: 

5783 raise DataError( 

5784 "GEOSEARCH member and longitude or latitude cant be set together" 

5785 ) 

5786 pieces.extend([b"FROMMEMBER", kwargs["member"]]) 

5787 if kwargs["longitude"] is not None and kwargs["latitude"] is not None: 

5788 pieces.extend([b"FROMLONLAT", kwargs["longitude"], kwargs["latitude"]]) 

5789 

5790 # BYRADIUS or BYBOX 

5791 if kwargs["radius"] is None: 

5792 if kwargs["width"] is None or kwargs["height"] is None: 

5793 raise DataError("GEOSEARCH must have radius or width and height") 

5794 if kwargs["unit"] is None: 

5795 raise DataError("GEOSEARCH must have unit") 

5796 if kwargs["unit"].lower() not in ("m", "km", "mi", "ft"): 

5797 raise DataError("GEOSEARCH invalid unit") 

5798 if kwargs["radius"]: 

5799 if kwargs["width"] or kwargs["height"]: 

5800 raise DataError( 

5801 "GEOSEARCH radius and width or height cant be set together" 

5802 ) 

5803 pieces.extend([b"BYRADIUS", kwargs["radius"], kwargs["unit"]]) 

5804 if kwargs["width"] and kwargs["height"]: 

5805 pieces.extend([b"BYBOX", kwargs["width"], kwargs["height"], kwargs["unit"]]) 

5806 

5807 # sort 

5808 if kwargs["sort"]: 

5809 if kwargs["sort"].upper() == "ASC": 

5810 pieces.append(b"ASC") 

5811 elif kwargs["sort"].upper() == "DESC": 

5812 pieces.append(b"DESC") 

5813 else: 

5814 raise DataError("GEOSEARCH invalid sort") 

5815 

5816 # count any 

5817 if kwargs["count"]: 

5818 pieces.extend([b"COUNT", kwargs["count"]]) 

5819 if kwargs["any"]: 

5820 pieces.append(b"ANY") 

5821 elif kwargs["any"]: 

5822 raise DataError("GEOSEARCH ``any`` can't be provided without count") 

5823 

5824 # other properties 

5825 for arg_name, byte_repr in ( 

5826 ("withdist", b"WITHDIST"), 

5827 ("withcoord", b"WITHCOORD"), 

5828 ("withhash", b"WITHHASH"), 

5829 ("store_dist", b"STOREDIST"), 

5830 ): 

5831 if kwargs[arg_name]: 

5832 pieces.append(byte_repr) 

5833 

5834 kwargs["keys"] = [args[0] if command == "GEOSEARCH" else args[1]] 

5835 

5836 return self.execute_command(command, *pieces, **kwargs) 

5837 

5838 

5839AsyncGeoCommands = GeoCommands 

5840 

5841 

5842class ModuleCommands(CommandsProtocol): 

5843 """ 

5844 Redis Module commands. 

5845 see: https://redis.io/topics/modules-intro 

5846 """ 

5847 

5848 def module_load(self, path, *args) -> ResponseT: 

5849 """ 

5850 Loads the module from ``path``. 

5851 Passes all ``*args`` to the module, during loading. 

5852 Raises ``ModuleError`` if a module is not found at ``path``. 

5853 

5854 For more information see https://redis.io/commands/module-load 

5855 """ 

5856 return self.execute_command("MODULE LOAD", path, *args) 

5857 

5858 def module_loadex( 

5859 self, 

5860 path: str, 

5861 options: Optional[List[str]] = None, 

5862 args: Optional[List[str]] = None, 

5863 ) -> ResponseT: 

5864 """ 

5865 Loads a module from a dynamic library at runtime with configuration directives. 

5866 

5867 For more information see https://redis.io/commands/module-loadex 

5868 """ 

5869 pieces = [] 

5870 if options is not None: 

5871 pieces.append("CONFIG") 

5872 pieces.extend(options) 

5873 if args is not None: 

5874 pieces.append("ARGS") 

5875 pieces.extend(args) 

5876 

5877 return self.execute_command("MODULE LOADEX", path, *pieces) 

5878 

5879 def module_unload(self, name) -> ResponseT: 

5880 """ 

5881 Unloads the module ``name``. 

5882 Raises ``ModuleError`` if ``name`` is not in loaded modules. 

5883 

5884 For more information see https://redis.io/commands/module-unload 

5885 """ 

5886 return self.execute_command("MODULE UNLOAD", name) 

5887 

5888 def module_list(self) -> ResponseT: 

5889 """ 

5890 Returns a list of dictionaries containing the name and version of 

5891 all loaded modules. 

5892 

5893 For more information see https://redis.io/commands/module-list 

5894 """ 

5895 return self.execute_command("MODULE LIST") 

5896 

5897 def command_info(self) -> None: 

5898 raise NotImplementedError( 

5899 "COMMAND INFO is intentionally not implemented in the client." 

5900 ) 

5901 

5902 def command_count(self) -> ResponseT: 

5903 return self.execute_command("COMMAND COUNT") 

5904 

5905 def command_getkeys(self, *args) -> ResponseT: 

5906 return self.execute_command("COMMAND GETKEYS", *args) 

5907 

5908 def command(self) -> ResponseT: 

5909 return self.execute_command("COMMAND") 

5910 

5911 

5912class Script: 

5913 """ 

5914 An executable Lua script object returned by ``register_script`` 

5915 """ 

5916 

5917 def __init__(self, registered_client, script): 

5918 self.registered_client = registered_client 

5919 self.script = script 

5920 # Precalculate and store the SHA1 hex digest of the script. 

5921 

5922 if isinstance(script, str): 

5923 # We need the encoding from the client in order to generate an 

5924 # accurate byte representation of the script 

5925 encoder = self.get_encoder() 

5926 script = encoder.encode(script) 

5927 self.sha = hashlib.sha1(script).hexdigest() 

5928 

5929 def __call__(self, keys=[], args=[], client=None): 

5930 "Execute the script, passing any required ``args``" 

5931 if client is None: 

5932 client = self.registered_client 

5933 args = tuple(keys) + tuple(args) 

5934 # make sure the Redis server knows about the script 

5935 from redis.client import Pipeline 

5936 

5937 if isinstance(client, Pipeline): 

5938 # Make sure the pipeline can register the script before executing. 

5939 client.scripts.add(self) 

5940 try: 

5941 return client.evalsha(self.sha, len(keys), *args) 

5942 except NoScriptError: 

5943 # Maybe the client is pointed to a different server than the client 

5944 # that created this instance? 

5945 # Overwrite the sha just in case there was a discrepancy. 

5946 self.sha = client.script_load(self.script) 

5947 return client.evalsha(self.sha, len(keys), *args) 

5948 

5949 def get_encoder(self): 

5950 """Get the encoder to encode string scripts into bytes.""" 

5951 try: 

5952 return self.registered_client.get_encoder() 

5953 except AttributeError: 

5954 # DEPRECATED 

5955 # In version <=4.1.2, this was the code we used to get the encoder. 

5956 # However, after 4.1.2 we added support for scripting in clustered 

5957 # redis. ClusteredRedis doesn't have a `.connection_pool` attribute 

5958 # so we changed the Script class to use 

5959 # `self.registered_client.get_encoder` (see above). 

5960 # However, that is technically a breaking change, as consumers who 

5961 # use Scripts directly might inject a `registered_client` that 

5962 # doesn't have a `.get_encoder` field. This try/except prevents us 

5963 # from breaking backward-compatibility. Ideally, it would be 

5964 # removed in the next major release. 

5965 return self.registered_client.connection_pool.get_encoder() 

5966 

5967 

5968class AsyncModuleCommands(ModuleCommands): 

5969 async def command_info(self) -> None: 

5970 return super().command_info() 

5971 

5972 

5973class ClusterCommands(CommandsProtocol): 

5974 """ 

5975 Class for Redis Cluster commands 

5976 """ 

5977 

5978 def cluster(self, cluster_arg, *args, **kwargs) -> ResponseT: 

5979 return self.execute_command(f"CLUSTER {cluster_arg.upper()}", *args, **kwargs) 

5980 

5981 def readwrite(self, **kwargs) -> ResponseT: 

5982 """ 

5983 Disables read queries for a connection to a Redis Cluster slave node. 

5984 

5985 For more information see https://redis.io/commands/readwrite 

5986 """ 

5987 return self.execute_command("READWRITE", **kwargs) 

5988 

5989 def readonly(self, **kwargs) -> ResponseT: 

5990 """ 

5991 Enables read queries for a connection to a Redis Cluster replica node. 

5992 

5993 For more information see https://redis.io/commands/readonly 

5994 """ 

5995 return self.execute_command("READONLY", **kwargs) 

5996 

5997 

5998AsyncClusterCommands = ClusterCommands 

5999 

6000 

6001class FunctionCommands: 

6002 """ 

6003 Redis Function commands 

6004 """ 

6005 

6006 def function_load( 

6007 self, code: str, replace: Optional[bool] = False 

6008 ) -> Union[Awaitable[str], str]: 

6009 """ 

6010 Load a library to Redis. 

6011 :param code: the source code (must start with 

6012 Shebang statement that provides a metadata about the library) 

6013 :param replace: changes the behavior to overwrite the existing library 

6014 with the new contents. 

6015 Return the library name that was loaded. 

6016 

6017 For more information see https://redis.io/commands/function-load 

6018 """ 

6019 pieces = ["REPLACE"] if replace else [] 

6020 pieces.append(code) 

6021 return self.execute_command("FUNCTION LOAD", *pieces) 

6022 

6023 def function_delete(self, library: str) -> Union[Awaitable[str], str]: 

6024 """ 

6025 Delete the library called ``library`` and all its functions. 

6026 

6027 For more information see https://redis.io/commands/function-delete 

6028 """ 

6029 return self.execute_command("FUNCTION DELETE", library) 

6030 

6031 def function_flush(self, mode: str = "SYNC") -> Union[Awaitable[str], str]: 

6032 """ 

6033 Deletes all the libraries. 

6034 

6035 For more information see https://redis.io/commands/function-flush 

6036 """ 

6037 return self.execute_command("FUNCTION FLUSH", mode) 

6038 

6039 def function_list( 

6040 self, library: Optional[str] = "*", withcode: Optional[bool] = False 

6041 ) -> Union[Awaitable[List], List]: 

6042 """ 

6043 Return information about the functions and libraries. 

6044 :param library: pecify a pattern for matching library names 

6045 :param withcode: cause the server to include the libraries source 

6046 implementation in the reply 

6047 """ 

6048 args = ["LIBRARYNAME", library] 

6049 if withcode: 

6050 args.append("WITHCODE") 

6051 return self.execute_command("FUNCTION LIST", *args) 

6052 

6053 def _fcall( 

6054 self, command: str, function, numkeys: int, *keys_and_args: Optional[List] 

6055 ) -> Union[Awaitable[str], str]: 

6056 return self.execute_command(command, function, numkeys, *keys_and_args) 

6057 

6058 def fcall( 

6059 self, function, numkeys: int, *keys_and_args: Optional[List] 

6060 ) -> Union[Awaitable[str], str]: 

6061 """ 

6062 Invoke a function. 

6063 

6064 For more information see https://redis.io/commands/fcall 

6065 """ 

6066 return self._fcall("FCALL", function, numkeys, *keys_and_args) 

6067 

6068 def fcall_ro( 

6069 self, function, numkeys: int, *keys_and_args: Optional[List] 

6070 ) -> Union[Awaitable[str], str]: 

6071 """ 

6072 This is a read-only variant of the FCALL command that cannot 

6073 execute commands that modify data. 

6074 

6075 For more information see https://redis.io/commands/fcal_ro 

6076 """ 

6077 return self._fcall("FCALL_RO", function, numkeys, *keys_and_args) 

6078 

6079 def function_dump(self) -> Union[Awaitable[str], str]: 

6080 """ 

6081 Return the serialized payload of loaded libraries. 

6082 

6083 For more information see https://redis.io/commands/function-dump 

6084 """ 

6085 from redis.client import NEVER_DECODE 

6086 

6087 options = {} 

6088 options[NEVER_DECODE] = [] 

6089 

6090 return self.execute_command("FUNCTION DUMP", **options) 

6091 

6092 def function_restore( 

6093 self, payload: str, policy: Optional[str] = "APPEND" 

6094 ) -> Union[Awaitable[str], str]: 

6095 """ 

6096 Restore libraries from the serialized ``payload``. 

6097 You can use the optional policy argument to provide a policy 

6098 for handling existing libraries. 

6099 

6100 For more information see https://redis.io/commands/function-restore 

6101 """ 

6102 return self.execute_command("FUNCTION RESTORE", payload, policy) 

6103 

6104 def function_kill(self) -> Union[Awaitable[str], str]: 

6105 """ 

6106 Kill a function that is currently executing. 

6107 

6108 For more information see https://redis.io/commands/function-kill 

6109 """ 

6110 return self.execute_command("FUNCTION KILL") 

6111 

6112 def function_stats(self) -> Union[Awaitable[List], List]: 

6113 """ 

6114 Return information about the function that's currently running 

6115 and information about the available execution engines. 

6116 

6117 For more information see https://redis.io/commands/function-stats 

6118 """ 

6119 return self.execute_command("FUNCTION STATS") 

6120 

6121 

6122AsyncFunctionCommands = FunctionCommands 

6123 

6124 

6125class GearsCommands: 

6126 def tfunction_load( 

6127 self, lib_code: str, replace: bool = False, config: Union[str, None] = None 

6128 ) -> ResponseT: 

6129 """ 

6130 Load a new library to RedisGears. 

6131 

6132 ``lib_code`` - the library code. 

6133 ``config`` - a string representation of a JSON object 

6134 that will be provided to the library on load time, 

6135 for more information refer to 

6136 https://github.com/RedisGears/RedisGears/blob/master/docs/function_advance_topics.md#library-configuration 

6137 ``replace`` - an optional argument, instructs RedisGears to replace the 

6138 function if its already exists 

6139 

6140 For more information see https://redis.io/commands/tfunction-load/ 

6141 """ 

6142 pieces = [] 

6143 if replace: 

6144 pieces.append("REPLACE") 

6145 if config is not None: 

6146 pieces.extend(["CONFIG", config]) 

6147 pieces.append(lib_code) 

6148 return self.execute_command("TFUNCTION LOAD", *pieces) 

6149 

6150 def tfunction_delete(self, lib_name: str) -> ResponseT: 

6151 """ 

6152 Delete a library from RedisGears. 

6153 

6154 ``lib_name`` the library name to delete. 

6155 

6156 For more information see https://redis.io/commands/tfunction-delete/ 

6157 """ 

6158 return self.execute_command("TFUNCTION DELETE", lib_name) 

6159 

6160 def tfunction_list( 

6161 self, 

6162 with_code: bool = False, 

6163 verbose: int = 0, 

6164 lib_name: Union[str, None] = None, 

6165 ) -> ResponseT: 

6166 """ 

6167 List the functions with additional information about each function. 

6168 

6169 ``with_code`` Show libraries code. 

6170 ``verbose`` output verbosity level, higher number will increase verbosity level 

6171 ``lib_name`` specifying a library name (can be used multiple times to show multiple libraries in a single command) # noqa 

6172 

6173 For more information see https://redis.io/commands/tfunction-list/ 

6174 """ 

6175 pieces = [] 

6176 if with_code: 

6177 pieces.append("WITHCODE") 

6178 if verbose >= 1 and verbose <= 3: 

6179 pieces.append("v" * verbose) 

6180 else: 

6181 raise DataError("verbose can be 1, 2 or 3") 

6182 if lib_name is not None: 

6183 pieces.append("LIBRARY") 

6184 pieces.append(lib_name) 

6185 

6186 return self.execute_command("TFUNCTION LIST", *pieces) 

6187 

6188 def _tfcall( 

6189 self, 

6190 lib_name: str, 

6191 func_name: str, 

6192 keys: KeysT = None, 

6193 _async: bool = False, 

6194 *args: List, 

6195 ) -> ResponseT: 

6196 pieces = [f"{lib_name}.{func_name}"] 

6197 if keys is not None: 

6198 pieces.append(len(keys)) 

6199 pieces.extend(keys) 

6200 else: 

6201 pieces.append(0) 

6202 if args is not None: 

6203 pieces.extend(args) 

6204 if _async: 

6205 return self.execute_command("TFCALLASYNC", *pieces) 

6206 return self.execute_command("TFCALL", *pieces) 

6207 

6208 def tfcall( 

6209 self, 

6210 lib_name: str, 

6211 func_name: str, 

6212 keys: KeysT = None, 

6213 *args: List, 

6214 ) -> ResponseT: 

6215 """ 

6216 Invoke a function. 

6217 

6218 ``lib_name`` - the library name contains the function. 

6219 ``func_name`` - the function name to run. 

6220 ``keys`` - the keys that will be touched by the function. 

6221 ``args`` - Additional argument to pass to the function. 

6222 

6223 For more information see https://redis.io/commands/tfcall/ 

6224 """ 

6225 return self._tfcall(lib_name, func_name, keys, False, *args) 

6226 

6227 def tfcall_async( 

6228 self, 

6229 lib_name: str, 

6230 func_name: str, 

6231 keys: KeysT = None, 

6232 *args: List, 

6233 ) -> ResponseT: 

6234 """ 

6235 Invoke an async function (coroutine). 

6236 

6237 ``lib_name`` - the library name contains the function. 

6238 ``func_name`` - the function name to run. 

6239 ``keys`` - the keys that will be touched by the function. 

6240 ``args`` - Additional argument to pass to the function. 

6241 

6242 For more information see https://redis.io/commands/tfcall/ 

6243 """ 

6244 return self._tfcall(lib_name, func_name, keys, True, *args) 

6245 

6246 

6247AsyncGearsCommands = GearsCommands 

6248 

6249 

6250class DataAccessCommands( 

6251 BasicKeyCommands, 

6252 HyperlogCommands, 

6253 HashCommands, 

6254 GeoCommands, 

6255 ListCommands, 

6256 ScanCommands, 

6257 SetCommands, 

6258 StreamCommands, 

6259 SortedSetCommands, 

6260): 

6261 """ 

6262 A class containing all of the implemented data access redis commands. 

6263 This class is to be used as a mixin for synchronous Redis clients. 

6264 """ 

6265 

6266 

6267class AsyncDataAccessCommands( 

6268 AsyncBasicKeyCommands, 

6269 AsyncHyperlogCommands, 

6270 AsyncHashCommands, 

6271 AsyncGeoCommands, 

6272 AsyncListCommands, 

6273 AsyncScanCommands, 

6274 AsyncSetCommands, 

6275 AsyncStreamCommands, 

6276 AsyncSortedSetCommands, 

6277): 

6278 """ 

6279 A class containing all of the implemented data access redis commands. 

6280 This class is to be used as a mixin for asynchronous Redis clients. 

6281 """ 

6282 

6283 

6284class CoreCommands( 

6285 ACLCommands, 

6286 ClusterCommands, 

6287 DataAccessCommands, 

6288 ManagementCommands, 

6289 ModuleCommands, 

6290 PubSubCommands, 

6291 ScriptCommands, 

6292 FunctionCommands, 

6293 GearsCommands, 

6294): 

6295 """ 

6296 A class containing all of the implemented redis commands. This class is 

6297 to be used as a mixin for synchronous Redis clients. 

6298 """ 

6299 

6300 

6301class AsyncCoreCommands( 

6302 AsyncACLCommands, 

6303 AsyncClusterCommands, 

6304 AsyncDataAccessCommands, 

6305 AsyncManagementCommands, 

6306 AsyncModuleCommands, 

6307 AsyncPubSubCommands, 

6308 AsyncScriptCommands, 

6309 AsyncFunctionCommands, 

6310 AsyncGearsCommands, 

6311): 

6312 """ 

6313 A class containing all of the implemented redis commands. This class is 

6314 to be used as a mixin for asynchronous Redis clients. 

6315 """