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

1804 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 07:09 +0000

1# from __future__ import annotations 

2 

3import datetime 

4import hashlib 

5import warnings 

6from typing import ( 

7 TYPE_CHECKING, 

8 Any, 

9 AsyncIterator, 

10 Awaitable, 

11 Callable, 

12 Dict, 

13 Iterable, 

14 Iterator, 

15 List, 

16 Mapping, 

17 Optional, 

18 Sequence, 

19 Set, 

20 Tuple, 

21 Union, 

22) 

23 

24from redis.compat import Literal 

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

26from redis.typing import ( 

27 AbsExpiryT, 

28 AnyKeyT, 

29 BitfieldOffsetT, 

30 ChannelT, 

31 CommandsProtocol, 

32 ConsumerT, 

33 EncodableT, 

34 ExpiryT, 

35 FieldT, 

36 GroupT, 

37 KeysT, 

38 KeyT, 

39 PatternT, 

40 ScriptTextT, 

41 StreamIdT, 

42 TimeoutSecT, 

43 ZScoreBoundT, 

44) 

45 

46from .helpers import list_or_args 

47 

48if TYPE_CHECKING: 

49 from redis.asyncio.client import Redis as AsyncRedis 

50 from redis.client import Redis 

51 

52ResponseT = Union[Awaitable, Any] 

53 

54 

55class ACLCommands(CommandsProtocol): 

56 """ 

57 Redis Access Control List (ACL) commands. 

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

59 """ 

60 

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

62 """ 

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

64 

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

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

67 that category. 

68 

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

70 """ 

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

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

73 

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

75 """ 

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

77 

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

79 """ 

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

81 

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

83 """ 

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

85 

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

87 """ 

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

89 

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

91 """Generate a random password value. 

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

93 the next multiple of 4. 

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

95 """ 

96 pieces = [] 

97 if bits is not None: 

98 try: 

99 b = int(bits) 

100 if b < 0 or b > 4096: 

101 raise ValueError 

102 except ValueError: 

103 raise DataError( 

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

105 ) 

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

107 

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

109 """ 

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

111 

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

113 

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

115 """ 

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

117 

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

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

120 the different subcommands. 

121 

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

123 """ 

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

125 

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

127 """ 

128 Return a list of all ACLs on the server 

129 

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

131 """ 

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

133 

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

135 """ 

136 Get ACL logs as a list. 

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

138 :rtype: List. 

139 

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

141 """ 

142 args = [] 

143 if count is not None: 

144 if not isinstance(count, int): 

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

146 args.append(count) 

147 

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

149 

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

151 """ 

152 Reset ACL logs. 

153 :rtype: Boolean. 

154 

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

156 """ 

157 args = [b"RESET"] 

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

159 

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

161 """ 

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

163 

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

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

166 

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

168 """ 

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

170 

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

172 """ 

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

174 

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

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

177 

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

179 """ 

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

181 

182 def acl_setuser( 

183 self, 

184 username: str, 

185 enabled: bool = False, 

186 nopass: bool = False, 

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

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

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

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

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

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

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

194 reset: bool = False, 

195 reset_keys: bool = False, 

196 reset_channels: bool = False, 

197 reset_passwords: bool = False, 

198 **kwargs, 

199 ) -> ResponseT: 

200 """ 

201 Create or update an ACL user. 

202 

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

204 the existing ACL is completely overwritten and replaced with the 

205 specified values. 

206 

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

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

209 

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

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

212 

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

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

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

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

217 removing a single password. 

218 

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

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

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

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

223 adding or removing a single password. 

224 

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

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

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

228 

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

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

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

232 

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

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

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

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

237 prefixed with a '~'. 

238 

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

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

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

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

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

244 rules will be applied on top. 

245 

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

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

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

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

250 will be applied on top. 

251 

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

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

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

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

256 will be applied on top. 

257 

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

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

260 applying any new passwords specified in 'passwords' or 

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

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

263 or hashed_passwords will be applied on top. 

264 

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

266 """ 

267 encoder = self.get_encoder() 

268 pieces: List[EncodableT] = [username] 

269 

270 if reset: 

271 pieces.append(b"reset") 

272 

273 if reset_keys: 

274 pieces.append(b"resetkeys") 

275 

276 if reset_channels: 

277 pieces.append(b"resetchannels") 

278 

279 if reset_passwords: 

280 pieces.append(b"resetpass") 

281 

282 if enabled: 

283 pieces.append(b"on") 

284 else: 

285 pieces.append(b"off") 

286 

287 if (passwords or hashed_passwords) and nopass: 

288 raise DataError( 

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

290 ) 

291 

292 if passwords: 

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

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

295 passwords = list_or_args(passwords, []) 

296 for i, password in enumerate(passwords): 

297 password = encoder.encode(password) 

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

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

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

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

302 else: 

303 raise DataError( 

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

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

306 ) 

307 

308 if hashed_passwords: 

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

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

311 hashed_passwords = list_or_args(hashed_passwords, []) 

312 for i, hashed_password in enumerate(hashed_passwords): 

313 hashed_password = encoder.encode(hashed_password) 

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

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

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

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

318 else: 

319 raise DataError( 

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

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

322 ) 

323 

324 if nopass: 

325 pieces.append(b"nopass") 

326 

327 if categories: 

328 for category in categories: 

329 category = encoder.encode(category) 

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

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

332 pieces.append(category) 

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

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

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

336 pieces.append(category) 

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

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

339 else: 

340 raise DataError( 

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

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

343 ) 

344 if commands: 

345 for cmd in commands: 

346 cmd = encoder.encode(cmd) 

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

348 raise DataError( 

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

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

351 ) 

352 pieces.append(cmd) 

353 

354 if keys: 

355 for key in keys: 

356 key = encoder.encode(key) 

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

358 key = b"~%s" % key 

359 pieces.append(key) 

360 

361 if channels: 

362 for channel in channels: 

363 channel = encoder.encode(channel) 

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

365 

366 if selectors: 

367 for cmd, key in selectors: 

368 cmd = encoder.encode(cmd) 

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

370 raise DataError( 

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

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

373 ) 

374 

375 key = encoder.encode(key) 

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

377 key = b"~%s" % key 

378 

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

380 

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

382 

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

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

385 

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

387 """ 

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

389 

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

391 """Get the username for the current connection 

392 

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

394 """ 

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

396 

397 

398AsyncACLCommands = ACLCommands 

399 

400 

401class ManagementCommands(CommandsProtocol): 

402 """ 

403 Redis management commands 

404 """ 

405 

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

407 """ 

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

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

410 authenticate for the given user. 

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

412 """ 

413 pieces = [] 

414 if username is not None: 

415 pieces.append(username) 

416 pieces.append(password) 

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

418 

419 def bgrewriteaof(self, **kwargs): 

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

421 

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

423 """ 

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

425 

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

427 """ 

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

429 this method is asynchronous and returns immediately. 

430 

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

432 """ 

433 pieces = [] 

434 if schedule: 

435 pieces.append("SCHEDULE") 

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

437 

438 def role(self) -> ResponseT: 

439 """ 

440 Provide information on the role of a Redis instance in 

441 the context of replication, by returning if the instance 

442 is currently a master, slave, or sentinel. 

443 

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

445 """ 

446 return self.execute_command("ROLE") 

447 

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

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

450 

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

452 """ 

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

454 

455 def client_kill_filter( 

456 self, 

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

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

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

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

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

462 user: str = None, 

463 **kwargs, 

464 ) -> ResponseT: 

465 """ 

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

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

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

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

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

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

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

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

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

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

476 """ 

477 args = [] 

478 if _type is not None: 

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

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

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

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

483 if skipme is not None: 

484 if not isinstance(skipme, bool): 

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

486 if skipme: 

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

488 else: 

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

490 if _id is not None: 

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

492 if addr is not None: 

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

494 if laddr is not None: 

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

496 if user is not None: 

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

498 if not args: 

499 raise DataError( 

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

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

502 ) 

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

504 

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

506 """ 

507 Returns information and statistics about the current 

508 client connection. 

509 

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

511 """ 

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

513 

514 def client_list( 

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

516 ) -> ResponseT: 

517 """ 

518 Returns a list of currently connected clients. 

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

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

521 replica, pubsub) 

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

523 

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

525 """ 

526 args = [] 

527 if _type is not None: 

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

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

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

531 args.append(b"TYPE") 

532 args.append(_type) 

533 if not isinstance(client_id, list): 

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

535 if client_id: 

536 args.append(b"ID") 

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

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

539 

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

541 """ 

542 Returns the current connection name 

543 

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

545 """ 

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

547 

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

549 """ 

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

551 redirecting tracking notifications. 

552 

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

554 """ 

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

556 

557 def client_reply( 

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

559 ) -> ResponseT: 

560 """ 

561 Enable and disable redis server replies. 

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

563 ON - The default most with server replies to commands 

564 OFF - Disable server responses to commands 

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

566 

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

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

569 TimeoutError. 

570 The test_client_reply unit test illustrates this, and 

571 conftest.py has a client with a timeout. 

572 

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

574 """ 

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

576 if reply not in replies: 

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

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

579 

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

581 """ 

582 Returns the current connection id 

583 

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

585 """ 

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

587 

588 def client_tracking_on( 

589 self, 

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

591 prefix: Sequence[KeyT] = [], 

592 bcast: bool = False, 

593 optin: bool = False, 

594 optout: bool = False, 

595 noloop: bool = False, 

596 ) -> ResponseT: 

597 """ 

598 Turn on the tracking mode. 

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

600 

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

602 """ 

603 return self.client_tracking( 

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

605 ) 

606 

607 def client_tracking_off( 

608 self, 

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

610 prefix: Sequence[KeyT] = [], 

611 bcast: bool = False, 

612 optin: bool = False, 

613 optout: bool = False, 

614 noloop: bool = False, 

615 ) -> ResponseT: 

616 """ 

617 Turn off the tracking mode. 

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

619 

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

621 """ 

622 return self.client_tracking( 

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

624 ) 

625 

626 def client_tracking( 

627 self, 

628 on: bool = True, 

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

630 prefix: Sequence[KeyT] = [], 

631 bcast: bool = False, 

632 optin: bool = False, 

633 optout: bool = False, 

634 noloop: bool = False, 

635 **kwargs, 

636 ) -> ResponseT: 

637 """ 

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

639 for server assisted client side caching. 

640 

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

642 

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

644 the specified ID. 

645 

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

647 invalidation messages are reported for all the prefixes 

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

649 

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

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

652 after a CLIENT CACHING yes command. 

653 

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

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

656 CLIENT CACHING no command. 

657 

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

659 connection itself. 

660 

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

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

663 

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

665 """ 

666 

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

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

669 

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

671 if clientid is not None: 

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

673 for p in prefix: 

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

675 if bcast: 

676 pieces.append("BCAST") 

677 if optin: 

678 pieces.append("OPTIN") 

679 if optout: 

680 pieces.append("OPTOUT") 

681 if noloop: 

682 pieces.append("NOLOOP") 

683 

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

685 

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

687 """ 

688 Returns the information about the current client connection's 

689 use of the server assisted client side cache. 

690 

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

692 """ 

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

694 

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

696 """ 

697 Sets the current connection name 

698 

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

700 

701 .. note:: 

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

703 

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

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

706 """ 

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

708 

709 def client_unblock( 

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

711 ) -> ResponseT: 

712 """ 

713 Unblocks a connection by its client id. 

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

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

716 regular timeout mechanism. 

717 

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

719 """ 

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

721 if error: 

722 args.append(b"ERROR") 

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

724 

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

726 """ 

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

728 :param timeout: milliseconds to pause clients 

729 

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

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

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

733 a write command. 

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

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

736 PUBLISH: Will block client. 

737 PFCOUNT: Will block client. 

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

739 appear blocked. 

740 """ 

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

742 if not isinstance(timeout, int): 

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

744 if not all: 

745 args.append("WRITE") 

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

747 

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

749 """ 

750 Unpause all redis clients 

751 

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

753 """ 

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

755 

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

757 """ 

758 Sets the client eviction mode for the current connection. 

759 

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

761 """ 

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

763 

764 def command(self, **kwargs): 

765 """ 

766 Returns dict reply of details about all Redis commands. 

767 

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

769 """ 

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

771 

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

773 raise NotImplementedError( 

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

775 ) 

776 

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

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

779 

780 def command_list( 

781 self, 

782 module: Optional[str] = None, 

783 category: Optional[str] = None, 

784 pattern: Optional[str] = None, 

785 ) -> ResponseT: 

786 """ 

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

788 You can use one of the following filters: 

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

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

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

792 

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

794 """ 

795 pieces = [] 

796 if module is not None: 

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

798 if category is not None: 

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

800 if pattern is not None: 

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

802 

803 if pieces: 

804 pieces.insert(0, "FILTERBY") 

805 

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

807 

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

809 """ 

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

811 

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

813 """ 

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

815 

816 def command_docs(self, *args): 

817 """ 

818 This function throws a NotImplementedError since it is intentionally 

819 not supported. 

820 """ 

821 raise NotImplementedError( 

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

823 ) 

824 

825 def config_get( 

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

827 ) -> ResponseT: 

828 """ 

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

830 

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

832 """ 

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

834 

835 def config_set( 

836 self, 

837 name: KeyT, 

838 value: EncodableT, 

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

840 **kwargs, 

841 ) -> ResponseT: 

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

843 

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

845 """ 

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

847 

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

849 """ 

850 Reset runtime statistics 

851 

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

853 """ 

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

855 

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

857 """ 

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

859 

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

861 """ 

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

863 

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

865 """ 

866 Returns the number of keys in the current database 

867 

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

869 """ 

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

871 

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

873 """ 

874 Returns version specific meta information about a given key 

875 

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

877 """ 

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

879 

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

881 raise NotImplementedError( 

882 """ 

883 DEBUG SEGFAULT is intentionally not implemented in the client. 

884 

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

886 """ 

887 ) 

888 

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

890 """ 

891 Echo the string back from the server 

892 

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

894 """ 

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

896 

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

898 """ 

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

900 

901 ``asynchronous`` indicates whether the operation is 

902 executed asynchronously by the server. 

903 

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

905 """ 

906 args = [] 

907 if asynchronous: 

908 args.append(b"ASYNC") 

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

910 

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

912 """ 

913 Delete all keys in the current database. 

914 

915 ``asynchronous`` indicates whether the operation is 

916 executed asynchronously by the server. 

917 

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

919 """ 

920 args = [] 

921 if asynchronous: 

922 args.append(b"ASYNC") 

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

924 

925 def sync(self) -> ResponseT: 

926 """ 

927 Initiates a replication stream from the master. 

928 

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

930 """ 

931 from redis.client import NEVER_DECODE 

932 

933 options = {} 

934 options[NEVER_DECODE] = [] 

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

936 

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

938 """ 

939 Initiates a replication stream from the master. 

940 Newer version for `sync`. 

941 

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

943 """ 

944 from redis.client import NEVER_DECODE 

945 

946 options = {} 

947 options[NEVER_DECODE] = [] 

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

949 

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

951 """ 

952 Swap two databases 

953 

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

955 """ 

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

957 

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

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

960 

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

962 """ 

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

964 

965 def info( 

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

967 ) -> ResponseT: 

968 """ 

969 Returns a dictionary containing information about the Redis server 

970 

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

972 of information 

973 

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

975 and will generate ResponseError 

976 

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

978 """ 

979 if section is None: 

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

981 else: 

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

983 

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

985 """ 

986 Return a Python datetime object representing the last time the 

987 Redis database was saved to disk 

988 

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

990 """ 

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

992 

993 def latency_doctor(self): 

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

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

996 

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

998 """ 

999 raise NotImplementedError( 

1000 """ 

1001 LATENCY DOCTOR is intentionally not implemented in the client. 

1002 

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

1004 """ 

1005 ) 

1006 

1007 def latency_graph(self): 

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

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

1010 

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

1012 """ 

1013 raise NotImplementedError( 

1014 """ 

1015 LATENCY GRAPH is intentionally not implemented in the client. 

1016 

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

1018 """ 

1019 ) 

1020 

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

1022 """ 

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

1024 

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

1026 """ 

1027 if version_numbers: 

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

1029 else: 

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

1031 

1032 def reset(self) -> ResponseT: 

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

1034 

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

1036 """ 

1037 return self.execute_command("RESET") 

1038 

1039 def migrate( 

1040 self, 

1041 host: str, 

1042 port: int, 

1043 keys: KeysT, 

1044 destination_db: int, 

1045 timeout: int, 

1046 copy: bool = False, 

1047 replace: bool = False, 

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

1049 **kwargs, 

1050 ) -> ResponseT: 

1051 """ 

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

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

1054 

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

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

1057 command is interrupted. 

1058 

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

1060 the source server. 

1061 

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

1063 on the destination server if they exist. 

1064 

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

1066 the password provided. 

1067 

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

1069 """ 

1070 keys = list_or_args(keys, []) 

1071 if not keys: 

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

1073 pieces = [] 

1074 if copy: 

1075 pieces.append(b"COPY") 

1076 if replace: 

1077 pieces.append(b"REPLACE") 

1078 if auth: 

1079 pieces.append(b"AUTH") 

1080 pieces.append(auth) 

1081 pieces.append(b"KEYS") 

1082 pieces.extend(keys) 

1083 return self.execute_command( 

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

1085 ) 

1086 

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

1088 """ 

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

1090 """ 

1091 return self.execute_command( 

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

1093 ) 

1094 

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

1096 raise NotImplementedError( 

1097 """ 

1098 MEMORY DOCTOR is intentionally not implemented in the client. 

1099 

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

1101 """ 

1102 ) 

1103 

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

1105 raise NotImplementedError( 

1106 """ 

1107 MEMORY HELP is intentionally not implemented in the client. 

1108 

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

1110 """ 

1111 ) 

1112 

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

1114 """ 

1115 Return a dictionary of memory stats 

1116 

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

1118 """ 

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

1120 

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

1122 """ 

1123 Return an internal statistics report from the memory allocator. 

1124 

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

1126 """ 

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

1128 

1129 def memory_usage( 

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

1131 ) -> ResponseT: 

1132 """ 

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

1134 administrative overheads. 

1135 

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

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

1138 all elements. 

1139 

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

1141 """ 

1142 args = [] 

1143 if isinstance(samples, int): 

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

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

1146 

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

1148 """ 

1149 Attempts to purge dirty pages for reclamation by allocator 

1150 

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

1152 """ 

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

1154 

1155 def latency_histogram(self, *args): 

1156 """ 

1157 This function throws a NotImplementedError since it is intentionally 

1158 not supported. 

1159 """ 

1160 raise NotImplementedError( 

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

1162 ) 

1163 

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

1165 """ 

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

1167 

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

1169 """ 

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

1171 

1172 def latency_latest(self) -> ResponseT: 

1173 """ 

1174 Reports the latest latency events logged. 

1175 

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

1177 """ 

1178 return self.execute_command("LATENCY LATEST") 

1179 

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

1181 """ 

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

1183 

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

1185 """ 

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

1187 

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

1189 """ 

1190 Ping the Redis server 

1191 

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

1193 """ 

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

1195 

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

1197 """ 

1198 Ask the server to close the connection. 

1199 

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

1201 """ 

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

1203 

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

1205 """ 

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

1207 Examples of valid arguments include: 

1208 NO ONE (set no replication) 

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

1210 

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

1212 """ 

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

1214 

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

1216 """ 

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

1218 blocking until the save is complete 

1219 

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

1221 """ 

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

1223 

1224 def shutdown( 

1225 self, 

1226 save: bool = False, 

1227 nosave: bool = False, 

1228 now: bool = False, 

1229 force: bool = False, 

1230 abort: bool = False, 

1231 **kwargs, 

1232 ) -> None: 

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

1234 data will be flushed before shutdown. 

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

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

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

1238 are configured. 

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

1240 the shutdown sequence. 

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

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

1243 

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

1245 """ 

1246 if save and nosave: 

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

1248 args = ["SHUTDOWN"] 

1249 if save: 

1250 args.append("SAVE") 

1251 if nosave: 

1252 args.append("NOSAVE") 

1253 if now: 

1254 args.append("NOW") 

1255 if force: 

1256 args.append("FORCE") 

1257 if abort: 

1258 args.append("ABORT") 

1259 try: 

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

1261 except ConnectionError: 

1262 # a ConnectionError here is expected 

1263 return 

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

1265 

1266 def slaveof( 

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

1268 ) -> ResponseT: 

1269 """ 

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

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

1272 instance is promoted to a master instead. 

1273 

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

1275 """ 

1276 if host is None and port is None: 

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

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

1279 

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

1281 """ 

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

1283 most recent ``num`` items. 

1284 

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

1286 """ 

1287 from redis.client import NEVER_DECODE 

1288 

1289 args = ["SLOWLOG GET"] 

1290 if num is not None: 

1291 args.append(num) 

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

1293 if decode_responses is True: 

1294 kwargs[NEVER_DECODE] = [] 

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

1296 

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

1298 """ 

1299 Get the number of items in the slowlog 

1300 

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

1302 """ 

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

1304 

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

1306 """ 

1307 Remove all items in the slowlog 

1308 

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

1310 """ 

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

1312 

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

1314 """ 

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

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

1317 

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

1319 """ 

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

1321 

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

1323 """ 

1324 Redis synchronous replication 

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

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

1327 reached. 

1328 

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

1330 """ 

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

1332 

1333 def hello(self): 

1334 """ 

1335 This function throws a NotImplementedError since it is intentionally 

1336 not supported. 

1337 """ 

1338 raise NotImplementedError( 

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

1340 ) 

1341 

1342 def failover(self): 

1343 """ 

1344 This function throws a NotImplementedError since it is intentionally 

1345 not supported. 

1346 """ 

1347 raise NotImplementedError( 

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

1349 ) 

1350 

1351 

1352AsyncManagementCommands = ManagementCommands 

1353 

1354 

1355class AsyncManagementCommands(ManagementCommands): 

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

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

1358 

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

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

1361 

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

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

1364 

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

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

1367 

1368 async def shutdown( 

1369 self, 

1370 save: bool = False, 

1371 nosave: bool = False, 

1372 now: bool = False, 

1373 force: bool = False, 

1374 abort: bool = False, 

1375 **kwargs, 

1376 ) -> None: 

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

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

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

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

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

1382 

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

1384 """ 

1385 if save and nosave: 

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

1387 args = ["SHUTDOWN"] 

1388 if save: 

1389 args.append("SAVE") 

1390 if nosave: 

1391 args.append("NOSAVE") 

1392 if now: 

1393 args.append("NOW") 

1394 if force: 

1395 args.append("FORCE") 

1396 if abort: 

1397 args.append("ABORT") 

1398 try: 

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

1400 except ConnectionError: 

1401 # a ConnectionError here is expected 

1402 return 

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

1404 

1405 

1406class BitFieldOperation: 

1407 """ 

1408 Command builder for BITFIELD commands. 

1409 """ 

1410 

1411 def __init__( 

1412 self, 

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

1414 key: str, 

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

1416 ): 

1417 self.client = client 

1418 self.key = key 

1419 self._default_overflow = default_overflow 

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

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

1422 self._last_overflow = "WRAP" 

1423 self.reset() 

1424 

1425 def reset(self): 

1426 """ 

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

1428 """ 

1429 self.operations = [] 

1430 self._last_overflow = "WRAP" 

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

1432 

1433 def overflow(self, overflow: str): 

1434 """ 

1435 Update the overflow algorithm of successive INCRBY operations 

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

1437 Redis docs for descriptions of these algorithmsself. 

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

1439 """ 

1440 overflow = overflow.upper() 

1441 if overflow != self._last_overflow: 

1442 self._last_overflow = overflow 

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

1444 return self 

1445 

1446 def incrby( 

1447 self, 

1448 fmt: str, 

1449 offset: BitfieldOffsetT, 

1450 increment: int, 

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

1452 ): 

1453 """ 

1454 Increment a bitfield by a given amount. 

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

1456 for an unsigned 8-bit integer. 

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

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

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

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

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

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

1463 descriptions of these algorithms. 

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

1465 """ 

1466 if overflow is not None: 

1467 self.overflow(overflow) 

1468 

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

1470 return self 

1471 

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

1473 """ 

1474 Get the value of a given bitfield. 

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

1476 an unsigned 8-bit integer. 

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

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

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

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

1481 """ 

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

1483 return self 

1484 

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

1486 """ 

1487 Set the value of a given bitfield. 

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

1489 an unsigned 8-bit integer. 

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

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

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

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

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

1495 """ 

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

1497 return self 

1498 

1499 @property 

1500 def command(self): 

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

1502 for ops in self.operations: 

1503 cmd.extend(ops) 

1504 return cmd 

1505 

1506 def execute(self) -> ResponseT: 

1507 """ 

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

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

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

1511 will be present within the pipeline's execute. 

1512 """ 

1513 command = self.command 

1514 self.reset() 

1515 return self.client.execute_command(*command) 

1516 

1517 

1518class BasicKeyCommands(CommandsProtocol): 

1519 """ 

1520 Redis basic key-based commands 

1521 """ 

1522 

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

1524 """ 

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

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

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

1528 

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

1530 """ 

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

1532 

1533 def bitcount( 

1534 self, 

1535 key: KeyT, 

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

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

1538 mode: Optional[str] = None, 

1539 ) -> ResponseT: 

1540 """ 

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

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

1543 

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

1545 """ 

1546 params = [key] 

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

1548 params.append(start) 

1549 params.append(end) 

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

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

1552 if mode is not None: 

1553 params.append(mode) 

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

1555 

1556 def bitfield( 

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

1558 key: KeyT, 

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

1560 ) -> BitFieldOperation: 

1561 """ 

1562 Return a BitFieldOperation instance to conveniently construct one or 

1563 more bitfield operations on ``key``. 

1564 

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

1566 """ 

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

1568 

1569 def bitfield_ro( 

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

1571 key: KeyT, 

1572 encoding: str, 

1573 offset: BitfieldOffsetT, 

1574 items: Optional[list] = None, 

1575 ) -> ResponseT: 

1576 """ 

1577 Return an array of the specified bitfield values 

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

1579 parameters and remaining values are result of corresponding 

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

1581 Read-only variant of the BITFIELD command. 

1582 

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

1584 """ 

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

1586 

1587 items = items or [] 

1588 for encoding, offset in items: 

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

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

1591 

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

1593 """ 

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

1595 store the result in ``dest``. 

1596 

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

1598 """ 

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

1600 

1601 def bitpos( 

1602 self, 

1603 key: KeyT, 

1604 bit: int, 

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

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

1607 mode: Optional[str] = None, 

1608 ) -> ResponseT: 

1609 """ 

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

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

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

1613 means to look at the first three bytes. 

1614 

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

1616 """ 

1617 if bit not in (0, 1): 

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

1619 params = [key, bit] 

1620 

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

1622 

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

1624 params.append(end) 

1625 elif start is None and end is not None: 

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

1627 

1628 if mode is not None: 

1629 params.append(mode) 

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

1631 

1632 def copy( 

1633 self, 

1634 source: str, 

1635 destination: str, 

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

1637 replace: bool = False, 

1638 ) -> ResponseT: 

1639 """ 

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

1641 

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

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

1644 

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

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

1647 the ``destination`` key already exists. 

1648 

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

1650 """ 

1651 params = [source, destination] 

1652 if destination_db is not None: 

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

1654 if replace: 

1655 params.append("REPLACE") 

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

1657 

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

1659 """ 

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

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

1662 

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

1664 """ 

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

1666 

1667 decr = decrby 

1668 

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

1670 """ 

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

1672 """ 

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

1674 

1675 def __delitem__(self, name: KeyT): 

1676 self.delete(name) 

1677 

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

1679 """ 

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

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

1682 

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

1684 """ 

1685 from redis.client import NEVER_DECODE 

1686 

1687 options = {} 

1688 options[NEVER_DECODE] = [] 

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

1690 

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

1692 """ 

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

1694 

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

1696 """ 

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

1698 

1699 __contains__ = exists 

1700 

1701 def expire( 

1702 self, 

1703 name: KeyT, 

1704 time: ExpiryT, 

1705 nx: bool = False, 

1706 xx: bool = False, 

1707 gt: bool = False, 

1708 lt: bool = False, 

1709 ) -> ResponseT: 

1710 """ 

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

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

1713 object. 

1714 

1715 Valid options are: 

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

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

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

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

1720 

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

1722 """ 

1723 if isinstance(time, datetime.timedelta): 

1724 time = int(time.total_seconds()) 

1725 

1726 exp_option = list() 

1727 if nx: 

1728 exp_option.append("NX") 

1729 if xx: 

1730 exp_option.append("XX") 

1731 if gt: 

1732 exp_option.append("GT") 

1733 if lt: 

1734 exp_option.append("LT") 

1735 

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

1737 

1738 def expireat( 

1739 self, 

1740 name: KeyT, 

1741 when: AbsExpiryT, 

1742 nx: bool = False, 

1743 xx: bool = False, 

1744 gt: bool = False, 

1745 lt: bool = False, 

1746 ) -> ResponseT: 

1747 """ 

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

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

1750 datetime object. 

1751 

1752 Valid options are: 

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

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

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

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

1757 

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

1759 """ 

1760 if isinstance(when, datetime.datetime): 

1761 when = int(when.timestamp()) 

1762 

1763 exp_option = list() 

1764 if nx: 

1765 exp_option.append("NX") 

1766 if xx: 

1767 exp_option.append("XX") 

1768 if gt: 

1769 exp_option.append("GT") 

1770 if lt: 

1771 exp_option.append("LT") 

1772 

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

1774 

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

1776 """ 

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

1778 at which the given key will expire. 

1779 

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

1781 """ 

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

1783 

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

1785 """ 

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

1787 

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

1789 """ 

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

1791 

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

1793 """ 

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

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

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

1797 is a string). 

1798 

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

1800 """ 

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

1802 

1803 def getex( 

1804 self, 

1805 name: KeyT, 

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

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

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

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

1810 persist: bool = False, 

1811 ) -> ResponseT: 

1812 """ 

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

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

1815 additional options. All time parameters can be given as 

1816 datetime.timedelta or integers. 

1817 

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

1819 

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

1821 

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

1823 specified in unix time. 

1824 

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

1826 specified in unix time. 

1827 

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

1829 

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

1831 """ 

1832 

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

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

1835 raise DataError( 

1836 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

1838 ) 

1839 

1840 pieces: list[EncodableT] = [] 

1841 # similar to set command 

1842 if ex is not None: 

1843 pieces.append("EX") 

1844 if isinstance(ex, datetime.timedelta): 

1845 ex = int(ex.total_seconds()) 

1846 pieces.append(ex) 

1847 if px is not None: 

1848 pieces.append("PX") 

1849 if isinstance(px, datetime.timedelta): 

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

1851 pieces.append(px) 

1852 # similar to pexpireat command 

1853 if exat is not None: 

1854 pieces.append("EXAT") 

1855 if isinstance(exat, datetime.datetime): 

1856 exat = int(exat.timestamp()) 

1857 pieces.append(exat) 

1858 if pxat is not None: 

1859 pieces.append("PXAT") 

1860 if isinstance(pxat, datetime.datetime): 

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

1862 pieces.append(pxat) 

1863 if persist: 

1864 pieces.append("PERSIST") 

1865 

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

1867 

1868 def __getitem__(self, name: KeyT): 

1869 """ 

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

1871 doesn't exist. 

1872 """ 

1873 value = self.get(name) 

1874 if value is not None: 

1875 return value 

1876 raise KeyError(name) 

1877 

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

1879 """ 

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

1881 

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

1883 """ 

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

1885 

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

1887 """ 

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

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

1890 

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

1892 """ 

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

1894 

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

1896 """ 

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

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

1899 

1900 As per Redis 6.2, GETSET is considered deprecated. 

1901 Please use SET with GET parameter in new code. 

1902 

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

1904 """ 

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

1906 

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

1908 """ 

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

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

1911 

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

1913 """ 

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

1915 

1916 incr = incrby 

1917 

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

1919 """ 

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

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

1922 

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

1924 """ 

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

1926 

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

1928 """ 

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

1930 

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

1932 """ 

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

1934 

1935 def lmove( 

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

1937 ) -> ResponseT: 

1938 """ 

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

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

1941 Returns the element being popped and pushed. 

1942 

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

1944 """ 

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

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

1947 

1948 def blmove( 

1949 self, 

1950 first_list: str, 

1951 second_list: str, 

1952 timeout: int, 

1953 src: str = "LEFT", 

1954 dest: str = "RIGHT", 

1955 ) -> ResponseT: 

1956 """ 

1957 Blocking version of lmove. 

1958 

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

1960 """ 

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

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

1963 

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

1965 """ 

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

1967 

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

1969 """ 

1970 from redis.client import EMPTY_RESPONSE 

1971 

1972 args = list_or_args(keys, args) 

1973 options = {} 

1974 if not args: 

1975 options[EMPTY_RESPONSE] = [] 

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

1977 

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

1979 """ 

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

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

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

1983 

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

1985 """ 

1986 items = [] 

1987 for pair in mapping.items(): 

1988 items.extend(pair) 

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

1990 

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

1992 """ 

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

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

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

1996 Returns a boolean indicating if the operation was successful. 

1997 

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

1999 """ 

2000 items = [] 

2001 for pair in mapping.items(): 

2002 items.extend(pair) 

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

2004 

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

2006 """ 

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

2008 

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

2010 """ 

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

2012 

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

2014 """ 

2015 Removes an expiration on ``name`` 

2016 

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

2018 """ 

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

2020 

2021 def pexpire( 

2022 self, 

2023 name: KeyT, 

2024 time: ExpiryT, 

2025 nx: bool = False, 

2026 xx: bool = False, 

2027 gt: bool = False, 

2028 lt: bool = False, 

2029 ) -> ResponseT: 

2030 """ 

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

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

2033 integer or a Python timedelta object. 

2034 

2035 Valid options are: 

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

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

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

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

2040 

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

2042 """ 

2043 if isinstance(time, datetime.timedelta): 

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

2045 

2046 exp_option = list() 

2047 if nx: 

2048 exp_option.append("NX") 

2049 if xx: 

2050 exp_option.append("XX") 

2051 if gt: 

2052 exp_option.append("GT") 

2053 if lt: 

2054 exp_option.append("LT") 

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

2056 

2057 def pexpireat( 

2058 self, 

2059 name: KeyT, 

2060 when: AbsExpiryT, 

2061 nx: bool = False, 

2062 xx: bool = False, 

2063 gt: bool = False, 

2064 lt: bool = False, 

2065 ) -> ResponseT: 

2066 """ 

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

2068 can be represented as an integer representing unix time in 

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

2070 

2071 Valid options are: 

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

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

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

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

2076 

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

2078 """ 

2079 if isinstance(when, datetime.datetime): 

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

2081 exp_option = list() 

2082 if nx: 

2083 exp_option.append("NX") 

2084 if xx: 

2085 exp_option.append("XX") 

2086 if gt: 

2087 exp_option.append("GT") 

2088 if lt: 

2089 exp_option.append("LT") 

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

2091 

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

2093 """ 

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

2095 at which the given key will expire. 

2096 

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

2098 """ 

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

2100 

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

2102 """ 

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

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

2105 timedelta object 

2106 

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

2108 """ 

2109 if isinstance(time_ms, datetime.timedelta): 

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

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

2112 

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

2114 """ 

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

2116 

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

2118 """ 

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

2120 

2121 def hrandfield( 

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

2123 ) -> ResponseT: 

2124 """ 

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

2126 

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

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

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

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

2131 specified count. 

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

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

2134 

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

2136 """ 

2137 params = [] 

2138 if count is not None: 

2139 params.append(count) 

2140 if withvalues: 

2141 params.append("WITHVALUES") 

2142 

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

2144 

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

2146 """ 

2147 Returns the name of a random key 

2148 

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

2150 """ 

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

2152 

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

2154 """ 

2155 Rename key ``src`` to ``dst`` 

2156 

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

2158 """ 

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

2160 

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

2162 """ 

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

2164 

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

2166 """ 

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

2168 

2169 def restore( 

2170 self, 

2171 name: KeyT, 

2172 ttl: float, 

2173 value: EncodableT, 

2174 replace: bool = False, 

2175 absttl: bool = False, 

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

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

2178 ) -> ResponseT: 

2179 """ 

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

2181 using DUMP. 

2182 

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

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

2185 

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

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

2188 greater). 

2189 

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

2191 key must be idle, prior to execution. 

2192 

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

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

2195 

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

2197 """ 

2198 params = [name, ttl, value] 

2199 if replace: 

2200 params.append("REPLACE") 

2201 if absttl: 

2202 params.append("ABSTTL") 

2203 if idletime is not None: 

2204 params.append("IDLETIME") 

2205 try: 

2206 params.append(int(idletime)) 

2207 except ValueError: 

2208 raise DataError("idletimemust be an integer") 

2209 

2210 if frequency is not None: 

2211 params.append("FREQ") 

2212 try: 

2213 params.append(int(frequency)) 

2214 except ValueError: 

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

2216 

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

2218 

2219 def set( 

2220 self, 

2221 name: KeyT, 

2222 value: EncodableT, 

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

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

2225 nx: bool = False, 

2226 xx: bool = False, 

2227 keepttl: bool = False, 

2228 get: bool = False, 

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

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

2231 ) -> ResponseT: 

2232 """ 

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

2234 

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

2236 

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

2238 

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

2240 if it does not exist. 

2241 

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

2243 if it already exists. 

2244 

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

2246 (Available since Redis 6.0) 

2247 

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

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

2250 (Available since Redis 6.2) 

2251 

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

2253 specified in unix time. 

2254 

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

2256 specified in unix time. 

2257 

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

2259 """ 

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

2261 options = {} 

2262 if ex is not None: 

2263 pieces.append("EX") 

2264 if isinstance(ex, datetime.timedelta): 

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

2266 elif isinstance(ex, int): 

2267 pieces.append(ex) 

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

2269 pieces.append(int(ex)) 

2270 else: 

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

2272 if px is not None: 

2273 pieces.append("PX") 

2274 if isinstance(px, datetime.timedelta): 

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

2276 elif isinstance(px, int): 

2277 pieces.append(px) 

2278 else: 

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

2280 if exat is not None: 

2281 pieces.append("EXAT") 

2282 if isinstance(exat, datetime.datetime): 

2283 exat = int(exat.timestamp()) 

2284 pieces.append(exat) 

2285 if pxat is not None: 

2286 pieces.append("PXAT") 

2287 if isinstance(pxat, datetime.datetime): 

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

2289 pieces.append(pxat) 

2290 if keepttl: 

2291 pieces.append("KEEPTTL") 

2292 

2293 if nx: 

2294 pieces.append("NX") 

2295 if xx: 

2296 pieces.append("XX") 

2297 

2298 if get: 

2299 pieces.append("GET") 

2300 options["get"] = True 

2301 

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

2303 

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

2305 self.set(name, value) 

2306 

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

2308 """ 

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

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

2311 

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

2313 """ 

2314 value = value and 1 or 0 

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

2316 

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

2318 """ 

2319 Set the value of key ``name`` to ``value`` that expires in ``time`` 

2320 seconds. ``time`` can be represented by an integer or a Python 

2321 timedelta object. 

2322 

2323 For more information see https://redis.io/commands/setex 

2324 """ 

2325 if isinstance(time, datetime.timedelta): 

2326 time = int(time.total_seconds()) 

2327 return self.execute_command("SETEX", name, time, value) 

2328 

2329 def setnx(self, name: KeyT, value: EncodableT) -> ResponseT: 

2330 """ 

2331 Set the value of key ``name`` to ``value`` if key doesn't exist 

2332 

2333 For more information see https://redis.io/commands/setnx 

2334 """ 

2335 return self.execute_command("SETNX", name, value) 

2336 

2337 def setrange(self, name: KeyT, offset: int, value: EncodableT) -> ResponseT: 

2338 """ 

2339 Overwrite bytes in the value of ``name`` starting at ``offset`` with 

2340 ``value``. If ``offset`` plus the length of ``value`` exceeds the 

2341 length of the original value, the new value will be larger than before. 

2342 If ``offset`` exceeds the length of the original value, null bytes 

2343 will be used to pad between the end of the previous value and the start 

2344 of what's being injected. 

2345 

2346 Returns the length of the new string. 

2347 

2348 For more information see https://redis.io/commands/setrange 

2349 """ 

2350 return self.execute_command("SETRANGE", name, offset, value) 

2351 

2352 def stralgo( 

2353 self, 

2354 algo: Literal["LCS"], 

2355 value1: KeyT, 

2356 value2: KeyT, 

2357 specific_argument: Union[Literal["strings"], Literal["keys"]] = "strings", 

2358 len: bool = False, 

2359 idx: bool = False, 

2360 minmatchlen: Union[int, None] = None, 

2361 withmatchlen: bool = False, 

2362 **kwargs, 

2363 ) -> ResponseT: 

2364 """ 

2365 Implements complex algorithms that operate on strings. 

2366 Right now the only algorithm implemented is the LCS algorithm 

2367 (longest common substring). However new algorithms could be 

2368 implemented in the future. 

2369 

2370 ``algo`` Right now must be LCS 

2371 ``value1`` and ``value2`` Can be two strings or two keys 

2372 ``specific_argument`` Specifying if the arguments to the algorithm 

2373 will be keys or strings. strings is the default. 

2374 ``len`` Returns just the len of the match. 

2375 ``idx`` Returns the match positions in each string. 

2376 ``minmatchlen`` Restrict the list of matches to the ones of a given 

2377 minimal length. Can be provided only when ``idx`` set to True. 

2378 ``withmatchlen`` Returns the matches with the len of the match. 

2379 Can be provided only when ``idx`` set to True. 

2380 

2381 For more information see https://redis.io/commands/stralgo 

2382 """ 

2383 # check validity 

2384 supported_algo = ["LCS"] 

2385 if algo not in supported_algo: 

2386 supported_algos_str = ", ".join(supported_algo) 

2387 raise DataError(f"The supported algorithms are: {supported_algos_str}") 

2388 if specific_argument not in ["keys", "strings"]: 

2389 raise DataError("specific_argument can be only keys or strings") 

2390 if len and idx: 

2391 raise DataError("len and idx cannot be provided together.") 

2392 

2393 pieces: list[EncodableT] = [algo, specific_argument.upper(), value1, value2] 

2394 if len: 

2395 pieces.append(b"LEN") 

2396 if idx: 

2397 pieces.append(b"IDX") 

2398 try: 

2399 int(minmatchlen) 

2400 pieces.extend([b"MINMATCHLEN", minmatchlen]) 

2401 except TypeError: 

2402 pass 

2403 if withmatchlen: 

2404 pieces.append(b"WITHMATCHLEN") 

2405 

2406 return self.execute_command( 

2407 "STRALGO", 

2408 *pieces, 

2409 len=len, 

2410 idx=idx, 

2411 minmatchlen=minmatchlen, 

2412 withmatchlen=withmatchlen, 

2413 **kwargs, 

2414 ) 

2415 

2416 def strlen(self, name: KeyT) -> ResponseT: 

2417 """ 

2418 Return the number of bytes stored in the value of ``name`` 

2419 

2420 For more information see https://redis.io/commands/strlen 

2421 """ 

2422 return self.execute_command("STRLEN", name) 

2423 

2424 def substr(self, name: KeyT, start: int, end: int = -1) -> ResponseT: 

2425 """ 

2426 Return a substring of the string at key ``name``. ``start`` and ``end`` 

2427 are 0-based integers specifying the portion of the string to return. 

2428 """ 

2429 return self.execute_command("SUBSTR", name, start, end) 

2430 

2431 def touch(self, *args: KeyT) -> ResponseT: 

2432 """ 

2433 Alters the last access time of a key(s) ``*args``. A key is ignored 

2434 if it does not exist. 

2435 

2436 For more information see https://redis.io/commands/touch 

2437 """ 

2438 return self.execute_command("TOUCH", *args) 

2439 

2440 def ttl(self, name: KeyT) -> ResponseT: 

2441 """ 

2442 Returns the number of seconds until the key ``name`` will expire 

2443 

2444 For more information see https://redis.io/commands/ttl 

2445 """ 

2446 return self.execute_command("TTL", name) 

2447 

2448 def type(self, name: KeyT) -> ResponseT: 

2449 """ 

2450 Returns the type of key ``name`` 

2451 

2452 For more information see https://redis.io/commands/type 

2453 """ 

2454 return self.execute_command("TYPE", name) 

2455 

2456 def watch(self, *names: KeyT) -> None: 

2457 """ 

2458 Watches the values at keys ``names``, or None if the key doesn't exist 

2459 

2460 For more information see https://redis.io/commands/watch 

2461 """ 

2462 warnings.warn(DeprecationWarning("Call WATCH from a Pipeline object")) 

2463 

2464 def unwatch(self) -> None: 

2465 """ 

2466 Unwatches the value at key ``name``, or None of the key doesn't exist 

2467 

2468 For more information see https://redis.io/commands/unwatch 

2469 """ 

2470 warnings.warn(DeprecationWarning("Call UNWATCH from a Pipeline object")) 

2471 

2472 def unlink(self, *names: KeyT) -> ResponseT: 

2473 """ 

2474 Unlink one or more keys specified by ``names`` 

2475 

2476 For more information see https://redis.io/commands/unlink 

2477 """ 

2478 return self.execute_command("UNLINK", *names) 

2479 

2480 def lcs( 

2481 self, 

2482 key1: str, 

2483 key2: str, 

2484 len: Optional[bool] = False, 

2485 idx: Optional[bool] = False, 

2486 minmatchlen: Optional[int] = 0, 

2487 withmatchlen: Optional[bool] = False, 

2488 ) -> Union[str, int, list]: 

2489 """ 

2490 Find the longest common subsequence between ``key1`` and ``key2``. 

2491 If ``len`` is true the length of the match will will be returned. 

2492 If ``idx`` is true the match position in each strings will be returned. 

2493 ``minmatchlen`` restrict the list of matches to the ones of 

2494 the given ``minmatchlen``. 

2495 If ``withmatchlen`` the length of the match also will be returned. 

2496 For more information see https://redis.io/commands/lcs 

2497 """ 

2498 pieces = [key1, key2] 

2499 if len: 

2500 pieces.append("LEN") 

2501 if idx: 

2502 pieces.append("IDX") 

2503 if minmatchlen != 0: 

2504 pieces.extend(["MINMATCHLEN", minmatchlen]) 

2505 if withmatchlen: 

2506 pieces.append("WITHMATCHLEN") 

2507 return self.execute_command("LCS", *pieces) 

2508 

2509 

2510class AsyncBasicKeyCommands(BasicKeyCommands): 

2511 def __delitem__(self, name: KeyT): 

2512 raise TypeError("Async Redis client does not support class deletion") 

2513 

2514 def __contains__(self, name: KeyT): 

2515 raise TypeError("Async Redis client does not support class inclusion") 

2516 

2517 def __getitem__(self, name: KeyT): 

2518 raise TypeError("Async Redis client does not support class retrieval") 

2519 

2520 def __setitem__(self, name: KeyT, value: EncodableT): 

2521 raise TypeError("Async Redis client does not support class assignment") 

2522 

2523 async def watch(self, *names: KeyT) -> None: 

2524 return super().watch(*names) 

2525 

2526 async def unwatch(self) -> None: 

2527 return super().unwatch() 

2528 

2529 

2530class ListCommands(CommandsProtocol): 

2531 """ 

2532 Redis commands for List data type. 

2533 see: https://redis.io/topics/data-types#lists 

2534 """ 

2535 

2536 def blpop( 

2537 self, keys: List, timeout: Optional[int] = 0 

2538 ) -> Union[Awaitable[list], list]: 

2539 """ 

2540 LPOP a value off of the first non-empty list 

2541 named in the ``keys`` list. 

2542 

2543 If none of the lists in ``keys`` has a value to LPOP, then block 

2544 for ``timeout`` seconds, or until a value gets pushed on to one 

2545 of the lists. 

2546 

2547 If timeout is 0, then block indefinitely. 

2548 

2549 For more information see https://redis.io/commands/blpop 

2550 """ 

2551 if timeout is None: 

2552 timeout = 0 

2553 keys = list_or_args(keys, None) 

2554 keys.append(timeout) 

2555 return self.execute_command("BLPOP", *keys) 

2556 

2557 def brpop( 

2558 self, keys: List, timeout: Optional[int] = 0 

2559 ) -> Union[Awaitable[list], list]: 

2560 """ 

2561 RPOP a value off of the first non-empty list 

2562 named in the ``keys`` list. 

2563 

2564 If none of the lists in ``keys`` has a value to RPOP, then block 

2565 for ``timeout`` seconds, or until a value gets pushed on to one 

2566 of the lists. 

2567 

2568 If timeout is 0, then block indefinitely. 

2569 

2570 For more information see https://redis.io/commands/brpop 

2571 """ 

2572 if timeout is None: 

2573 timeout = 0 

2574 keys = list_or_args(keys, None) 

2575 keys.append(timeout) 

2576 return self.execute_command("BRPOP", *keys) 

2577 

2578 def brpoplpush( 

2579 self, src: str, dst: str, timeout: Optional[int] = 0 

2580 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

2581 """ 

2582 Pop a value off the tail of ``src``, push it on the head of ``dst`` 

2583 and then return it. 

2584 

2585 This command blocks until a value is in ``src`` or until ``timeout`` 

2586 seconds elapse, whichever is first. A ``timeout`` value of 0 blocks 

2587 forever. 

2588 

2589 For more information see https://redis.io/commands/brpoplpush 

2590 """ 

2591 if timeout is None: 

2592 timeout = 0 

2593 return self.execute_command("BRPOPLPUSH", src, dst, timeout) 

2594 

2595 def blmpop( 

2596 self, 

2597 timeout: float, 

2598 numkeys: int, 

2599 *args: List[str], 

2600 direction: str, 

2601 count: Optional[int] = 1, 

2602 ) -> Optional[list]: 

2603 """ 

2604 Pop ``count`` values (default 1) from first non-empty in the list 

2605 of provided key names. 

2606 

2607 When all lists are empty this command blocks the connection until another 

2608 client pushes to it or until the timeout, timeout of 0 blocks indefinitely 

2609 

2610 For more information see https://redis.io/commands/blmpop 

2611 """ 

2612 args = [timeout, numkeys, *args, direction, "COUNT", count] 

2613 

2614 return self.execute_command("BLMPOP", *args) 

2615 

2616 def lmpop( 

2617 self, 

2618 num_keys: int, 

2619 *args: List[str], 

2620 direction: str, 

2621 count: Optional[int] = 1, 

2622 ) -> Union[Awaitable[list], list]: 

2623 """ 

2624 Pop ``count`` values (default 1) first non-empty list key from the list 

2625 of args provided key names. 

2626 

2627 For more information see https://redis.io/commands/lmpop 

2628 """ 

2629 args = [num_keys] + list(args) + [direction] 

2630 if count != 1: 

2631 args.extend(["COUNT", count]) 

2632 

2633 return self.execute_command("LMPOP", *args) 

2634 

2635 def lindex( 

2636 self, name: str, index: int 

2637 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

2638 """ 

2639 Return the item from list ``name`` at position ``index`` 

2640 

2641 Negative indexes are supported and will return an item at the 

2642 end of the list 

2643 

2644 For more information see https://redis.io/commands/lindex 

2645 """ 

2646 return self.execute_command("LINDEX", name, index) 

2647 

2648 def linsert( 

2649 self, name: str, where: str, refvalue: str, value: str 

2650 ) -> Union[Awaitable[int], int]: 

2651 """ 

2652 Insert ``value`` in list ``name`` either immediately before or after 

2653 [``where``] ``refvalue`` 

2654 

2655 Returns the new length of the list on success or -1 if ``refvalue`` 

2656 is not in the list. 

2657 

2658 For more information see https://redis.io/commands/linsert 

2659 """ 

2660 return self.execute_command("LINSERT", name, where, refvalue, value) 

2661 

2662 def llen(self, name: str) -> Union[Awaitable[int], int]: 

2663 """ 

2664 Return the length of the list ``name`` 

2665 

2666 For more information see https://redis.io/commands/llen 

2667 """ 

2668 return self.execute_command("LLEN", name) 

2669 

2670 def lpop( 

2671 self, 

2672 name: str, 

2673 count: Optional[int] = None, 

2674 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]: 

2675 """ 

2676 Removes and returns the first elements of the list ``name``. 

2677 

2678 By default, the command pops a single element from the beginning of 

2679 the list. When provided with the optional ``count`` argument, the reply 

2680 will consist of up to count elements, depending on the list's length. 

2681 

2682 For more information see https://redis.io/commands/lpop 

2683 """ 

2684 if count is not None: 

2685 return self.execute_command("LPOP", name, count) 

2686 else: 

2687 return self.execute_command("LPOP", name) 

2688 

2689 def lpush(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2690 """ 

2691 Push ``values`` onto the head of the list ``name`` 

2692 

2693 For more information see https://redis.io/commands/lpush 

2694 """ 

2695 return self.execute_command("LPUSH", name, *values) 

2696 

2697 def lpushx(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2698 """ 

2699 Push ``value`` onto the head of the list ``name`` if ``name`` exists 

2700 

2701 For more information see https://redis.io/commands/lpushx 

2702 """ 

2703 return self.execute_command("LPUSHX", name, *values) 

2704 

2705 def lrange(self, name: str, start: int, end: int) -> Union[Awaitable[list], list]: 

2706 """ 

2707 Return a slice of the list ``name`` between 

2708 position ``start`` and ``end`` 

2709 

2710 ``start`` and ``end`` can be negative numbers just like 

2711 Python slicing notation 

2712 

2713 For more information see https://redis.io/commands/lrange 

2714 """ 

2715 return self.execute_command("LRANGE", name, start, end) 

2716 

2717 def lrem(self, name: str, count: int, value: str) -> Union[Awaitable[int], int]: 

2718 """ 

2719 Remove the first ``count`` occurrences of elements equal to ``value`` 

2720 from the list stored at ``name``. 

2721 

2722 The count argument influences the operation in the following ways: 

2723 count > 0: Remove elements equal to value moving from head to tail. 

2724 count < 0: Remove elements equal to value moving from tail to head. 

2725 count = 0: Remove all elements equal to value. 

2726 

2727 For more information see https://redis.io/commands/lrem 

2728 """ 

2729 return self.execute_command("LREM", name, count, value) 

2730 

2731 def lset(self, name: str, index: int, value: str) -> Union[Awaitable[str], str]: 

2732 """ 

2733 Set element at ``index`` of list ``name`` to ``value`` 

2734 

2735 For more information see https://redis.io/commands/lset 

2736 """ 

2737 return self.execute_command("LSET", name, index, value) 

2738 

2739 def ltrim(self, name: str, start: int, end: int) -> Union[Awaitable[str], str]: 

2740 """ 

2741 Trim the list ``name``, removing all values not within the slice 

2742 between ``start`` and ``end`` 

2743 

2744 ``start`` and ``end`` can be negative numbers just like 

2745 Python slicing notation 

2746 

2747 For more information see https://redis.io/commands/ltrim 

2748 """ 

2749 return self.execute_command("LTRIM", name, start, end) 

2750 

2751 def rpop( 

2752 self, 

2753 name: str, 

2754 count: Optional[int] = None, 

2755 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]: 

2756 """ 

2757 Removes and returns the last elements of the list ``name``. 

2758 

2759 By default, the command pops a single element from the end of the list. 

2760 When provided with the optional ``count`` argument, the reply will 

2761 consist of up to count elements, depending on the list's length. 

2762 

2763 For more information see https://redis.io/commands/rpop 

2764 """ 

2765 if count is not None: 

2766 return self.execute_command("RPOP", name, count) 

2767 else: 

2768 return self.execute_command("RPOP", name) 

2769 

2770 def rpoplpush(self, src: str, dst: str) -> Union[Awaitable[str], str]: 

2771 """ 

2772 RPOP a value off of the ``src`` list and atomically LPUSH it 

2773 on to the ``dst`` list. Returns the value. 

2774 

2775 For more information see https://redis.io/commands/rpoplpush 

2776 """ 

2777 return self.execute_command("RPOPLPUSH", src, dst) 

2778 

2779 def rpush(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2780 """ 

2781 Push ``values`` onto the tail of the list ``name`` 

2782 

2783 For more information see https://redis.io/commands/rpush 

2784 """ 

2785 return self.execute_command("RPUSH", name, *values) 

2786 

2787 def rpushx(self, name: str, value: str) -> Union[Awaitable[int], int]: 

2788 """ 

2789 Push ``value`` onto the tail of the list ``name`` if ``name`` exists 

2790 

2791 For more information see https://redis.io/commands/rpushx 

2792 """ 

2793 return self.execute_command("RPUSHX", name, value) 

2794 

2795 def lpos( 

2796 self, 

2797 name: str, 

2798 value: str, 

2799 rank: Optional[int] = None, 

2800 count: Optional[int] = None, 

2801 maxlen: Optional[int] = None, 

2802 ) -> Union[str, List, None]: 

2803 """ 

2804 Get position of ``value`` within the list ``name`` 

2805 

2806 If specified, ``rank`` indicates the "rank" of the first element to 

2807 return in case there are multiple copies of ``value`` in the list. 

2808 By default, LPOS returns the position of the first occurrence of 

2809 ``value`` in the list. When ``rank`` 2, LPOS returns the position of 

2810 the second ``value`` in the list. If ``rank`` is negative, LPOS 

2811 searches the list in reverse. For example, -1 would return the 

2812 position of the last occurrence of ``value`` and -2 would return the 

2813 position of the next to last occurrence of ``value``. 

2814 

2815 If specified, ``count`` indicates that LPOS should return a list of 

2816 up to ``count`` positions. A ``count`` of 2 would return a list of 

2817 up to 2 positions. A ``count`` of 0 returns a list of all positions 

2818 matching ``value``. When ``count`` is specified and but ``value`` 

2819 does not exist in the list, an empty list is returned. 

2820 

2821 If specified, ``maxlen`` indicates the maximum number of list 

2822 elements to scan. A ``maxlen`` of 1000 will only return the 

2823 position(s) of items within the first 1000 entries in the list. 

2824 A ``maxlen`` of 0 (the default) will scan the entire list. 

2825 

2826 For more information see https://redis.io/commands/lpos 

2827 """ 

2828 pieces: list[EncodableT] = [name, value] 

2829 if rank is not None: 

2830 pieces.extend(["RANK", rank]) 

2831 

2832 if count is not None: 

2833 pieces.extend(["COUNT", count]) 

2834 

2835 if maxlen is not None: 

2836 pieces.extend(["MAXLEN", maxlen]) 

2837 

2838 return self.execute_command("LPOS", *pieces) 

2839 

2840 def sort( 

2841 self, 

2842 name: str, 

2843 start: Optional[int] = None, 

2844 num: Optional[int] = None, 

2845 by: Optional[str] = None, 

2846 get: Optional[List[str]] = None, 

2847 desc: bool = False, 

2848 alpha: bool = False, 

2849 store: Optional[str] = None, 

2850 groups: Optional[bool] = False, 

2851 ) -> Union[List, int]: 

2852 """ 

2853 Sort and return the list, set or sorted set at ``name``. 

2854 

2855 ``start`` and ``num`` allow for paging through the sorted data 

2856 

2857 ``by`` allows using an external key to weight and sort the items. 

2858 Use an "*" to indicate where in the key the item value is located 

2859 

2860 ``get`` allows for returning items from external keys rather than the 

2861 sorted data itself. Use an "*" to indicate where in the key 

2862 the item value is located 

2863 

2864 ``desc`` allows for reversing the sort 

2865 

2866 ``alpha`` allows for sorting lexicographically rather than numerically 

2867 

2868 ``store`` allows for storing the result of the sort into 

2869 the key ``store`` 

2870 

2871 ``groups`` if set to True and if ``get`` contains at least two 

2872 elements, sort will return a list of tuples, each containing the 

2873 values fetched from the arguments to ``get``. 

2874 

2875 For more information see https://redis.io/commands/sort 

2876 """ 

2877 if (start is not None and num is None) or (num is not None and start is None): 

2878 raise DataError("``start`` and ``num`` must both be specified") 

2879 

2880 pieces: list[EncodableT] = [name] 

2881 if by is not None: 

2882 pieces.extend([b"BY", by]) 

2883 if start is not None and num is not None: 

2884 pieces.extend([b"LIMIT", start, num]) 

2885 if get is not None: 

2886 # If get is a string assume we want to get a single value. 

2887 # Otherwise assume it's an interable and we want to get multiple 

2888 # values. We can't just iterate blindly because strings are 

2889 # iterable. 

2890 if isinstance(get, (bytes, str)): 

2891 pieces.extend([b"GET", get]) 

2892 else: 

2893 for g in get: 

2894 pieces.extend([b"GET", g]) 

2895 if desc: 

2896 pieces.append(b"DESC") 

2897 if alpha: 

2898 pieces.append(b"ALPHA") 

2899 if store is not None: 

2900 pieces.extend([b"STORE", store]) 

2901 if groups: 

2902 if not get or isinstance(get, (bytes, str)) or len(get) < 2: 

2903 raise DataError( 

2904 'when using "groups" the "get" argument ' 

2905 "must be specified and contain at least " 

2906 "two keys" 

2907 ) 

2908 

2909 options = {"groups": len(get) if groups else None} 

2910 return self.execute_command("SORT", *pieces, **options) 

2911 

2912 def sort_ro( 

2913 self, 

2914 key: str, 

2915 start: Optional[int] = None, 

2916 num: Optional[int] = None, 

2917 by: Optional[str] = None, 

2918 get: Optional[List[str]] = None, 

2919 desc: bool = False, 

2920 alpha: bool = False, 

2921 ) -> list: 

2922 """ 

2923 Returns the elements contained in the list, set or sorted set at key. 

2924 (read-only variant of the SORT command) 

2925 

2926 ``start`` and ``num`` allow for paging through the sorted data 

2927 

2928 ``by`` allows using an external key to weight and sort the items. 

2929 Use an "*" to indicate where in the key the item value is located 

2930 

2931 ``get`` allows for returning items from external keys rather than the 

2932 sorted data itself. Use an "*" to indicate where in the key 

2933 the item value is located 

2934 

2935 ``desc`` allows for reversing the sort 

2936 

2937 ``alpha`` allows for sorting lexicographically rather than numerically 

2938 

2939 For more information see https://redis.io/commands/sort_ro 

2940 """ 

2941 return self.sort( 

2942 key, start=start, num=num, by=by, get=get, desc=desc, alpha=alpha 

2943 ) 

2944 

2945 

2946AsyncListCommands = ListCommands 

2947 

2948 

2949class ScanCommands(CommandsProtocol): 

2950 """ 

2951 Redis SCAN commands. 

2952 see: https://redis.io/commands/scan 

2953 """ 

2954 

2955 def scan( 

2956 self, 

2957 cursor: int = 0, 

2958 match: Union[PatternT, None] = None, 

2959 count: Union[int, None] = None, 

2960 _type: Union[str, None] = None, 

2961 **kwargs, 

2962 ) -> ResponseT: 

2963 """ 

2964 Incrementally return lists of key names. Also return a cursor 

2965 indicating the scan position. 

2966 

2967 ``match`` allows for filtering the keys by pattern 

2968 

2969 ``count`` provides a hint to Redis about the number of keys to 

2970 return per batch. 

2971 

2972 ``_type`` filters the returned values by a particular Redis type. 

2973 Stock Redis instances allow for the following types: 

2974 HASH, LIST, SET, STREAM, STRING, ZSET 

2975 Additionally, Redis modules can expose other types as well. 

2976 

2977 For more information see https://redis.io/commands/scan 

2978 """ 

2979 pieces: list[EncodableT] = [cursor] 

2980 if match is not None: 

2981 pieces.extend([b"MATCH", match]) 

2982 if count is not None: 

2983 pieces.extend([b"COUNT", count]) 

2984 if _type is not None: 

2985 pieces.extend([b"TYPE", _type]) 

2986 return self.execute_command("SCAN", *pieces, **kwargs) 

2987 

2988 def scan_iter( 

2989 self, 

2990 match: Union[PatternT, None] = None, 

2991 count: Union[int, None] = None, 

2992 _type: Union[str, None] = None, 

2993 **kwargs, 

2994 ) -> Iterator: 

2995 """ 

2996 Make an iterator using the SCAN command so that the client doesn't 

2997 need to remember the cursor position. 

2998 

2999 ``match`` allows for filtering the keys by pattern 

3000 

3001 ``count`` provides a hint to Redis about the number of keys to 

3002 return per batch. 

3003 

3004 ``_type`` filters the returned values by a particular Redis type. 

3005 Stock Redis instances allow for the following types: 

3006 HASH, LIST, SET, STREAM, STRING, ZSET 

3007 Additionally, Redis modules can expose other types as well. 

3008 """ 

3009 cursor = "0" 

3010 while cursor != 0: 

3011 cursor, data = self.scan( 

3012 cursor=cursor, match=match, count=count, _type=_type, **kwargs 

3013 ) 

3014 yield from data 

3015 

3016 def sscan( 

3017 self, 

3018 name: KeyT, 

3019 cursor: int = 0, 

3020 match: Union[PatternT, None] = None, 

3021 count: Union[int, None] = None, 

3022 ) -> ResponseT: 

3023 """ 

3024 Incrementally return lists of elements in a set. Also return a cursor 

3025 indicating the scan position. 

3026 

3027 ``match`` allows for filtering the keys by pattern 

3028 

3029 ``count`` allows for hint the minimum number of returns 

3030 

3031 For more information see https://redis.io/commands/sscan 

3032 """ 

3033 pieces: list[EncodableT] = [name, cursor] 

3034 if match is not None: 

3035 pieces.extend([b"MATCH", match]) 

3036 if count is not None: 

3037 pieces.extend([b"COUNT", count]) 

3038 return self.execute_command("SSCAN", *pieces) 

3039 

3040 def sscan_iter( 

3041 self, 

3042 name: KeyT, 

3043 match: Union[PatternT, None] = None, 

3044 count: Union[int, None] = None, 

3045 ) -> Iterator: 

3046 """ 

3047 Make an iterator using the SSCAN command so that the client doesn't 

3048 need to remember the cursor position. 

3049 

3050 ``match`` allows for filtering the keys by pattern 

3051 

3052 ``count`` allows for hint the minimum number of returns 

3053 """ 

3054 cursor = "0" 

3055 while cursor != 0: 

3056 cursor, data = self.sscan(name, cursor=cursor, match=match, count=count) 

3057 yield from data 

3058 

3059 def hscan( 

3060 self, 

3061 name: KeyT, 

3062 cursor: int = 0, 

3063 match: Union[PatternT, None] = None, 

3064 count: Union[int, None] = None, 

3065 ) -> ResponseT: 

3066 """ 

3067 Incrementally return key/value slices in a hash. Also return a cursor 

3068 indicating the scan position. 

3069 

3070 ``match`` allows for filtering the keys by pattern 

3071 

3072 ``count`` allows for hint the minimum number of returns 

3073 

3074 For more information see https://redis.io/commands/hscan 

3075 """ 

3076 pieces: list[EncodableT] = [name, cursor] 

3077 if match is not None: 

3078 pieces.extend([b"MATCH", match]) 

3079 if count is not None: 

3080 pieces.extend([b"COUNT", count]) 

3081 return self.execute_command("HSCAN", *pieces) 

3082 

3083 def hscan_iter( 

3084 self, 

3085 name: str, 

3086 match: Union[PatternT, None] = None, 

3087 count: Union[int, None] = None, 

3088 ) -> Iterator: 

3089 """ 

3090 Make an iterator using the HSCAN command so that the client doesn't 

3091 need to remember the cursor position. 

3092 

3093 ``match`` allows for filtering the keys by pattern 

3094 

3095 ``count`` allows for hint the minimum number of returns 

3096 """ 

3097 cursor = "0" 

3098 while cursor != 0: 

3099 cursor, data = self.hscan(name, cursor=cursor, match=match, count=count) 

3100 yield from data.items() 

3101 

3102 def zscan( 

3103 self, 

3104 name: KeyT, 

3105 cursor: int = 0, 

3106 match: Union[PatternT, None] = None, 

3107 count: Union[int, None] = None, 

3108 score_cast_func: Union[type, Callable] = float, 

3109 ) -> ResponseT: 

3110 """ 

3111 Incrementally return lists of elements in a sorted set. Also return a 

3112 cursor indicating the scan position. 

3113 

3114 ``match`` allows for filtering the keys by pattern 

3115 

3116 ``count`` allows for hint the minimum number of returns 

3117 

3118 ``score_cast_func`` a callable used to cast the score return value 

3119 

3120 For more information see https://redis.io/commands/zscan 

3121 """ 

3122 pieces = [name, cursor] 

3123 if match is not None: 

3124 pieces.extend([b"MATCH", match]) 

3125 if count is not None: 

3126 pieces.extend([b"COUNT", count]) 

3127 options = {"score_cast_func": score_cast_func} 

3128 return self.execute_command("ZSCAN", *pieces, **options) 

3129 

3130 def zscan_iter( 

3131 self, 

3132 name: KeyT, 

3133 match: Union[PatternT, None] = None, 

3134 count: Union[int, None] = None, 

3135 score_cast_func: Union[type, Callable] = float, 

3136 ) -> Iterator: 

3137 """ 

3138 Make an iterator using the ZSCAN command so that the client doesn't 

3139 need to remember the cursor position. 

3140 

3141 ``match`` allows for filtering the keys by pattern 

3142 

3143 ``count`` allows for hint the minimum number of returns 

3144 

3145 ``score_cast_func`` a callable used to cast the score return value 

3146 """ 

3147 cursor = "0" 

3148 while cursor != 0: 

3149 cursor, data = self.zscan( 

3150 name, 

3151 cursor=cursor, 

3152 match=match, 

3153 count=count, 

3154 score_cast_func=score_cast_func, 

3155 ) 

3156 yield from data 

3157 

3158 

3159class AsyncScanCommands(ScanCommands): 

3160 async def scan_iter( 

3161 self, 

3162 match: Union[PatternT, None] = None, 

3163 count: Union[int, None] = None, 

3164 _type: Union[str, None] = None, 

3165 **kwargs, 

3166 ) -> AsyncIterator: 

3167 """ 

3168 Make an iterator using the SCAN command so that the client doesn't 

3169 need to remember the cursor position. 

3170 

3171 ``match`` allows for filtering the keys by pattern 

3172 

3173 ``count`` provides a hint to Redis about the number of keys to 

3174 return per batch. 

3175 

3176 ``_type`` filters the returned values by a particular Redis type. 

3177 Stock Redis instances allow for the following types: 

3178 HASH, LIST, SET, STREAM, STRING, ZSET 

3179 Additionally, Redis modules can expose other types as well. 

3180 """ 

3181 cursor = "0" 

3182 while cursor != 0: 

3183 cursor, data = await self.scan( 

3184 cursor=cursor, match=match, count=count, _type=_type, **kwargs 

3185 ) 

3186 for d in data: 

3187 yield d 

3188 

3189 async def sscan_iter( 

3190 self, 

3191 name: KeyT, 

3192 match: Union[PatternT, None] = None, 

3193 count: Union[int, None] = None, 

3194 ) -> AsyncIterator: 

3195 """ 

3196 Make an iterator using the SSCAN command so that the client doesn't 

3197 need to remember the cursor position. 

3198 

3199 ``match`` allows for filtering the keys by pattern 

3200 

3201 ``count`` allows for hint the minimum number of returns 

3202 """ 

3203 cursor = "0" 

3204 while cursor != 0: 

3205 cursor, data = await self.sscan( 

3206 name, cursor=cursor, match=match, count=count 

3207 ) 

3208 for d in data: 

3209 yield d 

3210 

3211 async def hscan_iter( 

3212 self, 

3213 name: str, 

3214 match: Union[PatternT, None] = None, 

3215 count: Union[int, None] = None, 

3216 ) -> AsyncIterator: 

3217 """ 

3218 Make an iterator using the HSCAN command so that the client doesn't 

3219 need to remember the cursor position. 

3220 

3221 ``match`` allows for filtering the keys by pattern 

3222 

3223 ``count`` allows for hint the minimum number of returns 

3224 """ 

3225 cursor = "0" 

3226 while cursor != 0: 

3227 cursor, data = await self.hscan( 

3228 name, cursor=cursor, match=match, count=count 

3229 ) 

3230 for it in data.items(): 

3231 yield it 

3232 

3233 async def zscan_iter( 

3234 self, 

3235 name: KeyT, 

3236 match: Union[PatternT, None] = None, 

3237 count: Union[int, None] = None, 

3238 score_cast_func: Union[type, Callable] = float, 

3239 ) -> AsyncIterator: 

3240 """ 

3241 Make an iterator using the ZSCAN command so that the client doesn't 

3242 need to remember the cursor position. 

3243 

3244 ``match`` allows for filtering the keys by pattern 

3245 

3246 ``count`` allows for hint the minimum number of returns 

3247 

3248 ``score_cast_func`` a callable used to cast the score return value 

3249 """ 

3250 cursor = "0" 

3251 while cursor != 0: 

3252 cursor, data = await self.zscan( 

3253 name, 

3254 cursor=cursor, 

3255 match=match, 

3256 count=count, 

3257 score_cast_func=score_cast_func, 

3258 ) 

3259 for d in data: 

3260 yield d 

3261 

3262 

3263class SetCommands(CommandsProtocol): 

3264 """ 

3265 Redis commands for Set data type. 

3266 see: https://redis.io/topics/data-types#sets 

3267 """ 

3268 

3269 def sadd(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

3270 """ 

3271 Add ``value(s)`` to set ``name`` 

3272 

3273 For more information see https://redis.io/commands/sadd 

3274 """ 

3275 return self.execute_command("SADD", name, *values) 

3276 

3277 def scard(self, name: str) -> Union[Awaitable[int], int]: 

3278 """ 

3279 Return the number of elements in set ``name`` 

3280 

3281 For more information see https://redis.io/commands/scard 

3282 """ 

3283 return self.execute_command("SCARD", name) 

3284 

3285 def sdiff(self, keys: List, *args: List) -> Union[Awaitable[list], list]: 

3286 """ 

3287 Return the difference of sets specified by ``keys`` 

3288 

3289 For more information see https://redis.io/commands/sdiff 

3290 """ 

3291 args = list_or_args(keys, args) 

3292 return self.execute_command("SDIFF", *args) 

3293 

3294 def sdiffstore( 

3295 self, dest: str, keys: List, *args: List 

3296 ) -> Union[Awaitable[int], int]: 

3297 """ 

3298 Store the difference of sets specified by ``keys`` into a new 

3299 set named ``dest``. Returns the number of keys in the new set. 

3300 

3301 For more information see https://redis.io/commands/sdiffstore 

3302 """ 

3303 args = list_or_args(keys, args) 

3304 return self.execute_command("SDIFFSTORE", dest, *args) 

3305 

3306 def sinter(self, keys: List, *args: List) -> Union[Awaitable[list], list]: 

3307 """ 

3308 Return the intersection of sets specified by ``keys`` 

3309 

3310 For more information see https://redis.io/commands/sinter 

3311 """ 

3312 args = list_or_args(keys, args) 

3313 return self.execute_command("SINTER", *args) 

3314 

3315 def sintercard( 

3316 self, numkeys: int, keys: List[str], limit: int = 0 

3317 ) -> Union[Awaitable[int], int]: 

3318 """ 

3319 Return the cardinality of the intersect of multiple sets specified by ``keys`. 

3320 

3321 When LIMIT provided (defaults to 0 and means unlimited), if the intersection 

3322 cardinality reaches limit partway through the computation, the algorithm will 

3323 exit and yield limit as the cardinality 

3324 

3325 For more information see https://redis.io/commands/sintercard 

3326 """ 

3327 args = [numkeys, *keys, "LIMIT", limit] 

3328 return self.execute_command("SINTERCARD", *args) 

3329 

3330 def sinterstore( 

3331 self, dest: str, keys: List, *args: List 

3332 ) -> Union[Awaitable[int], int]: 

3333 """ 

3334 Store the intersection of sets specified by ``keys`` into a new 

3335 set named ``dest``. Returns the number of keys in the new set. 

3336 

3337 For more information see https://redis.io/commands/sinterstore 

3338 """ 

3339 args = list_or_args(keys, args) 

3340 return self.execute_command("SINTERSTORE", dest, *args) 

3341 

3342 def sismember(self, name: str, value: str) -> Union[Awaitable[bool], bool]: 

3343 """ 

3344 Return a boolean indicating if ``value`` is a member of set ``name`` 

3345 

3346 For more information see https://redis.io/commands/sismember 

3347 """ 

3348 return self.execute_command("SISMEMBER", name, value) 

3349 

3350 def smembers(self, name: str) -> Union[Awaitable[Set], Set]: 

3351 """ 

3352 Return all members of the set ``name`` 

3353 

3354 For more information see https://redis.io/commands/smembers 

3355 """ 

3356 return self.execute_command("SMEMBERS", name) 

3357 

3358 def smismember( 

3359 self, name: str, values: List, *args: List 

3360 ) -> Union[ 

3361 Awaitable[List[Union[Literal[0], Literal[1]]]], 

3362 List[Union[Literal[0], Literal[1]]], 

3363 ]: 

3364 """ 

3365 Return whether each value in ``values`` is a member of the set ``name`` 

3366 as a list of ``int`` in the order of ``values``: 

3367 - 1 if the value is a member of the set. 

3368 - 0 if the value is not a member of the set or if key does not exist. 

3369 

3370 For more information see https://redis.io/commands/smismember 

3371 """ 

3372 args = list_or_args(values, args) 

3373 return self.execute_command("SMISMEMBER", name, *args) 

3374 

3375 def smove(self, src: str, dst: str, value: str) -> Union[Awaitable[bool], bool]: 

3376 """ 

3377 Move ``value`` from set ``src`` to set ``dst`` atomically 

3378 

3379 For more information see https://redis.io/commands/smove 

3380 """ 

3381 return self.execute_command("SMOVE", src, dst, value) 

3382 

3383 def spop(self, name: str, count: Optional[int] = None) -> Union[str, List, None]: 

3384 """ 

3385 Remove and return a random member of set ``name`` 

3386 

3387 For more information see https://redis.io/commands/spop 

3388 """ 

3389 args = (count is not None) and [count] or [] 

3390 return self.execute_command("SPOP", name, *args) 

3391 

3392 def srandmember( 

3393 self, name: str, number: Optional[int] = None 

3394 ) -> Union[str, List, None]: 

3395 """ 

3396 If ``number`` is None, returns a random member of set ``name``. 

3397 

3398 If ``number`` is supplied, returns a list of ``number`` random 

3399 members of set ``name``. Note this is only available when running 

3400 Redis 2.6+. 

3401 

3402 For more information see https://redis.io/commands/srandmember 

3403 """ 

3404 args = (number is not None) and [number] or [] 

3405 return self.execute_command("SRANDMEMBER", name, *args) 

3406 

3407 def srem(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

3408 """ 

3409 Remove ``values`` from set ``name`` 

3410 

3411 For more information see https://redis.io/commands/srem 

3412 """ 

3413 return self.execute_command("SREM", name, *values) 

3414 

3415 def sunion(self, keys: List, *args: List) -> Union[Awaitable[List], List]: 

3416 """ 

3417 Return the union of sets specified by ``keys`` 

3418 

3419 For more information see https://redis.io/commands/sunion 

3420 """ 

3421 args = list_or_args(keys, args) 

3422 return self.execute_command("SUNION", *args) 

3423 

3424 def sunionstore( 

3425 self, dest: str, keys: List, *args: List 

3426 ) -> Union[Awaitable[int], int]: 

3427 """ 

3428 Store the union of sets specified by ``keys`` into a new 

3429 set named ``dest``. Returns the number of keys in the new set. 

3430 

3431 For more information see https://redis.io/commands/sunionstore 

3432 """ 

3433 args = list_or_args(keys, args) 

3434 return self.execute_command("SUNIONSTORE", dest, *args) 

3435 

3436 

3437AsyncSetCommands = SetCommands 

3438 

3439 

3440class StreamCommands(CommandsProtocol): 

3441 """ 

3442 Redis commands for Stream data type. 

3443 see: https://redis.io/topics/streams-intro 

3444 """ 

3445 

3446 def xack(self, name: KeyT, groupname: GroupT, *ids: StreamIdT) -> ResponseT: 

3447 """ 

3448 Acknowledges the successful processing of one or more messages. 

3449 name: name of the stream. 

3450 groupname: name of the consumer group. 

3451 *ids: message ids to acknowledge. 

3452 

3453 For more information see https://redis.io/commands/xack 

3454 """ 

3455 return self.execute_command("XACK", name, groupname, *ids) 

3456 

3457 def xadd( 

3458 self, 

3459 name: KeyT, 

3460 fields: Dict[FieldT, EncodableT], 

3461 id: StreamIdT = "*", 

3462 maxlen: Union[int, None] = None, 

3463 approximate: bool = True, 

3464 nomkstream: bool = False, 

3465 minid: Union[StreamIdT, None] = None, 

3466 limit: Union[int, None] = None, 

3467 ) -> ResponseT: 

3468 """ 

3469 Add to a stream. 

3470 name: name of the stream 

3471 fields: dict of field/value pairs to insert into the stream 

3472 id: Location to insert this record. By default it is appended. 

3473 maxlen: truncate old stream members beyond this size. 

3474 Can't be specified with minid. 

3475 approximate: actual stream length may be slightly more than maxlen 

3476 nomkstream: When set to true, do not make a stream 

3477 minid: the minimum id in the stream to query. 

3478 Can't be specified with maxlen. 

3479 limit: specifies the maximum number of entries to retrieve 

3480 

3481 For more information see https://redis.io/commands/xadd 

3482 """ 

3483 pieces: list[EncodableT] = [] 

3484 if maxlen is not None and minid is not None: 

3485 raise DataError("Only one of ```maxlen``` or ```minid``` may be specified") 

3486 

3487 if maxlen is not None: 

3488 if not isinstance(maxlen, int) or maxlen < 1: 

3489 raise DataError("XADD maxlen must be a positive integer") 

3490 pieces.append(b"MAXLEN") 

3491 if approximate: 

3492 pieces.append(b"~") 

3493 pieces.append(str(maxlen)) 

3494 if minid is not None: 

3495 pieces.append(b"MINID") 

3496 if approximate: 

3497 pieces.append(b"~") 

3498 pieces.append(minid) 

3499 if limit is not None: 

3500 pieces.extend([b"LIMIT", limit]) 

3501 if nomkstream: 

3502 pieces.append(b"NOMKSTREAM") 

3503 pieces.append(id) 

3504 if not isinstance(fields, dict) or len(fields) == 0: 

3505 raise DataError("XADD fields must be a non-empty dict") 

3506 for pair in fields.items(): 

3507 pieces.extend(pair) 

3508 return self.execute_command("XADD", name, *pieces) 

3509 

3510 def xautoclaim( 

3511 self, 

3512 name: KeyT, 

3513 groupname: GroupT, 

3514 consumername: ConsumerT, 

3515 min_idle_time: int, 

3516 start_id: StreamIdT = "0-0", 

3517 count: Union[int, None] = None, 

3518 justid: bool = False, 

3519 ) -> ResponseT: 

3520 """ 

3521 Transfers ownership of pending stream entries that match the specified 

3522 criteria. Conceptually, equivalent to calling XPENDING and then XCLAIM, 

3523 but provides a more straightforward way to deal with message delivery 

3524 failures via SCAN-like semantics. 

3525 name: name of the stream. 

3526 groupname: name of the consumer group. 

3527 consumername: name of a consumer that claims the message. 

3528 min_idle_time: filter messages that were idle less than this amount of 

3529 milliseconds. 

3530 start_id: filter messages with equal or greater ID. 

3531 count: optional integer, upper limit of the number of entries that the 

3532 command attempts to claim. Set to 100 by default. 

3533 justid: optional boolean, false by default. Return just an array of IDs 

3534 of messages successfully claimed, without returning the actual message 

3535 

3536 For more information see https://redis.io/commands/xautoclaim 

3537 """ 

3538 try: 

3539 if int(min_idle_time) < 0: 

3540 raise DataError( 

3541 "XAUTOCLAIM min_idle_time must be a nonnegative integer" 

3542 ) 

3543 except TypeError: 

3544 pass 

3545 

3546 kwargs = {} 

3547 pieces = [name, groupname, consumername, min_idle_time, start_id] 

3548 

3549 try: 

3550 if int(count) < 0: 

3551 raise DataError("XPENDING count must be a integer >= 0") 

3552 pieces.extend([b"COUNT", count]) 

3553 except TypeError: 

3554 pass 

3555 if justid: 

3556 pieces.append(b"JUSTID") 

3557 kwargs["parse_justid"] = True 

3558 

3559 return self.execute_command("XAUTOCLAIM", *pieces, **kwargs) 

3560 

3561 def xclaim( 

3562 self, 

3563 name: KeyT, 

3564 groupname: GroupT, 

3565 consumername: ConsumerT, 

3566 min_idle_time: int, 

3567 message_ids: Union[List[StreamIdT], Tuple[StreamIdT]], 

3568 idle: Union[int, None] = None, 

3569 time: Union[int, None] = None, 

3570 retrycount: Union[int, None] = None, 

3571 force: bool = False, 

3572 justid: bool = False, 

3573 ) -> ResponseT: 

3574 """ 

3575 Changes the ownership of a pending message. 

3576 name: name of the stream. 

3577 groupname: name of the consumer group. 

3578 consumername: name of a consumer that claims the message. 

3579 min_idle_time: filter messages that were idle less than this amount of 

3580 milliseconds 

3581 message_ids: non-empty list or tuple of message IDs to claim 

3582 idle: optional. Set the idle time (last time it was delivered) of the 

3583 message in ms 

3584 time: optional integer. This is the same as idle but instead of a 

3585 relative amount of milliseconds, it sets the idle time to a specific 

3586 Unix time (in milliseconds). 

3587 retrycount: optional integer. set the retry counter to the specified 

3588 value. This counter is incremented every time a message is delivered 

3589 again. 

3590 force: optional boolean, false by default. Creates the pending message 

3591 entry in the PEL even if certain specified IDs are not already in the 

3592 PEL assigned to a different client. 

3593 justid: optional boolean, false by default. Return just an array of IDs 

3594 of messages successfully claimed, without returning the actual message 

3595 

3596 For more information see https://redis.io/commands/xclaim 

3597 """ 

3598 if not isinstance(min_idle_time, int) or min_idle_time < 0: 

3599 raise DataError("XCLAIM min_idle_time must be a non negative integer") 

3600 if not isinstance(message_ids, (list, tuple)) or not message_ids: 

3601 raise DataError( 

3602 "XCLAIM message_ids must be a non empty list or " 

3603 "tuple of message IDs to claim" 

3604 ) 

3605 

3606 kwargs = {} 

3607 pieces: list[EncodableT] = [name, groupname, consumername, str(min_idle_time)] 

3608 pieces.extend(list(message_ids)) 

3609 

3610 if idle is not None: 

3611 if not isinstance(idle, int): 

3612 raise DataError("XCLAIM idle must be an integer") 

3613 pieces.extend((b"IDLE", str(idle))) 

3614 if time is not None: 

3615 if not isinstance(time, int): 

3616 raise DataError("XCLAIM time must be an integer") 

3617 pieces.extend((b"TIME", str(time))) 

3618 if retrycount is not None: 

3619 if not isinstance(retrycount, int): 

3620 raise DataError("XCLAIM retrycount must be an integer") 

3621 pieces.extend((b"RETRYCOUNT", str(retrycount))) 

3622 

3623 if force: 

3624 if not isinstance(force, bool): 

3625 raise DataError("XCLAIM force must be a boolean") 

3626 pieces.append(b"FORCE") 

3627 if justid: 

3628 if not isinstance(justid, bool): 

3629 raise DataError("XCLAIM justid must be a boolean") 

3630 pieces.append(b"JUSTID") 

3631 kwargs["parse_justid"] = True 

3632 return self.execute_command("XCLAIM", *pieces, **kwargs) 

3633 

3634 def xdel(self, name: KeyT, *ids: StreamIdT) -> ResponseT: 

3635 """ 

3636 Deletes one or more messages from a stream. 

3637 name: name of the stream. 

3638 *ids: message ids to delete. 

3639 

3640 For more information see https://redis.io/commands/xdel 

3641 """ 

3642 return self.execute_command("XDEL", name, *ids) 

3643 

3644 def xgroup_create( 

3645 self, 

3646 name: KeyT, 

3647 groupname: GroupT, 

3648 id: StreamIdT = "$", 

3649 mkstream: bool = False, 

3650 entries_read: Optional[int] = None, 

3651 ) -> ResponseT: 

3652 """ 

3653 Create a new consumer group associated with a stream. 

3654 name: name of the stream. 

3655 groupname: name of the consumer group. 

3656 id: ID of the last item in the stream to consider already delivered. 

3657 

3658 For more information see https://redis.io/commands/xgroup-create 

3659 """ 

3660 pieces: list[EncodableT] = ["XGROUP CREATE", name, groupname, id] 

3661 if mkstream: 

3662 pieces.append(b"MKSTREAM") 

3663 if entries_read is not None: 

3664 pieces.extend(["ENTRIESREAD", entries_read]) 

3665 

3666 return self.execute_command(*pieces) 

3667 

3668 def xgroup_delconsumer( 

3669 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

3670 ) -> ResponseT: 

3671 """ 

3672 Remove a specific consumer from a consumer group. 

3673 Returns the number of pending messages that the consumer had before it 

3674 was deleted. 

3675 name: name of the stream. 

3676 groupname: name of the consumer group. 

3677 consumername: name of consumer to delete 

3678 

3679 For more information see https://redis.io/commands/xgroup-delconsumer 

3680 """ 

3681 return self.execute_command("XGROUP DELCONSUMER", name, groupname, consumername) 

3682 

3683 def xgroup_destroy(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3684 """ 

3685 Destroy a consumer group. 

3686 name: name of the stream. 

3687 groupname: name of the consumer group. 

3688 

3689 For more information see https://redis.io/commands/xgroup-destroy 

3690 """ 

3691 return self.execute_command("XGROUP DESTROY", name, groupname) 

3692 

3693 def xgroup_createconsumer( 

3694 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

3695 ) -> ResponseT: 

3696 """ 

3697 Consumers in a consumer group are auto-created every time a new 

3698 consumer name is mentioned by some command. 

3699 They can be explicitly created by using this command. 

3700 name: name of the stream. 

3701 groupname: name of the consumer group. 

3702 consumername: name of consumer to create. 

3703 

3704 See: https://redis.io/commands/xgroup-createconsumer 

3705 """ 

3706 return self.execute_command( 

3707 "XGROUP CREATECONSUMER", name, groupname, consumername 

3708 ) 

3709 

3710 def xgroup_setid( 

3711 self, 

3712 name: KeyT, 

3713 groupname: GroupT, 

3714 id: StreamIdT, 

3715 entries_read: Optional[int] = None, 

3716 ) -> ResponseT: 

3717 """ 

3718 Set the consumer group last delivered ID to something else. 

3719 name: name of the stream. 

3720 groupname: name of the consumer group. 

3721 id: ID of the last item in the stream to consider already delivered. 

3722 

3723 For more information see https://redis.io/commands/xgroup-setid 

3724 """ 

3725 pieces = [name, groupname, id] 

3726 if entries_read is not None: 

3727 pieces.extend(["ENTRIESREAD", entries_read]) 

3728 return self.execute_command("XGROUP SETID", *pieces) 

3729 

3730 def xinfo_consumers(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3731 """ 

3732 Returns general information about the consumers in the group. 

3733 name: name of the stream. 

3734 groupname: name of the consumer group. 

3735 

3736 For more information see https://redis.io/commands/xinfo-consumers 

3737 """ 

3738 return self.execute_command("XINFO CONSUMERS", name, groupname) 

3739 

3740 def xinfo_groups(self, name: KeyT) -> ResponseT: 

3741 """ 

3742 Returns general information about the consumer groups of the stream. 

3743 name: name of the stream. 

3744 

3745 For more information see https://redis.io/commands/xinfo-groups 

3746 """ 

3747 return self.execute_command("XINFO GROUPS", name) 

3748 

3749 def xinfo_stream(self, name: KeyT, full: bool = False) -> ResponseT: 

3750 """ 

3751 Returns general information about the stream. 

3752 name: name of the stream. 

3753 full: optional boolean, false by default. Return full summary 

3754 

3755 For more information see https://redis.io/commands/xinfo-stream 

3756 """ 

3757 pieces = [name] 

3758 options = {} 

3759 if full: 

3760 pieces.append(b"FULL") 

3761 options = {"full": full} 

3762 return self.execute_command("XINFO STREAM", *pieces, **options) 

3763 

3764 def xlen(self, name: KeyT) -> ResponseT: 

3765 """ 

3766 Returns the number of elements in a given stream. 

3767 

3768 For more information see https://redis.io/commands/xlen 

3769 """ 

3770 return self.execute_command("XLEN", name) 

3771 

3772 def xpending(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3773 """ 

3774 Returns information about pending messages of a group. 

3775 name: name of the stream. 

3776 groupname: name of the consumer group. 

3777 

3778 For more information see https://redis.io/commands/xpending 

3779 """ 

3780 return self.execute_command("XPENDING", name, groupname) 

3781 

3782 def xpending_range( 

3783 self, 

3784 name: KeyT, 

3785 groupname: GroupT, 

3786 min: StreamIdT, 

3787 max: StreamIdT, 

3788 count: int, 

3789 consumername: Union[ConsumerT, None] = None, 

3790 idle: Union[int, None] = None, 

3791 ) -> ResponseT: 

3792 """ 

3793 Returns information about pending messages, in a range. 

3794 

3795 name: name of the stream. 

3796 groupname: name of the consumer group. 

3797 idle: available from version 6.2. filter entries by their 

3798 idle-time, given in milliseconds (optional). 

3799 min: minimum stream ID. 

3800 max: maximum stream ID. 

3801 count: number of messages to return 

3802 consumername: name of a consumer to filter by (optional). 

3803 """ 

3804 if {min, max, count} == {None}: 

3805 if idle is not None or consumername is not None: 

3806 raise DataError( 

3807 "if XPENDING is provided with idle time" 

3808 " or consumername, it must be provided" 

3809 " with min, max and count parameters" 

3810 ) 

3811 return self.xpending(name, groupname) 

3812 

3813 pieces = [name, groupname] 

3814 if min is None or max is None or count is None: 

3815 raise DataError( 

3816 "XPENDING must be provided with min, max " 

3817 "and count parameters, or none of them." 

3818 ) 

3819 # idle 

3820 try: 

3821 if int(idle) < 0: 

3822 raise DataError("XPENDING idle must be a integer >= 0") 

3823 pieces.extend(["IDLE", idle]) 

3824 except TypeError: 

3825 pass 

3826 # count 

3827 try: 

3828 if int(count) < 0: 

3829 raise DataError("XPENDING count must be a integer >= 0") 

3830 pieces.extend([min, max, count]) 

3831 except TypeError: 

3832 pass 

3833 # consumername 

3834 if consumername: 

3835 pieces.append(consumername) 

3836 

3837 return self.execute_command("XPENDING", *pieces, parse_detail=True) 

3838 

3839 def xrange( 

3840 self, 

3841 name: KeyT, 

3842 min: StreamIdT = "-", 

3843 max: StreamIdT = "+", 

3844 count: Union[int, None] = None, 

3845 ) -> ResponseT: 

3846 """ 

3847 Read stream values within an interval. 

3848 name: name of the stream. 

3849 start: first stream ID. defaults to '-', 

3850 meaning the earliest available. 

3851 finish: last stream ID. defaults to '+', 

3852 meaning the latest available. 

3853 count: if set, only return this many items, beginning with the 

3854 earliest available. 

3855 

3856 For more information see https://redis.io/commands/xrange 

3857 """ 

3858 pieces = [min, max] 

3859 if count is not None: 

3860 if not isinstance(count, int) or count < 1: 

3861 raise DataError("XRANGE count must be a positive integer") 

3862 pieces.append(b"COUNT") 

3863 pieces.append(str(count)) 

3864 

3865 return self.execute_command("XRANGE", name, *pieces) 

3866 

3867 def xread( 

3868 self, 

3869 streams: Dict[KeyT, StreamIdT], 

3870 count: Union[int, None] = None, 

3871 block: Union[int, None] = None, 

3872 ) -> ResponseT: 

3873 """ 

3874 Block and monitor multiple streams for new data. 

3875 streams: a dict of stream names to stream IDs, where 

3876 IDs indicate the last ID already seen. 

3877 count: if set, only return this many items, beginning with the 

3878 earliest available. 

3879 block: number of milliseconds to wait, if nothing already present. 

3880 

3881 For more information see https://redis.io/commands/xread 

3882 """ 

3883 pieces = [] 

3884 if block is not None: 

3885 if not isinstance(block, int) or block < 0: 

3886 raise DataError("XREAD block must be a non-negative integer") 

3887 pieces.append(b"BLOCK") 

3888 pieces.append(str(block)) 

3889 if count is not None: 

3890 if not isinstance(count, int) or count < 1: 

3891 raise DataError("XREAD count must be a positive integer") 

3892 pieces.append(b"COUNT") 

3893 pieces.append(str(count)) 

3894 if not isinstance(streams, dict) or len(streams) == 0: 

3895 raise DataError("XREAD streams must be a non empty dict") 

3896 pieces.append(b"STREAMS") 

3897 keys, values = zip(*streams.items()) 

3898 pieces.extend(keys) 

3899 pieces.extend(values) 

3900 return self.execute_command("XREAD", *pieces) 

3901 

3902 def xreadgroup( 

3903 self, 

3904 groupname: str, 

3905 consumername: str, 

3906 streams: Dict[KeyT, StreamIdT], 

3907 count: Union[int, None] = None, 

3908 block: Union[int, None] = None, 

3909 noack: bool = False, 

3910 ) -> ResponseT: 

3911 """ 

3912 Read from a stream via a consumer group. 

3913 groupname: name of the consumer group. 

3914 consumername: name of the requesting consumer. 

3915 streams: a dict of stream names to stream IDs, where 

3916 IDs indicate the last ID already seen. 

3917 count: if set, only return this many items, beginning with the 

3918 earliest available. 

3919 block: number of milliseconds to wait, if nothing already present. 

3920 noack: do not add messages to the PEL 

3921 

3922 For more information see https://redis.io/commands/xreadgroup 

3923 """ 

3924 pieces: list[EncodableT] = [b"GROUP", groupname, consumername] 

3925 if count is not None: 

3926 if not isinstance(count, int) or count < 1: 

3927 raise DataError("XREADGROUP count must be a positive integer") 

3928 pieces.append(b"COUNT") 

3929 pieces.append(str(count)) 

3930 if block is not None: 

3931 if not isinstance(block, int) or block < 0: 

3932 raise DataError("XREADGROUP block must be a non-negative integer") 

3933 pieces.append(b"BLOCK") 

3934 pieces.append(str(block)) 

3935 if noack: 

3936 pieces.append(b"NOACK") 

3937 if not isinstance(streams, dict) or len(streams) == 0: 

3938 raise DataError("XREADGROUP streams must be a non empty dict") 

3939 pieces.append(b"STREAMS") 

3940 pieces.extend(streams.keys()) 

3941 pieces.extend(streams.values()) 

3942 return self.execute_command("XREADGROUP", *pieces) 

3943 

3944 def xrevrange( 

3945 self, 

3946 name: KeyT, 

3947 max: StreamIdT = "+", 

3948 min: StreamIdT = "-", 

3949 count: Union[int, None] = None, 

3950 ) -> ResponseT: 

3951 """ 

3952 Read stream values within an interval, in reverse order. 

3953 name: name of the stream 

3954 start: first stream ID. defaults to '+', 

3955 meaning the latest available. 

3956 finish: last stream ID. defaults to '-', 

3957 meaning the earliest available. 

3958 count: if set, only return this many items, beginning with the 

3959 latest available. 

3960 

3961 For more information see https://redis.io/commands/xrevrange 

3962 """ 

3963 pieces: list[EncodableT] = [max, min] 

3964 if count is not None: 

3965 if not isinstance(count, int) or count < 1: 

3966 raise DataError("XREVRANGE count must be a positive integer") 

3967 pieces.append(b"COUNT") 

3968 pieces.append(str(count)) 

3969 

3970 return self.execute_command("XREVRANGE", name, *pieces) 

3971 

3972 def xtrim( 

3973 self, 

3974 name: KeyT, 

3975 maxlen: Union[int, None] = None, 

3976 approximate: bool = True, 

3977 minid: Union[StreamIdT, None] = None, 

3978 limit: Union[int, None] = None, 

3979 ) -> ResponseT: 

3980 """ 

3981 Trims old messages from a stream. 

3982 name: name of the stream. 

3983 maxlen: truncate old stream messages beyond this size 

3984 Can't be specified with minid. 

3985 approximate: actual stream length may be slightly more than maxlen 

3986 minid: the minimum id in the stream to query 

3987 Can't be specified with maxlen. 

3988 limit: specifies the maximum number of entries to retrieve 

3989 

3990 For more information see https://redis.io/commands/xtrim 

3991 """ 

3992 pieces: list[EncodableT] = [] 

3993 if maxlen is not None and minid is not None: 

3994 raise DataError("Only one of ``maxlen`` or ``minid`` may be specified") 

3995 

3996 if maxlen is None and minid is None: 

3997 raise DataError("One of ``maxlen`` or ``minid`` must be specified") 

3998 

3999 if maxlen is not None: 

4000 pieces.append(b"MAXLEN") 

4001 if minid is not None: 

4002 pieces.append(b"MINID") 

4003 if approximate: 

4004 pieces.append(b"~") 

4005 if maxlen is not None: 

4006 pieces.append(maxlen) 

4007 if minid is not None: 

4008 pieces.append(minid) 

4009 if limit is not None: 

4010 pieces.append(b"LIMIT") 

4011 pieces.append(limit) 

4012 

4013 return self.execute_command("XTRIM", name, *pieces) 

4014 

4015 

4016AsyncStreamCommands = StreamCommands 

4017 

4018 

4019class SortedSetCommands(CommandsProtocol): 

4020 """ 

4021 Redis commands for Sorted Sets data type. 

4022 see: https://redis.io/topics/data-types-intro#redis-sorted-sets 

4023 """ 

4024 

4025 def zadd( 

4026 self, 

4027 name: KeyT, 

4028 mapping: Mapping[AnyKeyT, EncodableT], 

4029 nx: bool = False, 

4030 xx: bool = False, 

4031 ch: bool = False, 

4032 incr: bool = False, 

4033 gt: bool = False, 

4034 lt: bool = False, 

4035 ) -> ResponseT: 

4036 """ 

4037 Set any number of element-name, score pairs to the key ``name``. Pairs 

4038 are specified as a dict of element-names keys to score values. 

4039 

4040 ``nx`` forces ZADD to only create new elements and not to update 

4041 scores for elements that already exist. 

4042 

4043 ``xx`` forces ZADD to only update scores of elements that already 

4044 exist. New elements will not be added. 

4045 

4046 ``ch`` modifies the return value to be the numbers of elements changed. 

4047 Changed elements include new elements that were added and elements 

4048 whose scores changed. 

4049 

4050 ``incr`` modifies ZADD to behave like ZINCRBY. In this mode only a 

4051 single element/score pair can be specified and the score is the amount 

4052 the existing score will be incremented by. When using this mode the 

4053 return value of ZADD will be the new score of the element. 

4054 

4055 ``LT`` Only update existing elements if the new score is less than 

4056 the current score. This flag doesn't prevent adding new elements. 

4057 

4058 ``GT`` Only update existing elements if the new score is greater than 

4059 the current score. This flag doesn't prevent adding new elements. 

4060 

4061 The return value of ZADD varies based on the mode specified. With no 

4062 options, ZADD returns the number of new elements added to the sorted 

4063 set. 

4064 

4065 ``NX``, ``LT``, and ``GT`` are mutually exclusive options. 

4066 

4067 See: https://redis.io/commands/ZADD 

4068 """ 

4069 if not mapping: 

4070 raise DataError("ZADD requires at least one element/score pair") 

4071 if nx and xx: 

4072 raise DataError("ZADD allows either 'nx' or 'xx', not both") 

4073 if gt and lt: 

4074 raise DataError("ZADD allows either 'gt' or 'lt', not both") 

4075 if incr and len(mapping) != 1: 

4076 raise DataError( 

4077 "ZADD option 'incr' only works when passing a " 

4078 "single element/score pair" 

4079 ) 

4080 if nx and (gt or lt): 

4081 raise DataError("Only one of 'nx', 'lt', or 'gr' may be defined.") 

4082 

4083 pieces: list[EncodableT] = [] 

4084 options = {} 

4085 if nx: 

4086 pieces.append(b"NX") 

4087 if xx: 

4088 pieces.append(b"XX") 

4089 if ch: 

4090 pieces.append(b"CH") 

4091 if incr: 

4092 pieces.append(b"INCR") 

4093 options["as_score"] = True 

4094 if gt: 

4095 pieces.append(b"GT") 

4096 if lt: 

4097 pieces.append(b"LT") 

4098 for pair in mapping.items(): 

4099 pieces.append(pair[1]) 

4100 pieces.append(pair[0]) 

4101 return self.execute_command("ZADD", name, *pieces, **options) 

4102 

4103 def zcard(self, name: KeyT) -> ResponseT: 

4104 """ 

4105 Return the number of elements in the sorted set ``name`` 

4106 

4107 For more information see https://redis.io/commands/zcard 

4108 """ 

4109 return self.execute_command("ZCARD", name) 

4110 

4111 def zcount(self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT) -> ResponseT: 

4112 """ 

4113 Returns the number of elements in the sorted set at key ``name`` with 

4114 a score between ``min`` and ``max``. 

4115 

4116 For more information see https://redis.io/commands/zcount 

4117 """ 

4118 return self.execute_command("ZCOUNT", name, min, max) 

4119 

4120 def zdiff(self, keys: KeysT, withscores: bool = False) -> ResponseT: 

4121 """ 

4122 Returns the difference between the first and all successive input 

4123 sorted sets provided in ``keys``. 

4124 

4125 For more information see https://redis.io/commands/zdiff 

4126 """ 

4127 pieces = [len(keys), *keys] 

4128 if withscores: 

4129 pieces.append("WITHSCORES") 

4130 return self.execute_command("ZDIFF", *pieces) 

4131 

4132 def zdiffstore(self, dest: KeyT, keys: KeysT) -> ResponseT: 

4133 """ 

4134 Computes the difference between the first and all successive input 

4135 sorted sets provided in ``keys`` and stores the result in ``dest``. 

4136 

4137 For more information see https://redis.io/commands/zdiffstore 

4138 """ 

4139 pieces = [len(keys), *keys] 

4140 return self.execute_command("ZDIFFSTORE", dest, *pieces) 

4141 

4142 def zincrby(self, name: KeyT, amount: float, value: EncodableT) -> ResponseT: 

4143 """ 

4144 Increment the score of ``value`` in sorted set ``name`` by ``amount`` 

4145 

4146 For more information see https://redis.io/commands/zincrby 

4147 """ 

4148 return self.execute_command("ZINCRBY", name, amount, value) 

4149 

4150 def zinter( 

4151 self, keys: KeysT, aggregate: Union[str, None] = None, withscores: bool = False 

4152 ) -> ResponseT: 

4153 """ 

4154 Return the intersect of multiple sorted sets specified by ``keys``. 

4155 With the ``aggregate`` option, it is possible to specify how the 

4156 results of the union are aggregated. This option defaults to SUM, 

4157 where the score of an element is summed across the inputs where it 

4158 exists. When this option is set to either MIN or MAX, the resulting 

4159 set will contain the minimum or maximum score of an element across 

4160 the inputs where it exists. 

4161 

4162 For more information see https://redis.io/commands/zinter 

4163 """ 

4164 return self._zaggregate("ZINTER", None, keys, aggregate, withscores=withscores) 

4165 

4166 def zinterstore( 

4167 self, 

4168 dest: KeyT, 

4169 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4170 aggregate: Union[str, None] = None, 

4171 ) -> ResponseT: 

4172 """ 

4173 Intersect multiple sorted sets specified by ``keys`` into a new 

4174 sorted set, ``dest``. Scores in the destination will be aggregated 

4175 based on the ``aggregate``. This option defaults to SUM, where the 

4176 score of an element is summed across the inputs where it exists. 

4177 When this option is set to either MIN or MAX, the resulting set will 

4178 contain the minimum or maximum score of an element across the inputs 

4179 where it exists. 

4180 

4181 For more information see https://redis.io/commands/zinterstore 

4182 """ 

4183 return self._zaggregate("ZINTERSTORE", dest, keys, aggregate) 

4184 

4185 def zintercard( 

4186 self, numkeys: int, keys: List[str], limit: int = 0 

4187 ) -> Union[Awaitable[int], int]: 

4188 """ 

4189 Return the cardinality of the intersect of multiple sorted sets 

4190 specified by ``keys`. 

4191 When LIMIT provided (defaults to 0 and means unlimited), if the intersection 

4192 cardinality reaches limit partway through the computation, the algorithm will 

4193 exit and yield limit as the cardinality 

4194 

4195 For more information see https://redis.io/commands/zintercard 

4196 """ 

4197 args = [numkeys, *keys, "LIMIT", limit] 

4198 return self.execute_command("ZINTERCARD", *args) 

4199 

4200 def zlexcount(self, name, min, max): 

4201 """ 

4202 Return the number of items in the sorted set ``name`` between the 

4203 lexicographical range ``min`` and ``max``. 

4204 

4205 For more information see https://redis.io/commands/zlexcount 

4206 """ 

4207 return self.execute_command("ZLEXCOUNT", name, min, max) 

4208 

4209 def zpopmax(self, name: KeyT, count: Union[int, None] = None) -> ResponseT: 

4210 """ 

4211 Remove and return up to ``count`` members with the highest scores 

4212 from the sorted set ``name``. 

4213 

4214 For more information see https://redis.io/commands/zpopmax 

4215 """ 

4216 args = (count is not None) and [count] or [] 

4217 options = {"withscores": True} 

4218 return self.execute_command("ZPOPMAX", name, *args, **options) 

4219 

4220 def zpopmin(self, name: KeyT, count: Union[int, None] = None) -> ResponseT: 

4221 """ 

4222 Remove and return up to ``count`` members with the lowest scores 

4223 from the sorted set ``name``. 

4224 

4225 For more information see https://redis.io/commands/zpopmin 

4226 """ 

4227 args = (count is not None) and [count] or [] 

4228 options = {"withscores": True} 

4229 return self.execute_command("ZPOPMIN", name, *args, **options) 

4230 

4231 def zrandmember( 

4232 self, key: KeyT, count: int = None, withscores: bool = False 

4233 ) -> ResponseT: 

4234 """ 

4235 Return a random element from the sorted set value stored at key. 

4236 

4237 ``count`` if the argument is positive, return an array of distinct 

4238 fields. If called with a negative count, the behavior changes and 

4239 the command is allowed to return the same field multiple times. 

4240 In this case, the number of returned fields is the absolute value 

4241 of the specified count. 

4242 

4243 ``withscores`` The optional WITHSCORES modifier changes the reply so it 

4244 includes the respective scores of the randomly selected elements from 

4245 the sorted set. 

4246 

4247 For more information see https://redis.io/commands/zrandmember 

4248 """ 

4249 params = [] 

4250 if count is not None: 

4251 params.append(count) 

4252 if withscores: 

4253 params.append("WITHSCORES") 

4254 

4255 return self.execute_command("ZRANDMEMBER", key, *params) 

4256 

4257 def bzpopmax(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4258 """ 

4259 ZPOPMAX a value off of the first non-empty sorted set 

4260 named in the ``keys`` list. 

4261 

4262 If none of the sorted sets in ``keys`` has a value to ZPOPMAX, 

4263 then block for ``timeout`` seconds, or until a member gets added 

4264 to one of the sorted sets. 

4265 

4266 If timeout is 0, then block indefinitely. 

4267 

4268 For more information see https://redis.io/commands/bzpopmax 

4269 """ 

4270 if timeout is None: 

4271 timeout = 0 

4272 keys = list_or_args(keys, None) 

4273 keys.append(timeout) 

4274 return self.execute_command("BZPOPMAX", *keys) 

4275 

4276 def bzpopmin(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4277 """ 

4278 ZPOPMIN a value off of the first non-empty sorted set 

4279 named in the ``keys`` list. 

4280 

4281 If none of the sorted sets in ``keys`` has a value to ZPOPMIN, 

4282 then block for ``timeout`` seconds, or until a member gets added 

4283 to one of the sorted sets. 

4284 

4285 If timeout is 0, then block indefinitely. 

4286 

4287 For more information see https://redis.io/commands/bzpopmin 

4288 """ 

4289 if timeout is None: 

4290 timeout = 0 

4291 keys: list[EncodableT] = list_or_args(keys, None) 

4292 keys.append(timeout) 

4293 return self.execute_command("BZPOPMIN", *keys) 

4294 

4295 def zmpop( 

4296 self, 

4297 num_keys: int, 

4298 keys: List[str], 

4299 min: Optional[bool] = False, 

4300 max: Optional[bool] = False, 

4301 count: Optional[int] = 1, 

4302 ) -> Union[Awaitable[list], list]: 

4303 """ 

4304 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4305 named in the ``keys`` list. 

4306 For more information see https://redis.io/commands/zmpop 

4307 """ 

4308 args = [num_keys] + keys 

4309 if (min and max) or (not min and not max): 

4310 raise DataError 

4311 elif min: 

4312 args.append("MIN") 

4313 else: 

4314 args.append("MAX") 

4315 if count != 1: 

4316 args.extend(["COUNT", count]) 

4317 

4318 return self.execute_command("ZMPOP", *args) 

4319 

4320 def bzmpop( 

4321 self, 

4322 timeout: float, 

4323 numkeys: int, 

4324 keys: List[str], 

4325 min: Optional[bool] = False, 

4326 max: Optional[bool] = False, 

4327 count: Optional[int] = 1, 

4328 ) -> Optional[list]: 

4329 """ 

4330 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4331 named in the ``keys`` list. 

4332 

4333 If none of the sorted sets in ``keys`` has a value to pop, 

4334 then block for ``timeout`` seconds, or until a member gets added 

4335 to one of the sorted sets. 

4336 

4337 If timeout is 0, then block indefinitely. 

4338 

4339 For more information see https://redis.io/commands/bzmpop 

4340 """ 

4341 args = [timeout, numkeys, *keys] 

4342 if (min and max) or (not min and not max): 

4343 raise DataError("Either min or max, but not both must be set") 

4344 elif min: 

4345 args.append("MIN") 

4346 else: 

4347 args.append("MAX") 

4348 args.extend(["COUNT", count]) 

4349 

4350 return self.execute_command("BZMPOP", *args) 

4351 

4352 def _zrange( 

4353 self, 

4354 command, 

4355 dest: Union[KeyT, None], 

4356 name: KeyT, 

4357 start: int, 

4358 end: int, 

4359 desc: bool = False, 

4360 byscore: bool = False, 

4361 bylex: bool = False, 

4362 withscores: bool = False, 

4363 score_cast_func: Union[type, Callable, None] = float, 

4364 offset: Union[int, None] = None, 

4365 num: Union[int, None] = None, 

4366 ) -> ResponseT: 

4367 if byscore and bylex: 

4368 raise DataError("``byscore`` and ``bylex`` can not be specified together.") 

4369 if (offset is not None and num is None) or (num is not None and offset is None): 

4370 raise DataError("``offset`` and ``num`` must both be specified.") 

4371 if bylex and withscores: 

4372 raise DataError( 

4373 "``withscores`` not supported in combination with ``bylex``." 

4374 ) 

4375 pieces = [command] 

4376 if dest: 

4377 pieces.append(dest) 

4378 pieces.extend([name, start, end]) 

4379 if byscore: 

4380 pieces.append("BYSCORE") 

4381 if bylex: 

4382 pieces.append("BYLEX") 

4383 if desc: 

4384 pieces.append("REV") 

4385 if offset is not None and num is not None: 

4386 pieces.extend(["LIMIT", offset, num]) 

4387 if withscores: 

4388 pieces.append("WITHSCORES") 

4389 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4390 return self.execute_command(*pieces, **options) 

4391 

4392 def zrange( 

4393 self, 

4394 name: KeyT, 

4395 start: int, 

4396 end: int, 

4397 desc: bool = False, 

4398 withscores: bool = False, 

4399 score_cast_func: Union[type, Callable] = float, 

4400 byscore: bool = False, 

4401 bylex: bool = False, 

4402 offset: int = None, 

4403 num: int = None, 

4404 ) -> ResponseT: 

4405 """ 

4406 Return a range of values from sorted set ``name`` between 

4407 ``start`` and ``end`` sorted in ascending order. 

4408 

4409 ``start`` and ``end`` can be negative, indicating the end of the range. 

4410 

4411 ``desc`` a boolean indicating whether to sort the results in reversed 

4412 order. 

4413 

4414 ``withscores`` indicates to return the scores along with the values. 

4415 The return type is a list of (value, score) pairs. 

4416 

4417 ``score_cast_func`` a callable used to cast the score return value. 

4418 

4419 ``byscore`` when set to True, returns the range of elements from the 

4420 sorted set having scores equal or between ``start`` and ``end``. 

4421 

4422 ``bylex`` when set to True, returns the range of elements from the 

4423 sorted set between the ``start`` and ``end`` lexicographical closed 

4424 range intervals. 

4425 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

4426 whether the range interval is exclusive or inclusive, respectively. 

4427 

4428 ``offset`` and ``num`` are specified, then return a slice of the range. 

4429 Can't be provided when using ``bylex``. 

4430 

4431 For more information see https://redis.io/commands/zrange 

4432 """ 

4433 # Need to support ``desc`` also when using old redis version 

4434 # because it was supported in 3.5.3 (of redis-py) 

4435 if not byscore and not bylex and (offset is None and num is None) and desc: 

4436 return self.zrevrange(name, start, end, withscores, score_cast_func) 

4437 

4438 return self._zrange( 

4439 "ZRANGE", 

4440 None, 

4441 name, 

4442 start, 

4443 end, 

4444 desc, 

4445 byscore, 

4446 bylex, 

4447 withscores, 

4448 score_cast_func, 

4449 offset, 

4450 num, 

4451 ) 

4452 

4453 def zrevrange( 

4454 self, 

4455 name: KeyT, 

4456 start: int, 

4457 end: int, 

4458 withscores: bool = False, 

4459 score_cast_func: Union[type, Callable] = float, 

4460 ) -> ResponseT: 

4461 """ 

4462 Return a range of values from sorted set ``name`` between 

4463 ``start`` and ``end`` sorted in descending order. 

4464 

4465 ``start`` and ``end`` can be negative, indicating the end of the range. 

4466 

4467 ``withscores`` indicates to return the scores along with the values 

4468 The return type is a list of (value, score) pairs 

4469 

4470 ``score_cast_func`` a callable used to cast the score return value 

4471 

4472 For more information see https://redis.io/commands/zrevrange 

4473 """ 

4474 pieces = ["ZREVRANGE", name, start, end] 

4475 if withscores: 

4476 pieces.append(b"WITHSCORES") 

4477 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4478 return self.execute_command(*pieces, **options) 

4479 

4480 def zrangestore( 

4481 self, 

4482 dest: KeyT, 

4483 name: KeyT, 

4484 start: int, 

4485 end: int, 

4486 byscore: bool = False, 

4487 bylex: bool = False, 

4488 desc: bool = False, 

4489 offset: Union[int, None] = None, 

4490 num: Union[int, None] = None, 

4491 ) -> ResponseT: 

4492 """ 

4493 Stores in ``dest`` the result of a range of values from sorted set 

4494 ``name`` between ``start`` and ``end`` sorted in ascending order. 

4495 

4496 ``start`` and ``end`` can be negative, indicating the end of the range. 

4497 

4498 ``byscore`` when set to True, returns the range of elements from the 

4499 sorted set having scores equal or between ``start`` and ``end``. 

4500 

4501 ``bylex`` when set to True, returns the range of elements from the 

4502 sorted set between the ``start`` and ``end`` lexicographical closed 

4503 range intervals. 

4504 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

4505 whether the range interval is exclusive or inclusive, respectively. 

4506 

4507 ``desc`` a boolean indicating whether to sort the results in reversed 

4508 order. 

4509 

4510 ``offset`` and ``num`` are specified, then return a slice of the range. 

4511 Can't be provided when using ``bylex``. 

4512 

4513 For more information see https://redis.io/commands/zrangestore 

4514 """ 

4515 return self._zrange( 

4516 "ZRANGESTORE", 

4517 dest, 

4518 name, 

4519 start, 

4520 end, 

4521 desc, 

4522 byscore, 

4523 bylex, 

4524 False, 

4525 None, 

4526 offset, 

4527 num, 

4528 ) 

4529 

4530 def zrangebylex( 

4531 self, 

4532 name: KeyT, 

4533 min: EncodableT, 

4534 max: EncodableT, 

4535 start: Union[int, None] = None, 

4536 num: Union[int, None] = None, 

4537 ) -> ResponseT: 

4538 """ 

4539 Return the lexicographical range of values from sorted set ``name`` 

4540 between ``min`` and ``max``. 

4541 

4542 If ``start`` and ``num`` are specified, then return a slice of the 

4543 range. 

4544 

4545 For more information see https://redis.io/commands/zrangebylex 

4546 """ 

4547 if (start is not None and num is None) or (num is not None and start is None): 

4548 raise DataError("``start`` and ``num`` must both be specified") 

4549 pieces = ["ZRANGEBYLEX", name, min, max] 

4550 if start is not None and num is not None: 

4551 pieces.extend([b"LIMIT", start, num]) 

4552 return self.execute_command(*pieces) 

4553 

4554 def zrevrangebylex( 

4555 self, 

4556 name: KeyT, 

4557 max: EncodableT, 

4558 min: EncodableT, 

4559 start: Union[int, None] = None, 

4560 num: Union[int, None] = None, 

4561 ) -> ResponseT: 

4562 """ 

4563 Return the reversed lexicographical range of values from sorted set 

4564 ``name`` between ``max`` and ``min``. 

4565 

4566 If ``start`` and ``num`` are specified, then return a slice of the 

4567 range. 

4568 

4569 For more information see https://redis.io/commands/zrevrangebylex 

4570 """ 

4571 if (start is not None and num is None) or (num is not None and start is None): 

4572 raise DataError("``start`` and ``num`` must both be specified") 

4573 pieces = ["ZREVRANGEBYLEX", name, max, min] 

4574 if start is not None and num is not None: 

4575 pieces.extend(["LIMIT", start, num]) 

4576 return self.execute_command(*pieces) 

4577 

4578 def zrangebyscore( 

4579 self, 

4580 name: KeyT, 

4581 min: ZScoreBoundT, 

4582 max: ZScoreBoundT, 

4583 start: Union[int, None] = None, 

4584 num: Union[int, None] = None, 

4585 withscores: bool = False, 

4586 score_cast_func: Union[type, Callable] = float, 

4587 ) -> ResponseT: 

4588 """ 

4589 Return a range of values from the sorted set ``name`` with scores 

4590 between ``min`` and ``max``. 

4591 

4592 If ``start`` and ``num`` are specified, then return a slice 

4593 of the range. 

4594 

4595 ``withscores`` indicates to return the scores along with the values. 

4596 The return type is a list of (value, score) pairs 

4597 

4598 `score_cast_func`` a callable used to cast the score return value 

4599 

4600 For more information see https://redis.io/commands/zrangebyscore 

4601 """ 

4602 if (start is not None and num is None) or (num is not None and start is None): 

4603 raise DataError("``start`` and ``num`` must both be specified") 

4604 pieces = ["ZRANGEBYSCORE", name, min, max] 

4605 if start is not None and num is not None: 

4606 pieces.extend(["LIMIT", start, num]) 

4607 if withscores: 

4608 pieces.append("WITHSCORES") 

4609 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4610 return self.execute_command(*pieces, **options) 

4611 

4612 def zrevrangebyscore( 

4613 self, 

4614 name: KeyT, 

4615 max: ZScoreBoundT, 

4616 min: ZScoreBoundT, 

4617 start: Union[int, None] = None, 

4618 num: Union[int, None] = None, 

4619 withscores: bool = False, 

4620 score_cast_func: Union[type, Callable] = float, 

4621 ): 

4622 """ 

4623 Return a range of values from the sorted set ``name`` with scores 

4624 between ``min`` and ``max`` in descending order. 

4625 

4626 If ``start`` and ``num`` are specified, then return a slice 

4627 of the range. 

4628 

4629 ``withscores`` indicates to return the scores along with the values. 

4630 The return type is a list of (value, score) pairs 

4631 

4632 ``score_cast_func`` a callable used to cast the score return value 

4633 

4634 For more information see https://redis.io/commands/zrevrangebyscore 

4635 """ 

4636 if (start is not None and num is None) or (num is not None and start is None): 

4637 raise DataError("``start`` and ``num`` must both be specified") 

4638 pieces = ["ZREVRANGEBYSCORE", name, max, min] 

4639 if start is not None and num is not None: 

4640 pieces.extend(["LIMIT", start, num]) 

4641 if withscores: 

4642 pieces.append("WITHSCORES") 

4643 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4644 return self.execute_command(*pieces, **options) 

4645 

4646 def zrank(self, name: KeyT, value: EncodableT) -> ResponseT: 

4647 """ 

4648 Returns a 0-based value indicating the rank of ``value`` in sorted set 

4649 ``name`` 

4650 

4651 For more information see https://redis.io/commands/zrank 

4652 """ 

4653 return self.execute_command("ZRANK", name, value) 

4654 

4655 def zrem(self, name: KeyT, *values: FieldT) -> ResponseT: 

4656 """ 

4657 Remove member ``values`` from sorted set ``name`` 

4658 

4659 For more information see https://redis.io/commands/zrem 

4660 """ 

4661 return self.execute_command("ZREM", name, *values) 

4662 

4663 def zremrangebylex(self, name: KeyT, min: EncodableT, max: EncodableT) -> ResponseT: 

4664 """ 

4665 Remove all elements in the sorted set ``name`` between the 

4666 lexicographical range specified by ``min`` and ``max``. 

4667 

4668 Returns the number of elements removed. 

4669 

4670 For more information see https://redis.io/commands/zremrangebylex 

4671 """ 

4672 return self.execute_command("ZREMRANGEBYLEX", name, min, max) 

4673 

4674 def zremrangebyrank(self, name: KeyT, min: int, max: int) -> ResponseT: 

4675 """ 

4676 Remove all elements in the sorted set ``name`` with ranks between 

4677 ``min`` and ``max``. Values are 0-based, ordered from smallest score 

4678 to largest. Values can be negative indicating the highest scores. 

4679 Returns the number of elements removed 

4680 

4681 For more information see https://redis.io/commands/zremrangebyrank 

4682 """ 

4683 return self.execute_command("ZREMRANGEBYRANK", name, min, max) 

4684 

4685 def zremrangebyscore( 

4686 self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT 

4687 ) -> ResponseT: 

4688 """ 

4689 Remove all elements in the sorted set ``name`` with scores 

4690 between ``min`` and ``max``. Returns the number of elements removed. 

4691 

4692 For more information see https://redis.io/commands/zremrangebyscore 

4693 """ 

4694 return self.execute_command("ZREMRANGEBYSCORE", name, min, max) 

4695 

4696 def zrevrank(self, name: KeyT, value: EncodableT) -> ResponseT: 

4697 """ 

4698 Returns a 0-based value indicating the descending rank of 

4699 ``value`` in sorted set ``name`` 

4700 

4701 For more information see https://redis.io/commands/zrevrank 

4702 """ 

4703 return self.execute_command("ZREVRANK", name, value) 

4704 

4705 def zscore(self, name: KeyT, value: EncodableT) -> ResponseT: 

4706 """ 

4707 Return the score of element ``value`` in sorted set ``name`` 

4708 

4709 For more information see https://redis.io/commands/zscore 

4710 """ 

4711 return self.execute_command("ZSCORE", name, value) 

4712 

4713 def zunion( 

4714 self, 

4715 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4716 aggregate: Union[str, None] = None, 

4717 withscores: bool = False, 

4718 ) -> ResponseT: 

4719 """ 

4720 Return the union of multiple sorted sets specified by ``keys``. 

4721 ``keys`` can be provided as dictionary of keys and their weights. 

4722 Scores will be aggregated based on the ``aggregate``, or SUM if 

4723 none is provided. 

4724 

4725 For more information see https://redis.io/commands/zunion 

4726 """ 

4727 return self._zaggregate("ZUNION", None, keys, aggregate, withscores=withscores) 

4728 

4729 def zunionstore( 

4730 self, 

4731 dest: KeyT, 

4732 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4733 aggregate: Union[str, None] = None, 

4734 ) -> ResponseT: 

4735 """ 

4736 Union multiple sorted sets specified by ``keys`` into 

4737 a new sorted set, ``dest``. Scores in the destination will be 

4738 aggregated based on the ``aggregate``, or SUM if none is provided. 

4739 

4740 For more information see https://redis.io/commands/zunionstore 

4741 """ 

4742 return self._zaggregate("ZUNIONSTORE", dest, keys, aggregate) 

4743 

4744 def zmscore(self, key: KeyT, members: List[str]) -> ResponseT: 

4745 """ 

4746 Returns the scores associated with the specified members 

4747 in the sorted set stored at key. 

4748 ``members`` should be a list of the member name. 

4749 Return type is a list of score. 

4750 If the member does not exist, a None will be returned 

4751 in corresponding position. 

4752 

4753 For more information see https://redis.io/commands/zmscore 

4754 """ 

4755 if not members: 

4756 raise DataError("ZMSCORE members must be a non-empty list") 

4757 pieces = [key] + members 

4758 return self.execute_command("ZMSCORE", *pieces) 

4759 

4760 def _zaggregate( 

4761 self, 

4762 command: str, 

4763 dest: Union[KeyT, None], 

4764 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4765 aggregate: Union[str, None] = None, 

4766 **options, 

4767 ) -> ResponseT: 

4768 pieces: list[EncodableT] = [command] 

4769 if dest is not None: 

4770 pieces.append(dest) 

4771 pieces.append(len(keys)) 

4772 if isinstance(keys, dict): 

4773 keys, weights = keys.keys(), keys.values() 

4774 else: 

4775 weights = None 

4776 pieces.extend(keys) 

4777 if weights: 

4778 pieces.append(b"WEIGHTS") 

4779 pieces.extend(weights) 

4780 if aggregate: 

4781 if aggregate.upper() in ["SUM", "MIN", "MAX"]: 

4782 pieces.append(b"AGGREGATE") 

4783 pieces.append(aggregate) 

4784 else: 

4785 raise DataError("aggregate can be sum, min or max.") 

4786 if options.get("withscores", False): 

4787 pieces.append(b"WITHSCORES") 

4788 return self.execute_command(*pieces, **options) 

4789 

4790 

4791AsyncSortedSetCommands = SortedSetCommands 

4792 

4793 

4794class HyperlogCommands(CommandsProtocol): 

4795 """ 

4796 Redis commands of HyperLogLogs data type. 

4797 see: https://redis.io/topics/data-types-intro#hyperloglogs 

4798 """ 

4799 

4800 def pfadd(self, name: KeyT, *values: FieldT) -> ResponseT: 

4801 """ 

4802 Adds the specified elements to the specified HyperLogLog. 

4803 

4804 For more information see https://redis.io/commands/pfadd 

4805 """ 

4806 return self.execute_command("PFADD", name, *values) 

4807 

4808 def pfcount(self, *sources: KeyT) -> ResponseT: 

4809 """ 

4810 Return the approximated cardinality of 

4811 the set observed by the HyperLogLog at key(s). 

4812 

4813 For more information see https://redis.io/commands/pfcount 

4814 """ 

4815 return self.execute_command("PFCOUNT", *sources) 

4816 

4817 def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT: 

4818 """ 

4819 Merge N different HyperLogLogs into a single one. 

4820 

4821 For more information see https://redis.io/commands/pfmerge 

4822 """ 

4823 return self.execute_command("PFMERGE", dest, *sources) 

4824 

4825 

4826AsyncHyperlogCommands = HyperlogCommands 

4827 

4828 

4829class HashCommands(CommandsProtocol): 

4830 """ 

4831 Redis commands for Hash data type. 

4832 see: https://redis.io/topics/data-types-intro#redis-hashes 

4833 """ 

4834 

4835 def hdel(self, name: str, *keys: List) -> Union[Awaitable[int], int]: 

4836 """ 

4837 Delete ``keys`` from hash ``name`` 

4838 

4839 For more information see https://redis.io/commands/hdel 

4840 """ 

4841 return self.execute_command("HDEL", name, *keys) 

4842 

4843 def hexists(self, name: str, key: str) -> Union[Awaitable[bool], bool]: 

4844 """ 

4845 Returns a boolean indicating if ``key`` exists within hash ``name`` 

4846 

4847 For more information see https://redis.io/commands/hexists 

4848 """ 

4849 return self.execute_command("HEXISTS", name, key) 

4850 

4851 def hget( 

4852 self, name: str, key: str 

4853 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

4854 """ 

4855 Return the value of ``key`` within the hash ``name`` 

4856 

4857 For more information see https://redis.io/commands/hget 

4858 """ 

4859 return self.execute_command("HGET", name, key) 

4860 

4861 def hgetall(self, name: str) -> Union[Awaitable[dict], dict]: 

4862 """ 

4863 Return a Python dict of the hash's name/value pairs 

4864 

4865 For more information see https://redis.io/commands/hgetall 

4866 """ 

4867 return self.execute_command("HGETALL", name) 

4868 

4869 def hincrby( 

4870 self, name: str, key: str, amount: int = 1 

4871 ) -> Union[Awaitable[int], int]: 

4872 """ 

4873 Increment the value of ``key`` in hash ``name`` by ``amount`` 

4874 

4875 For more information see https://redis.io/commands/hincrby 

4876 """ 

4877 return self.execute_command("HINCRBY", name, key, amount) 

4878 

4879 def hincrbyfloat( 

4880 self, name: str, key: str, amount: float = 1.0 

4881 ) -> Union[Awaitable[float], float]: 

4882 """ 

4883 Increment the value of ``key`` in hash ``name`` by floating ``amount`` 

4884 

4885 For more information see https://redis.io/commands/hincrbyfloat 

4886 """ 

4887 return self.execute_command("HINCRBYFLOAT", name, key, amount) 

4888 

4889 def hkeys(self, name: str) -> Union[Awaitable[List], List]: 

4890 """ 

4891 Return the list of keys within hash ``name`` 

4892 

4893 For more information see https://redis.io/commands/hkeys 

4894 """ 

4895 return self.execute_command("HKEYS", name) 

4896 

4897 def hlen(self, name: str) -> Union[Awaitable[int], int]: 

4898 """ 

4899 Return the number of elements in hash ``name`` 

4900 

4901 For more information see https://redis.io/commands/hlen 

4902 """ 

4903 return self.execute_command("HLEN", name) 

4904 

4905 def hset( 

4906 self, 

4907 name: str, 

4908 key: Optional[str] = None, 

4909 value: Optional[str] = None, 

4910 mapping: Optional[dict] = None, 

4911 items: Optional[list] = None, 

4912 ) -> Union[Awaitable[int], int]: 

4913 """ 

4914 Set ``key`` to ``value`` within hash ``name``, 

4915 ``mapping`` accepts a dict of key/value pairs that will be 

4916 added to hash ``name``. 

4917 ``items`` accepts a list of key/value pairs that will be 

4918 added to hash ``name``. 

4919 Returns the number of fields that were added. 

4920 

4921 For more information see https://redis.io/commands/hset 

4922 """ 

4923 if key is None and not mapping and not items: 

4924 raise DataError("'hset' with no key value pairs") 

4925 items = items or [] 

4926 if key is not None: 

4927 items.extend((key, value)) 

4928 if mapping: 

4929 for pair in mapping.items(): 

4930 items.extend(pair) 

4931 

4932 return self.execute_command("HSET", name, *items) 

4933 

4934 def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool]: 

4935 """ 

4936 Set ``key`` to ``value`` within hash ``name`` if ``key`` does not 

4937 exist. Returns 1 if HSETNX created a field, otherwise 0. 

4938 

4939 For more information see https://redis.io/commands/hsetnx 

4940 """ 

4941 return self.execute_command("HSETNX", name, key, value) 

4942 

4943 def hmset(self, name: str, mapping: dict) -> Union[Awaitable[str], str]: 

4944 """ 

4945 Set key to value within hash ``name`` for each corresponding 

4946 key and value from the ``mapping`` dict. 

4947 

4948 For more information see https://redis.io/commands/hmset 

4949 """ 

4950 warnings.warn( 

4951 f"{self.__class__.__name__}.hmset() is deprecated. " 

4952 f"Use {self.__class__.__name__}.hset() instead.", 

4953 DeprecationWarning, 

4954 stacklevel=2, 

4955 ) 

4956 if not mapping: 

4957 raise DataError("'hmset' with 'mapping' of length 0") 

4958 items = [] 

4959 for pair in mapping.items(): 

4960 items.extend(pair) 

4961 return self.execute_command("HMSET", name, *items) 

4962 

4963 def hmget(self, name: str, keys: List, *args: List) -> Union[Awaitable[List], List]: 

4964 """ 

4965 Returns a list of values ordered identically to ``keys`` 

4966 

4967 For more information see https://redis.io/commands/hmget 

4968 """ 

4969 args = list_or_args(keys, args) 

4970 return self.execute_command("HMGET", name, *args) 

4971 

4972 def hvals(self, name: str) -> Union[Awaitable[List], List]: 

4973 """ 

4974 Return the list of values within hash ``name`` 

4975 

4976 For more information see https://redis.io/commands/hvals 

4977 """ 

4978 return self.execute_command("HVALS", name) 

4979 

4980 def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]: 

4981 """ 

4982 Return the number of bytes stored in the value of ``key`` 

4983 within hash ``name`` 

4984 

4985 For more information see https://redis.io/commands/hstrlen 

4986 """ 

4987 return self.execute_command("HSTRLEN", name, key) 

4988 

4989 

4990AsyncHashCommands = HashCommands 

4991 

4992 

4993class Script: 

4994 """ 

4995 An executable Lua script object returned by ``register_script`` 

4996 """ 

4997 

4998 def __init__(self, registered_client: "Redis", script: ScriptTextT): 

4999 self.registered_client = registered_client 

5000 self.script = script 

5001 # Precalculate and store the SHA1 hex digest of the script. 

5002 

5003 if isinstance(script, str): 

5004 # We need the encoding from the client in order to generate an 

5005 # accurate byte representation of the script 

5006 try: 

5007 encoder = registered_client.connection_pool.get_encoder() 

5008 except AttributeError: 

5009 # Cluster 

5010 encoder = registered_client.get_encoder() 

5011 script = encoder.encode(script) 

5012 self.sha = hashlib.sha1(script).hexdigest() 

5013 

5014 def __call__( 

5015 self, 

5016 keys: Union[Sequence[KeyT], None] = None, 

5017 args: Union[Iterable[EncodableT], None] = None, 

5018 client: Union["Redis", None] = None, 

5019 ): 

5020 """Execute the script, passing any required ``args``""" 

5021 keys = keys or [] 

5022 args = args or [] 

5023 if client is None: 

5024 client = self.registered_client 

5025 args = tuple(keys) + tuple(args) 

5026 # make sure the Redis server knows about the script 

5027 from redis.client import Pipeline 

5028 

5029 if isinstance(client, Pipeline): 

5030 # Make sure the pipeline can register the script before executing. 

5031 client.scripts.add(self) 

5032 try: 

5033 return client.evalsha(self.sha, len(keys), *args) 

5034 except NoScriptError: 

5035 # Maybe the client is pointed to a different server than the client 

5036 # that created this instance? 

5037 # Overwrite the sha just in case there was a discrepancy. 

5038 self.sha = client.script_load(self.script) 

5039 return client.evalsha(self.sha, len(keys), *args) 

5040 

5041 

5042class AsyncScript: 

5043 """ 

5044 An executable Lua script object returned by ``register_script`` 

5045 """ 

5046 

5047 def __init__(self, registered_client: "AsyncRedis", script: ScriptTextT): 

5048 self.registered_client = registered_client 

5049 self.script = script 

5050 # Precalculate and store the SHA1 hex digest of the script. 

5051 

5052 if isinstance(script, str): 

5053 # We need the encoding from the client in order to generate an 

5054 # accurate byte representation of the script 

5055 try: 

5056 encoder = registered_client.connection_pool.get_encoder() 

5057 except AttributeError: 

5058 # Cluster 

5059 encoder = registered_client.get_encoder() 

5060 script = encoder.encode(script) 

5061 self.sha = hashlib.sha1(script).hexdigest() 

5062 

5063 async def __call__( 

5064 self, 

5065 keys: Union[Sequence[KeyT], None] = None, 

5066 args: Union[Iterable[EncodableT], None] = None, 

5067 client: Union["AsyncRedis", None] = None, 

5068 ): 

5069 """Execute the script, passing any required ``args``""" 

5070 keys = keys or [] 

5071 args = args or [] 

5072 if client is None: 

5073 client = self.registered_client 

5074 args = tuple(keys) + tuple(args) 

5075 # make sure the Redis server knows about the script 

5076 from redis.asyncio.client import Pipeline 

5077 

5078 if isinstance(client, Pipeline): 

5079 # Make sure the pipeline can register the script before executing. 

5080 client.scripts.add(self) 

5081 try: 

5082 return await client.evalsha(self.sha, len(keys), *args) 

5083 except NoScriptError: 

5084 # Maybe the client is pointed to a different server than the client 

5085 # that created this instance? 

5086 # Overwrite the sha just in case there was a discrepancy. 

5087 self.sha = await client.script_load(self.script) 

5088 return await client.evalsha(self.sha, len(keys), *args) 

5089 

5090 

5091class PubSubCommands(CommandsProtocol): 

5092 """ 

5093 Redis PubSub commands. 

5094 see https://redis.io/topics/pubsub 

5095 """ 

5096 

5097 def publish(self, channel: ChannelT, message: EncodableT, **kwargs) -> ResponseT: 

5098 """ 

5099 Publish ``message`` on ``channel``. 

5100 Returns the number of subscribers the message was delivered to. 

5101 

5102 For more information see https://redis.io/commands/publish 

5103 """ 

5104 return self.execute_command("PUBLISH", channel, message, **kwargs) 

5105 

5106 def pubsub_channels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

5107 """ 

5108 Return a list of channels that have at least one subscriber 

5109 

5110 For more information see https://redis.io/commands/pubsub-channels 

5111 """ 

5112 return self.execute_command("PUBSUB CHANNELS", pattern, **kwargs) 

5113 

5114 def pubsub_numpat(self, **kwargs) -> ResponseT: 

5115 """ 

5116 Returns the number of subscriptions to patterns 

5117 

5118 For more information see https://redis.io/commands/pubsub-numpat 

5119 """ 

5120 return self.execute_command("PUBSUB NUMPAT", **kwargs) 

5121 

5122 def pubsub_numsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

5123 """ 

5124 Return a list of (channel, number of subscribers) tuples 

5125 for each channel given in ``*args`` 

5126 

5127 For more information see https://redis.io/commands/pubsub-numsub 

5128 """ 

5129 return self.execute_command("PUBSUB NUMSUB", *args, **kwargs) 

5130 

5131 

5132AsyncPubSubCommands = PubSubCommands 

5133 

5134 

5135class ScriptCommands(CommandsProtocol): 

5136 """ 

5137 Redis Lua script commands. see: 

5138 https://redis.com/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/ 

5139 """ 

5140 

5141 def _eval( 

5142 self, command: str, script: str, numkeys: int, *keys_and_args: list 

5143 ) -> Union[Awaitable[str], str]: 

5144 return self.execute_command(command, script, numkeys, *keys_and_args) 

5145 

5146 def eval( 

5147 self, script: str, numkeys: int, *keys_and_args: list 

5148 ) -> Union[Awaitable[str], str]: 

5149 """ 

5150 Execute the Lua ``script``, specifying the ``numkeys`` the script 

5151 will touch and the key names and argument values in ``keys_and_args``. 

5152 Returns the result of the script. 

5153 

5154 In practice, use the object returned by ``register_script``. This 

5155 function exists purely for Redis API completion. 

5156 

5157 For more information see https://redis.io/commands/eval 

5158 """ 

5159 return self._eval("EVAL", script, numkeys, *keys_and_args) 

5160 

5161 def eval_ro( 

5162 self, script: str, numkeys: int, *keys_and_args: list 

5163 ) -> Union[Awaitable[str], str]: 

5164 """ 

5165 The read-only variant of the EVAL command 

5166 

5167 Execute the read-only Lua ``script`` specifying the ``numkeys`` the script 

5168 will touch and the key names and argument values in ``keys_and_args``. 

5169 Returns the result of the script. 

5170 

5171 For more information see https://redis.io/commands/eval_ro 

5172 """ 

5173 return self._eval("EVAL_RO", script, numkeys, *keys_and_args) 

5174 

5175 def _evalsha( 

5176 self, command: str, sha: str, numkeys: int, *keys_and_args: list 

5177 ) -> Union[Awaitable[str], str]: 

5178 return self.execute_command(command, sha, numkeys, *keys_and_args) 

5179 

5180 def evalsha( 

5181 self, sha: str, numkeys: int, *keys_and_args: list 

5182 ) -> Union[Awaitable[str], str]: 

5183 """ 

5184 Use the ``sha`` to execute a Lua script already registered via EVAL 

5185 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

5186 key names and argument values in ``keys_and_args``. Returns the result 

5187 of the script. 

5188 

5189 In practice, use the object returned by ``register_script``. This 

5190 function exists purely for Redis API completion. 

5191 

5192 For more information see https://redis.io/commands/evalsha 

5193 """ 

5194 return self._evalsha("EVALSHA", sha, numkeys, *keys_and_args) 

5195 

5196 def evalsha_ro( 

5197 self, sha: str, numkeys: int, *keys_and_args: list 

5198 ) -> Union[Awaitable[str], str]: 

5199 """ 

5200 The read-only variant of the EVALSHA command 

5201 

5202 Use the ``sha`` to execute a read-only Lua script already registered via EVAL 

5203 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

5204 key names and argument values in ``keys_and_args``. Returns the result 

5205 of the script. 

5206 

5207 For more information see https://redis.io/commands/evalsha_ro 

5208 """ 

5209 return self._evalsha("EVALSHA_RO", sha, numkeys, *keys_and_args) 

5210 

5211 def script_exists(self, *args: str) -> ResponseT: 

5212 """ 

5213 Check if a script exists in the script cache by specifying the SHAs of 

5214 each script as ``args``. Returns a list of boolean values indicating if 

5215 if each already script exists in the cache. 

5216 

5217 For more information see https://redis.io/commands/script-exists 

5218 """ 

5219 return self.execute_command("SCRIPT EXISTS", *args) 

5220 

5221 def script_debug(self, *args) -> None: 

5222 raise NotImplementedError( 

5223 "SCRIPT DEBUG is intentionally not implemented in the client." 

5224 ) 

5225 

5226 def script_flush( 

5227 self, sync_type: Union[Literal["SYNC"], Literal["ASYNC"]] = None 

5228 ) -> ResponseT: 

5229 """Flush all scripts from the script cache. 

5230 ``sync_type`` is by default SYNC (synchronous) but it can also be 

5231 ASYNC. 

5232 For more information see https://redis.io/commands/script-flush 

5233 """ 

5234 

5235 # Redis pre 6 had no sync_type. 

5236 if sync_type not in ["SYNC", "ASYNC", None]: 

5237 raise DataError( 

5238 "SCRIPT FLUSH defaults to SYNC in redis > 6.2, or " 

5239 "accepts SYNC/ASYNC. For older versions, " 

5240 "of redis leave as None." 

5241 ) 

5242 if sync_type is None: 

5243 pieces = [] 

5244 else: 

5245 pieces = [sync_type] 

5246 return self.execute_command("SCRIPT FLUSH", *pieces) 

5247 

5248 def script_kill(self) -> ResponseT: 

5249 """ 

5250 Kill the currently executing Lua script 

5251 

5252 For more information see https://redis.io/commands/script-kill 

5253 """ 

5254 return self.execute_command("SCRIPT KILL") 

5255 

5256 def script_load(self, script: ScriptTextT) -> ResponseT: 

5257 """ 

5258 Load a Lua ``script`` into the script cache. Returns the SHA. 

5259 

5260 For more information see https://redis.io/commands/script-load 

5261 """ 

5262 return self.execute_command("SCRIPT LOAD", script) 

5263 

5264 def register_script(self: "Redis", script: ScriptTextT) -> Script: 

5265 """ 

5266 Register a Lua ``script`` specifying the ``keys`` it will touch. 

5267 Returns a Script object that is callable and hides the complexity of 

5268 deal with scripts, keys, and shas. This is the preferred way to work 

5269 with Lua scripts. 

5270 """ 

5271 return Script(self, script) 

5272 

5273 

5274class AsyncScriptCommands(ScriptCommands): 

5275 async def script_debug(self, *args) -> None: 

5276 return super().script_debug() 

5277 

5278 def register_script(self: "AsyncRedis", script: ScriptTextT) -> AsyncScript: 

5279 """ 

5280 Register a Lua ``script`` specifying the ``keys`` it will touch. 

5281 Returns a Script object that is callable and hides the complexity of 

5282 deal with scripts, keys, and shas. This is the preferred way to work 

5283 with Lua scripts. 

5284 """ 

5285 return AsyncScript(self, script) 

5286 

5287 

5288class GeoCommands(CommandsProtocol): 

5289 """ 

5290 Redis Geospatial commands. 

5291 see: https://redis.com/redis-best-practices/indexing-patterns/geospatial/ 

5292 """ 

5293 

5294 def geoadd( 

5295 self, 

5296 name: KeyT, 

5297 values: Sequence[EncodableT], 

5298 nx: bool = False, 

5299 xx: bool = False, 

5300 ch: bool = False, 

5301 ) -> ResponseT: 

5302 """ 

5303 Add the specified geospatial items to the specified key identified 

5304 by the ``name`` argument. The Geospatial items are given as ordered 

5305 members of the ``values`` argument, each item or place is formed by 

5306 the triad longitude, latitude and name. 

5307 

5308 Note: You can use ZREM to remove elements. 

5309 

5310 ``nx`` forces ZADD to only create new elements and not to update 

5311 scores for elements that already exist. 

5312 

5313 ``xx`` forces ZADD to only update scores of elements that already 

5314 exist. New elements will not be added. 

5315 

5316 ``ch`` modifies the return value to be the numbers of elements changed. 

5317 Changed elements include new elements that were added and elements 

5318 whose scores changed. 

5319 

5320 For more information see https://redis.io/commands/geoadd 

5321 """ 

5322 if nx and xx: 

5323 raise DataError("GEOADD allows either 'nx' or 'xx', not both") 

5324 if len(values) % 3 != 0: 

5325 raise DataError("GEOADD requires places with lon, lat and name values") 

5326 pieces = [name] 

5327 if nx: 

5328 pieces.append("NX") 

5329 if xx: 

5330 pieces.append("XX") 

5331 if ch: 

5332 pieces.append("CH") 

5333 pieces.extend(values) 

5334 return self.execute_command("GEOADD", *pieces) 

5335 

5336 def geodist( 

5337 self, name: KeyT, place1: FieldT, place2: FieldT, unit: Union[str, None] = None 

5338 ) -> ResponseT: 

5339 """ 

5340 Return the distance between ``place1`` and ``place2`` members of the 

5341 ``name`` key. 

5342 The units must be one of the following : m, km mi, ft. By default 

5343 meters are used. 

5344 

5345 For more information see https://redis.io/commands/geodist 

5346 """ 

5347 pieces: list[EncodableT] = [name, place1, place2] 

5348 if unit and unit not in ("m", "km", "mi", "ft"): 

5349 raise DataError("GEODIST invalid unit") 

5350 elif unit: 

5351 pieces.append(unit) 

5352 return self.execute_command("GEODIST", *pieces) 

5353 

5354 def geohash(self, name: KeyT, *values: FieldT) -> ResponseT: 

5355 """ 

5356 Return the geo hash string for each item of ``values`` members of 

5357 the specified key identified by the ``name`` argument. 

5358 

5359 For more information see https://redis.io/commands/geohash 

5360 """ 

5361 return self.execute_command("GEOHASH", name, *values) 

5362 

5363 def geopos(self, name: KeyT, *values: FieldT) -> ResponseT: 

5364 """ 

5365 Return the positions of each item of ``values`` as members of 

5366 the specified key identified by the ``name`` argument. Each position 

5367 is represented by the pairs lon and lat. 

5368 

5369 For more information see https://redis.io/commands/geopos 

5370 """ 

5371 return self.execute_command("GEOPOS", name, *values) 

5372 

5373 def georadius( 

5374 self, 

5375 name: KeyT, 

5376 longitude: float, 

5377 latitude: float, 

5378 radius: float, 

5379 unit: Union[str, None] = None, 

5380 withdist: bool = False, 

5381 withcoord: bool = False, 

5382 withhash: bool = False, 

5383 count: Union[int, None] = None, 

5384 sort: Union[str, None] = None, 

5385 store: Union[KeyT, None] = None, 

5386 store_dist: Union[KeyT, None] = None, 

5387 any: bool = False, 

5388 ) -> ResponseT: 

5389 """ 

5390 Return the members of the specified key identified by the 

5391 ``name`` argument which are within the borders of the area specified 

5392 with the ``latitude`` and ``longitude`` location and the maximum 

5393 distance from the center specified by the ``radius`` value. 

5394 

5395 The units must be one of the following : m, km mi, ft. By default 

5396 

5397 ``withdist`` indicates to return the distances of each place. 

5398 

5399 ``withcoord`` indicates to return the latitude and longitude of 

5400 each place. 

5401 

5402 ``withhash`` indicates to return the geohash string of each place. 

5403 

5404 ``count`` indicates to return the number of elements up to N. 

5405 

5406 ``sort`` indicates to return the places in a sorted way, ASC for 

5407 nearest to fairest and DESC for fairest to nearest. 

5408 

5409 ``store`` indicates to save the places names in a sorted set named 

5410 with a specific key, each element of the destination sorted set is 

5411 populated with the score got from the original geo sorted set. 

5412 

5413 ``store_dist`` indicates to save the places names in a sorted set 

5414 named with a specific key, instead of ``store`` the sorted set 

5415 destination score is set with the distance. 

5416 

5417 For more information see https://redis.io/commands/georadius 

5418 """ 

5419 return self._georadiusgeneric( 

5420 "GEORADIUS", 

5421 name, 

5422 longitude, 

5423 latitude, 

5424 radius, 

5425 unit=unit, 

5426 withdist=withdist, 

5427 withcoord=withcoord, 

5428 withhash=withhash, 

5429 count=count, 

5430 sort=sort, 

5431 store=store, 

5432 store_dist=store_dist, 

5433 any=any, 

5434 ) 

5435 

5436 def georadiusbymember( 

5437 self, 

5438 name: KeyT, 

5439 member: FieldT, 

5440 radius: float, 

5441 unit: Union[str, None] = None, 

5442 withdist: bool = False, 

5443 withcoord: bool = False, 

5444 withhash: bool = False, 

5445 count: Union[int, None] = None, 

5446 sort: Union[str, None] = None, 

5447 store: Union[KeyT, None] = None, 

5448 store_dist: Union[KeyT, None] = None, 

5449 any: bool = False, 

5450 ) -> ResponseT: 

5451 """ 

5452 This command is exactly like ``georadius`` with the sole difference 

5453 that instead of taking, as the center of the area to query, a longitude 

5454 and latitude value, it takes the name of a member already existing 

5455 inside the geospatial index represented by the sorted set. 

5456 

5457 For more information see https://redis.io/commands/georadiusbymember 

5458 """ 

5459 return self._georadiusgeneric( 

5460 "GEORADIUSBYMEMBER", 

5461 name, 

5462 member, 

5463 radius, 

5464 unit=unit, 

5465 withdist=withdist, 

5466 withcoord=withcoord, 

5467 withhash=withhash, 

5468 count=count, 

5469 sort=sort, 

5470 store=store, 

5471 store_dist=store_dist, 

5472 any=any, 

5473 ) 

5474 

5475 def _georadiusgeneric( 

5476 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

5477 ) -> ResponseT: 

5478 pieces = list(args) 

5479 if kwargs["unit"] and kwargs["unit"] not in ("m", "km", "mi", "ft"): 

5480 raise DataError("GEORADIUS invalid unit") 

5481 elif kwargs["unit"]: 

5482 pieces.append(kwargs["unit"]) 

5483 else: 

5484 pieces.append("m") 

5485 

5486 if kwargs["any"] and kwargs["count"] is None: 

5487 raise DataError("``any`` can't be provided without ``count``") 

5488 

5489 for arg_name, byte_repr in ( 

5490 ("withdist", "WITHDIST"), 

5491 ("withcoord", "WITHCOORD"), 

5492 ("withhash", "WITHHASH"), 

5493 ): 

5494 if kwargs[arg_name]: 

5495 pieces.append(byte_repr) 

5496 

5497 if kwargs["count"] is not None: 

5498 pieces.extend(["COUNT", kwargs["count"]]) 

5499 if kwargs["any"]: 

5500 pieces.append("ANY") 

5501 

5502 if kwargs["sort"]: 

5503 if kwargs["sort"] == "ASC": 

5504 pieces.append("ASC") 

5505 elif kwargs["sort"] == "DESC": 

5506 pieces.append("DESC") 

5507 else: 

5508 raise DataError("GEORADIUS invalid sort") 

5509 

5510 if kwargs["store"] and kwargs["store_dist"]: 

5511 raise DataError("GEORADIUS store and store_dist cant be set together") 

5512 

5513 if kwargs["store"]: 

5514 pieces.extend([b"STORE", kwargs["store"]]) 

5515 

5516 if kwargs["store_dist"]: 

5517 pieces.extend([b"STOREDIST", kwargs["store_dist"]]) 

5518 

5519 return self.execute_command(command, *pieces, **kwargs) 

5520 

5521 def geosearch( 

5522 self, 

5523 name: KeyT, 

5524 member: Union[FieldT, None] = None, 

5525 longitude: Union[float, None] = None, 

5526 latitude: Union[float, None] = None, 

5527 unit: str = "m", 

5528 radius: Union[float, None] = None, 

5529 width: Union[float, None] = None, 

5530 height: Union[float, None] = None, 

5531 sort: Union[str, None] = None, 

5532 count: Union[int, None] = None, 

5533 any: bool = False, 

5534 withcoord: bool = False, 

5535 withdist: bool = False, 

5536 withhash: bool = False, 

5537 ) -> ResponseT: 

5538 """ 

5539 Return the members of specified key identified by the 

5540 ``name`` argument, which are within the borders of the 

5541 area specified by a given shape. This command extends the 

5542 GEORADIUS command, so in addition to searching within circular 

5543 areas, it supports searching within rectangular areas. 

5544 This command should be used in place of the deprecated 

5545 GEORADIUS and GEORADIUSBYMEMBER commands. 

5546 ``member`` Use the position of the given existing 

5547 member in the sorted set. Can't be given with ``longitude`` 

5548 and ``latitude``. 

5549 ``longitude`` and ``latitude`` Use the position given by 

5550 this coordinates. Can't be given with ``member`` 

5551 ``radius`` Similar to GEORADIUS, search inside circular 

5552 area according the given radius. Can't be given with 

5553 ``height`` and ``width``. 

5554 ``height`` and ``width`` Search inside an axis-aligned 

5555 rectangle, determined by the given height and width. 

5556 Can't be given with ``radius`` 

5557 ``unit`` must be one of the following : m, km, mi, ft. 

5558 `m` for meters (the default value), `km` for kilometers, 

5559 `mi` for miles and `ft` for feet. 

5560 ``sort`` indicates to return the places in a sorted way, 

5561 ASC for nearest to furthest and DESC for furthest to nearest. 

5562 ``count`` limit the results to the first count matching items. 

5563 ``any`` is set to True, the command will return as soon as 

5564 enough matches are found. Can't be provided without ``count`` 

5565 ``withdist`` indicates to return the distances of each place. 

5566 ``withcoord`` indicates to return the latitude and longitude of 

5567 each place. 

5568 ``withhash`` indicates to return the geohash string of each place. 

5569 

5570 For more information see https://redis.io/commands/geosearch 

5571 """ 

5572 

5573 return self._geosearchgeneric( 

5574 "GEOSEARCH", 

5575 name, 

5576 member=member, 

5577 longitude=longitude, 

5578 latitude=latitude, 

5579 unit=unit, 

5580 radius=radius, 

5581 width=width, 

5582 height=height, 

5583 sort=sort, 

5584 count=count, 

5585 any=any, 

5586 withcoord=withcoord, 

5587 withdist=withdist, 

5588 withhash=withhash, 

5589 store=None, 

5590 store_dist=None, 

5591 ) 

5592 

5593 def geosearchstore( 

5594 self, 

5595 dest: KeyT, 

5596 name: KeyT, 

5597 member: Union[FieldT, None] = None, 

5598 longitude: Union[float, None] = None, 

5599 latitude: Union[float, None] = None, 

5600 unit: str = "m", 

5601 radius: Union[float, None] = None, 

5602 width: Union[float, None] = None, 

5603 height: Union[float, None] = None, 

5604 sort: Union[str, None] = None, 

5605 count: Union[int, None] = None, 

5606 any: bool = False, 

5607 storedist: bool = False, 

5608 ) -> ResponseT: 

5609 """ 

5610 This command is like GEOSEARCH, but stores the result in 

5611 ``dest``. By default, it stores the results in the destination 

5612 sorted set with their geospatial information. 

5613 if ``store_dist`` set to True, the command will stores the 

5614 items in a sorted set populated with their distance from the 

5615 center of the circle or box, as a floating-point number. 

5616 

5617 For more information see https://redis.io/commands/geosearchstore 

5618 """ 

5619 return self._geosearchgeneric( 

5620 "GEOSEARCHSTORE", 

5621 dest, 

5622 name, 

5623 member=member, 

5624 longitude=longitude, 

5625 latitude=latitude, 

5626 unit=unit, 

5627 radius=radius, 

5628 width=width, 

5629 height=height, 

5630 sort=sort, 

5631 count=count, 

5632 any=any, 

5633 withcoord=None, 

5634 withdist=None, 

5635 withhash=None, 

5636 store=None, 

5637 store_dist=storedist, 

5638 ) 

5639 

5640 def _geosearchgeneric( 

5641 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

5642 ) -> ResponseT: 

5643 pieces = list(args) 

5644 

5645 # FROMMEMBER or FROMLONLAT 

5646 if kwargs["member"] is None: 

5647 if kwargs["longitude"] is None or kwargs["latitude"] is None: 

5648 raise DataError("GEOSEARCH must have member or longitude and latitude") 

5649 if kwargs["member"]: 

5650 if kwargs["longitude"] or kwargs["latitude"]: 

5651 raise DataError( 

5652 "GEOSEARCH member and longitude or latitude cant be set together" 

5653 ) 

5654 pieces.extend([b"FROMMEMBER", kwargs["member"]]) 

5655 if kwargs["longitude"] is not None and kwargs["latitude"] is not None: 

5656 pieces.extend([b"FROMLONLAT", kwargs["longitude"], kwargs["latitude"]]) 

5657 

5658 # BYRADIUS or BYBOX 

5659 if kwargs["radius"] is None: 

5660 if kwargs["width"] is None or kwargs["height"] is None: 

5661 raise DataError("GEOSEARCH must have radius or width and height") 

5662 if kwargs["unit"] is None: 

5663 raise DataError("GEOSEARCH must have unit") 

5664 if kwargs["unit"].lower() not in ("m", "km", "mi", "ft"): 

5665 raise DataError("GEOSEARCH invalid unit") 

5666 if kwargs["radius"]: 

5667 if kwargs["width"] or kwargs["height"]: 

5668 raise DataError( 

5669 "GEOSEARCH radius and width or height cant be set together" 

5670 ) 

5671 pieces.extend([b"BYRADIUS", kwargs["radius"], kwargs["unit"]]) 

5672 if kwargs["width"] and kwargs["height"]: 

5673 pieces.extend([b"BYBOX", kwargs["width"], kwargs["height"], kwargs["unit"]]) 

5674 

5675 # sort 

5676 if kwargs["sort"]: 

5677 if kwargs["sort"].upper() == "ASC": 

5678 pieces.append(b"ASC") 

5679 elif kwargs["sort"].upper() == "DESC": 

5680 pieces.append(b"DESC") 

5681 else: 

5682 raise DataError("GEOSEARCH invalid sort") 

5683 

5684 # count any 

5685 if kwargs["count"]: 

5686 pieces.extend([b"COUNT", kwargs["count"]]) 

5687 if kwargs["any"]: 

5688 pieces.append(b"ANY") 

5689 elif kwargs["any"]: 

5690 raise DataError("GEOSEARCH ``any`` can't be provided without count") 

5691 

5692 # other properties 

5693 for arg_name, byte_repr in ( 

5694 ("withdist", b"WITHDIST"), 

5695 ("withcoord", b"WITHCOORD"), 

5696 ("withhash", b"WITHHASH"), 

5697 ("store_dist", b"STOREDIST"), 

5698 ): 

5699 if kwargs[arg_name]: 

5700 pieces.append(byte_repr) 

5701 

5702 return self.execute_command(command, *pieces, **kwargs) 

5703 

5704 

5705AsyncGeoCommands = GeoCommands 

5706 

5707 

5708class ModuleCommands(CommandsProtocol): 

5709 """ 

5710 Redis Module commands. 

5711 see: https://redis.io/topics/modules-intro 

5712 """ 

5713 

5714 def module_load(self, path, *args) -> ResponseT: 

5715 """ 

5716 Loads the module from ``path``. 

5717 Passes all ``*args`` to the module, during loading. 

5718 Raises ``ModuleError`` if a module is not found at ``path``. 

5719 

5720 For more information see https://redis.io/commands/module-load 

5721 """ 

5722 return self.execute_command("MODULE LOAD", path, *args) 

5723 

5724 def module_loadex( 

5725 self, 

5726 path: str, 

5727 options: Optional[List[str]] = None, 

5728 args: Optional[List[str]] = None, 

5729 ) -> ResponseT: 

5730 """ 

5731 Loads a module from a dynamic library at runtime with configuration directives. 

5732 

5733 For more information see https://redis.io/commands/module-loadex 

5734 """ 

5735 pieces = [] 

5736 if options is not None: 

5737 pieces.append("CONFIG") 

5738 pieces.extend(options) 

5739 if args is not None: 

5740 pieces.append("ARGS") 

5741 pieces.extend(args) 

5742 

5743 return self.execute_command("MODULE LOADEX", path, *pieces) 

5744 

5745 def module_unload(self, name) -> ResponseT: 

5746 """ 

5747 Unloads the module ``name``. 

5748 Raises ``ModuleError`` if ``name`` is not in loaded modules. 

5749 

5750 For more information see https://redis.io/commands/module-unload 

5751 """ 

5752 return self.execute_command("MODULE UNLOAD", name) 

5753 

5754 def module_list(self) -> ResponseT: 

5755 """ 

5756 Returns a list of dictionaries containing the name and version of 

5757 all loaded modules. 

5758 

5759 For more information see https://redis.io/commands/module-list 

5760 """ 

5761 return self.execute_command("MODULE LIST") 

5762 

5763 def command_info(self) -> None: 

5764 raise NotImplementedError( 

5765 "COMMAND INFO is intentionally not implemented in the client." 

5766 ) 

5767 

5768 def command_count(self) -> ResponseT: 

5769 return self.execute_command("COMMAND COUNT") 

5770 

5771 def command_getkeys(self, *args) -> ResponseT: 

5772 return self.execute_command("COMMAND GETKEYS", *args) 

5773 

5774 def command(self) -> ResponseT: 

5775 return self.execute_command("COMMAND") 

5776 

5777 

5778class Script: 

5779 """ 

5780 An executable Lua script object returned by ``register_script`` 

5781 """ 

5782 

5783 def __init__(self, registered_client, script): 

5784 self.registered_client = registered_client 

5785 self.script = script 

5786 # Precalculate and store the SHA1 hex digest of the script. 

5787 

5788 if isinstance(script, str): 

5789 # We need the encoding from the client in order to generate an 

5790 # accurate byte representation of the script 

5791 encoder = self.get_encoder() 

5792 script = encoder.encode(script) 

5793 self.sha = hashlib.sha1(script).hexdigest() 

5794 

5795 def __call__(self, keys=[], args=[], client=None): 

5796 "Execute the script, passing any required ``args``" 

5797 if client is None: 

5798 client = self.registered_client 

5799 args = tuple(keys) + tuple(args) 

5800 # make sure the Redis server knows about the script 

5801 from redis.client import Pipeline 

5802 

5803 if isinstance(client, Pipeline): 

5804 # Make sure the pipeline can register the script before executing. 

5805 client.scripts.add(self) 

5806 try: 

5807 return client.evalsha(self.sha, len(keys), *args) 

5808 except NoScriptError: 

5809 # Maybe the client is pointed to a different server than the client 

5810 # that created this instance? 

5811 # Overwrite the sha just in case there was a discrepancy. 

5812 self.sha = client.script_load(self.script) 

5813 return client.evalsha(self.sha, len(keys), *args) 

5814 

5815 def get_encoder(self): 

5816 """Get the encoder to encode string scripts into bytes.""" 

5817 try: 

5818 return self.registered_client.get_encoder() 

5819 except AttributeError: 

5820 # DEPRECATED 

5821 # In version <=4.1.2, this was the code we used to get the encoder. 

5822 # However, after 4.1.2 we added support for scripting in clustered 

5823 # redis. ClusteredRedis doesn't have a `.connection_pool` attribute 

5824 # so we changed the Script class to use 

5825 # `self.registered_client.get_encoder` (see above). 

5826 # However, that is technically a breaking change, as consumers who 

5827 # use Scripts directly might inject a `registered_client` that 

5828 # doesn't have a `.get_encoder` field. This try/except prevents us 

5829 # from breaking backward-compatibility. Ideally, it would be 

5830 # removed in the next major release. 

5831 return self.registered_client.connection_pool.get_encoder() 

5832 

5833 

5834class AsyncModuleCommands(ModuleCommands): 

5835 async def command_info(self) -> None: 

5836 return super().command_info() 

5837 

5838 

5839class ClusterCommands(CommandsProtocol): 

5840 """ 

5841 Class for Redis Cluster commands 

5842 """ 

5843 

5844 def cluster(self, cluster_arg, *args, **kwargs) -> ResponseT: 

5845 return self.execute_command(f"CLUSTER {cluster_arg.upper()}", *args, **kwargs) 

5846 

5847 def readwrite(self, **kwargs) -> ResponseT: 

5848 """ 

5849 Disables read queries for a connection to a Redis Cluster slave node. 

5850 

5851 For more information see https://redis.io/commands/readwrite 

5852 """ 

5853 return self.execute_command("READWRITE", **kwargs) 

5854 

5855 def readonly(self, **kwargs) -> ResponseT: 

5856 """ 

5857 Enables read queries for a connection to a Redis Cluster replica node. 

5858 

5859 For more information see https://redis.io/commands/readonly 

5860 """ 

5861 return self.execute_command("READONLY", **kwargs) 

5862 

5863 

5864AsyncClusterCommands = ClusterCommands 

5865 

5866 

5867class FunctionCommands: 

5868 """ 

5869 Redis Function commands 

5870 """ 

5871 

5872 def function_load( 

5873 self, code: str, replace: Optional[bool] = False 

5874 ) -> Union[Awaitable[str], str]: 

5875 """ 

5876 Load a library to Redis. 

5877 :param code: the source code (must start with 

5878 Shebang statement that provides a metadata about the library) 

5879 :param replace: changes the behavior to overwrite the existing library 

5880 with the new contents. 

5881 Return the library name that was loaded. 

5882 

5883 For more information see https://redis.io/commands/function-load 

5884 """ 

5885 pieces = ["REPLACE"] if replace else [] 

5886 pieces.append(code) 

5887 return self.execute_command("FUNCTION LOAD", *pieces) 

5888 

5889 def function_delete(self, library: str) -> Union[Awaitable[str], str]: 

5890 """ 

5891 Delete the library called ``library`` and all its functions. 

5892 

5893 For more information see https://redis.io/commands/function-delete 

5894 """ 

5895 return self.execute_command("FUNCTION DELETE", library) 

5896 

5897 def function_flush(self, mode: str = "SYNC") -> Union[Awaitable[str], str]: 

5898 """ 

5899 Deletes all the libraries. 

5900 

5901 For more information see https://redis.io/commands/function-flush 

5902 """ 

5903 return self.execute_command("FUNCTION FLUSH", mode) 

5904 

5905 def function_list( 

5906 self, library: Optional[str] = "*", withcode: Optional[bool] = False 

5907 ) -> Union[Awaitable[List], List]: 

5908 """ 

5909 Return information about the functions and libraries. 

5910 :param library: pecify a pattern for matching library names 

5911 :param withcode: cause the server to include the libraries source 

5912 implementation in the reply 

5913 """ 

5914 args = ["LIBRARYNAME", library] 

5915 if withcode: 

5916 args.append("WITHCODE") 

5917 return self.execute_command("FUNCTION LIST", *args) 

5918 

5919 def _fcall( 

5920 self, command: str, function, numkeys: int, *keys_and_args: Optional[List] 

5921 ) -> Union[Awaitable[str], str]: 

5922 return self.execute_command(command, function, numkeys, *keys_and_args) 

5923 

5924 def fcall( 

5925 self, function, numkeys: int, *keys_and_args: Optional[List] 

5926 ) -> Union[Awaitable[str], str]: 

5927 """ 

5928 Invoke a function. 

5929 

5930 For more information see https://redis.io/commands/fcall 

5931 """ 

5932 return self._fcall("FCALL", function, numkeys, *keys_and_args) 

5933 

5934 def fcall_ro( 

5935 self, function, numkeys: int, *keys_and_args: Optional[List] 

5936 ) -> Union[Awaitable[str], str]: 

5937 """ 

5938 This is a read-only variant of the FCALL command that cannot 

5939 execute commands that modify data. 

5940 

5941 For more information see https://redis.io/commands/fcal_ro 

5942 """ 

5943 return self._fcall("FCALL_RO", function, numkeys, *keys_and_args) 

5944 

5945 def function_dump(self) -> Union[Awaitable[str], str]: 

5946 """ 

5947 Return the serialized payload of loaded libraries. 

5948 

5949 For more information see https://redis.io/commands/function-dump 

5950 """ 

5951 from redis.client import NEVER_DECODE 

5952 

5953 options = {} 

5954 options[NEVER_DECODE] = [] 

5955 

5956 return self.execute_command("FUNCTION DUMP", **options) 

5957 

5958 def function_restore( 

5959 self, payload: str, policy: Optional[str] = "APPEND" 

5960 ) -> Union[Awaitable[str], str]: 

5961 """ 

5962 Restore libraries from the serialized ``payload``. 

5963 You can use the optional policy argument to provide a policy 

5964 for handling existing libraries. 

5965 

5966 For more information see https://redis.io/commands/function-restore 

5967 """ 

5968 return self.execute_command("FUNCTION RESTORE", payload, policy) 

5969 

5970 def function_kill(self) -> Union[Awaitable[str], str]: 

5971 """ 

5972 Kill a function that is currently executing. 

5973 

5974 For more information see https://redis.io/commands/function-kill 

5975 """ 

5976 return self.execute_command("FUNCTION KILL") 

5977 

5978 def function_stats(self) -> Union[Awaitable[List], List]: 

5979 """ 

5980 Return information about the function that's currently running 

5981 and information about the available execution engines. 

5982 

5983 For more information see https://redis.io/commands/function-stats 

5984 """ 

5985 return self.execute_command("FUNCTION STATS") 

5986 

5987 

5988AsyncFunctionCommands = FunctionCommands 

5989 

5990 

5991class DataAccessCommands( 

5992 BasicKeyCommands, 

5993 HyperlogCommands, 

5994 HashCommands, 

5995 GeoCommands, 

5996 ListCommands, 

5997 ScanCommands, 

5998 SetCommands, 

5999 StreamCommands, 

6000 SortedSetCommands, 

6001): 

6002 """ 

6003 A class containing all of the implemented data access redis commands. 

6004 This class is to be used as a mixin for synchronous Redis clients. 

6005 """ 

6006 

6007 

6008class AsyncDataAccessCommands( 

6009 AsyncBasicKeyCommands, 

6010 AsyncHyperlogCommands, 

6011 AsyncHashCommands, 

6012 AsyncGeoCommands, 

6013 AsyncListCommands, 

6014 AsyncScanCommands, 

6015 AsyncSetCommands, 

6016 AsyncStreamCommands, 

6017 AsyncSortedSetCommands, 

6018): 

6019 """ 

6020 A class containing all of the implemented data access redis commands. 

6021 This class is to be used as a mixin for asynchronous Redis clients. 

6022 """ 

6023 

6024 

6025class CoreCommands( 

6026 ACLCommands, 

6027 ClusterCommands, 

6028 DataAccessCommands, 

6029 ManagementCommands, 

6030 ModuleCommands, 

6031 PubSubCommands, 

6032 ScriptCommands, 

6033 FunctionCommands, 

6034): 

6035 """ 

6036 A class containing all of the implemented redis commands. This class is 

6037 to be used as a mixin for synchronous Redis clients. 

6038 """ 

6039 

6040 

6041class AsyncCoreCommands( 

6042 AsyncACLCommands, 

6043 AsyncClusterCommands, 

6044 AsyncDataAccessCommands, 

6045 AsyncManagementCommands, 

6046 AsyncModuleCommands, 

6047 AsyncPubSubCommands, 

6048 AsyncScriptCommands, 

6049 AsyncFunctionCommands, 

6050): 

6051 """ 

6052 A class containing all of the implemented redis commands. This class is 

6053 to be used as a mixin for asynchronous Redis clients. 

6054 """