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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1918 statements  

1# from __future__ import annotations 

2 

3import datetime 

4import hashlib 

5import warnings 

6from enum import Enum 

7from typing import ( 

8 TYPE_CHECKING, 

9 Any, 

10 AsyncIterator, 

11 Awaitable, 

12 Callable, 

13 Dict, 

14 Iterable, 

15 Iterator, 

16 List, 

17 Literal, 

18 Mapping, 

19 Optional, 

20 Sequence, 

21 Set, 

22 Tuple, 

23 Union, 

24) 

25 

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

27from redis.typing import ( 

28 AbsExpiryT, 

29 AnyKeyT, 

30 BitfieldOffsetT, 

31 ChannelT, 

32 CommandsProtocol, 

33 ConsumerT, 

34 EncodableT, 

35 ExpiryT, 

36 FieldT, 

37 GroupT, 

38 KeysT, 

39 KeyT, 

40 Number, 

41 PatternT, 

42 ResponseT, 

43 ScriptTextT, 

44 StreamIdT, 

45 TimeoutSecT, 

46 ZScoreBoundT, 

47) 

48from redis.utils import ( 

49 deprecated_function, 

50 extract_expire_flags, 

51) 

52 

53from .helpers import list_or_args 

54 

55if TYPE_CHECKING: 

56 import redis.asyncio.client 

57 import redis.client 

58 

59 

60class ACLCommands(CommandsProtocol): 

61 """ 

62 Redis Access Control List (ACL) commands. 

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

64 """ 

65 

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

67 """ 

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

69 

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

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

72 that category. 

73 

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

75 """ 

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

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

78 

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

80 """ 

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

82 

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

84 """ 

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

86 

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

88 """ 

89 Delete the ACL for the specified ``username``\\s 

90 

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

92 """ 

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

94 

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

96 """Generate a random password value. 

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

98 the next multiple of 4. 

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

100 """ 

101 pieces = [] 

102 if bits is not None: 

103 try: 

104 b = int(bits) 

105 if b < 0 or b > 4096: 

106 raise ValueError 

107 pieces.append(b) 

108 except ValueError: 

109 raise DataError( 

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

111 ) 

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

113 

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

115 """ 

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

117 

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

119 

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

121 """ 

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

123 

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

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

126 the different subcommands. 

127 

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

129 """ 

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

131 

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

133 """ 

134 Return a list of all ACLs on the server 

135 

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

137 """ 

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

139 

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

141 """ 

142 Get ACL logs as a list. 

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

144 :rtype: List. 

145 

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

147 """ 

148 args = [] 

149 if count is not None: 

150 if not isinstance(count, int): 

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

152 args.append(count) 

153 

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

155 

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

157 """ 

158 Reset ACL logs. 

159 :rtype: Boolean. 

160 

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

162 """ 

163 args = [b"RESET"] 

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

165 

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

167 """ 

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

169 

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

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

172 

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

174 """ 

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

176 

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

178 """ 

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

180 

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

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

183 

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

185 """ 

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

187 

188 def acl_setuser( 

189 self, 

190 username: str, 

191 enabled: bool = False, 

192 nopass: bool = False, 

193 passwords: Optional[Union[str, Iterable[str]]] = None, 

194 hashed_passwords: Optional[Union[str, Iterable[str]]] = None, 

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

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

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

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

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

200 reset: bool = False, 

201 reset_keys: bool = False, 

202 reset_channels: bool = False, 

203 reset_passwords: bool = False, 

204 **kwargs, 

205 ) -> ResponseT: 

206 """ 

207 Create or update an ACL user. 

208 

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

210 the existing ACL is completely overwritten and replaced with the 

211 specified values. 

212 

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

214 

215 Args: 

216 username: The name of the user whose ACL is to be created or updated. 

217 enabled: Indicates whether the user should be allowed to authenticate. 

218 Defaults to `False`. 

219 nopass: Indicates whether the user can authenticate without a password. 

220 This cannot be `True` if `passwords` are also specified. 

221 passwords: A list of plain text passwords to add to or remove from the user. 

222 Each password must be prefixed with a '+' to add or a '-' to 

223 remove. For convenience, a single prefixed string can be used 

224 when adding or removing a single password. 

225 hashed_passwords: A list of SHA-256 hashed passwords to add to or remove 

226 from the user. Each hashed password must be prefixed with 

227 a '+' to add or a '-' to remove. For convenience, a single 

228 prefixed string can be used when adding or removing a 

229 single password. 

230 categories: A list of strings representing category permissions. Each string 

231 must be prefixed with either a '+' to add the category 

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

233 commands: A list of strings representing command permissions. Each string 

234 must be prefixed with either a '+' to add the command permission 

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

236 keys: A list of key patterns to grant the user access to. Key patterns allow 

237 ``'*'`` to support wildcard matching. For example, ``'*'`` grants 

238 access to all keys while ``'cache:*'`` grants access to all keys that 

239 are prefixed with ``cache:``. 

240 `keys` should not be prefixed with a ``'~'``. 

241 reset: Indicates whether the user should be fully reset prior to applying 

242 the new ACL. Setting this to `True` will remove all existing 

243 passwords, flags, and privileges from the user and then apply the 

244 specified rules. If `False`, the user's existing passwords, flags, 

245 and privileges will be kept and any new specified rules will be 

246 applied on top. 

247 reset_keys: Indicates whether the user's key permissions should be reset 

248 prior to applying any new key permissions specified in `keys`. 

249 If `False`, the user's existing key permissions will be kept and 

250 any new specified key permissions will be applied on top. 

251 reset_channels: Indicates whether the user's channel permissions should be 

252 reset prior to applying any new channel permissions 

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

254 channel permissions will be kept and any new specified 

255 channel permissions will be applied on top. 

256 reset_passwords: Indicates whether to remove all existing passwords and the 

257 `nopass` flag from the user prior to applying any new 

258 passwords specified in `passwords` or `hashed_passwords`. 

259 If `False`, the user's existing passwords and `nopass` 

260 status will be kept and any new specified passwords or 

261 hashed passwords will be applied on top. 

262 """ 

263 encoder = self.get_encoder() 

264 pieces: List[EncodableT] = [username] 

265 

266 if reset: 

267 pieces.append(b"reset") 

268 

269 if reset_keys: 

270 pieces.append(b"resetkeys") 

271 

272 if reset_channels: 

273 pieces.append(b"resetchannels") 

274 

275 if reset_passwords: 

276 pieces.append(b"resetpass") 

277 

278 if enabled: 

279 pieces.append(b"on") 

280 else: 

281 pieces.append(b"off") 

282 

283 if (passwords or hashed_passwords) and nopass: 

284 raise DataError( 

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

286 ) 

287 

288 if passwords: 

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

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

291 passwords = list_or_args(passwords, []) 

292 for i, password in enumerate(passwords): 

293 password = encoder.encode(password) 

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

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

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

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

298 else: 

299 raise DataError( 

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

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

302 ) 

303 

304 if hashed_passwords: 

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

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

307 hashed_passwords = list_or_args(hashed_passwords, []) 

308 for i, hashed_password in enumerate(hashed_passwords): 

309 hashed_password = encoder.encode(hashed_password) 

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

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

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

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

314 else: 

315 raise DataError( 

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

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

318 ) 

319 

320 if nopass: 

321 pieces.append(b"nopass") 

322 

323 if categories: 

324 for category in categories: 

325 category = encoder.encode(category) 

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

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

328 pieces.append(category) 

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

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

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

332 pieces.append(category) 

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

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

335 else: 

336 raise DataError( 

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

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

339 ) 

340 if commands: 

341 for cmd in commands: 

342 cmd = encoder.encode(cmd) 

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

344 raise DataError( 

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

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

347 ) 

348 pieces.append(cmd) 

349 

350 if keys: 

351 for key in keys: 

352 key = encoder.encode(key) 

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

354 key = b"~%s" % key 

355 pieces.append(key) 

356 

357 if channels: 

358 for channel in channels: 

359 channel = encoder.encode(channel) 

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

361 

362 if selectors: 

363 for cmd, key in selectors: 

364 cmd = encoder.encode(cmd) 

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

366 raise DataError( 

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

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

369 ) 

370 

371 key = encoder.encode(key) 

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

373 key = b"~%s" % key 

374 

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

376 

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

378 

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

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

381 

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

383 """ 

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

385 

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

387 """Get the username for the current connection 

388 

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

390 """ 

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

392 

393 

394AsyncACLCommands = ACLCommands 

395 

396 

397class ManagementCommands(CommandsProtocol): 

398 """ 

399 Redis management commands 

400 """ 

401 

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

403 """ 

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

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

406 authenticate for the given user. 

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

408 """ 

409 pieces = [] 

410 if username is not None: 

411 pieces.append(username) 

412 pieces.append(password) 

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

414 

415 def bgrewriteaof(self, **kwargs): 

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

417 

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

419 """ 

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

421 

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

423 """ 

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

425 this method is asynchronous and returns immediately. 

426 

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

428 """ 

429 pieces = [] 

430 if schedule: 

431 pieces.append("SCHEDULE") 

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

433 

434 def role(self) -> ResponseT: 

435 """ 

436 Provide information on the role of a Redis instance in 

437 the context of replication, by returning if the instance 

438 is currently a master, slave, or sentinel. 

439 

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

441 """ 

442 return self.execute_command("ROLE") 

443 

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

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

446 

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

448 """ 

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

450 

451 def client_kill_filter( 

452 self, 

453 _id: Optional[str] = None, 

454 _type: Optional[str] = None, 

455 addr: Optional[str] = None, 

456 skipme: Optional[bool] = None, 

457 laddr: Optional[bool] = None, 

458 user: Optional[str] = None, 

459 maxage: Optional[int] = None, 

460 **kwargs, 

461 ) -> ResponseT: 

462 """ 

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

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

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

466 'master', 'slave' or 'pubsub' 

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

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

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

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

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

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

473 :param maxage: Kills clients that are older than the specified age in seconds 

474 """ 

475 args = [] 

476 if _type is not None: 

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

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

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

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

481 if skipme is not None: 

482 if not isinstance(skipme, bool): 

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

484 if skipme: 

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

486 else: 

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

488 if _id is not None: 

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

490 if addr is not None: 

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

492 if laddr is not None: 

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

494 if user is not None: 

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

496 if maxage is not None: 

497 args.extend((b"MAXAGE", maxage)) 

498 if not args: 

499 raise DataError( 

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

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

502 ) 

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

504 

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

506 """ 

507 Returns information and statistics about the current 

508 client connection. 

509 

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

511 """ 

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

513 

514 def client_list( 

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

516 ) -> ResponseT: 

517 """ 

518 Returns a list of currently connected clients. 

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

520 

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

522 replica, pubsub) 

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

524 

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

526 """ 

527 args = [] 

528 if _type is not None: 

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

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

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

532 args.append(b"TYPE") 

533 args.append(_type) 

534 if not isinstance(client_id, list): 

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

536 if client_id: 

537 args.append(b"ID") 

538 args += client_id 

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

540 

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

542 """ 

543 Returns the current connection name 

544 

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

546 """ 

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

548 

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

550 """ 

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

552 redirecting tracking notifications. 

553 

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

555 """ 

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

557 

558 def client_reply( 

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

560 ) -> ResponseT: 

561 """ 

562 Enable and disable redis server replies. 

563 

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

565 ON - The default most with server replies to commands 

566 OFF - Disable server responses to commands 

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

568 

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

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

571 TimeoutError. 

572 The test_client_reply unit test illustrates this, and 

573 conftest.py has a client with a timeout. 

574 

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

576 """ 

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

578 if reply not in replies: 

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

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

581 

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

583 """ 

584 Returns the current connection id 

585 

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

587 """ 

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

589 

590 def client_tracking_on( 

591 self, 

592 clientid: Optional[int] = None, 

593 prefix: Sequence[KeyT] = [], 

594 bcast: bool = False, 

595 optin: bool = False, 

596 optout: bool = False, 

597 noloop: bool = False, 

598 ) -> ResponseT: 

599 """ 

600 Turn on the tracking mode. 

601 For more information, about the options look at client_tracking func. 

602 

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

604 """ 

605 return self.client_tracking( 

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

607 ) 

608 

609 def client_tracking_off( 

610 self, 

611 clientid: Optional[int] = None, 

612 prefix: Sequence[KeyT] = [], 

613 bcast: bool = False, 

614 optin: bool = False, 

615 optout: bool = False, 

616 noloop: bool = False, 

617 ) -> ResponseT: 

618 """ 

619 Turn off the tracking mode. 

620 For more information, about the options look at client_tracking func. 

621 

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

623 """ 

624 return self.client_tracking( 

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

626 ) 

627 

628 def client_tracking( 

629 self, 

630 on: bool = True, 

631 clientid: Optional[int] = None, 

632 prefix: Sequence[KeyT] = [], 

633 bcast: bool = False, 

634 optin: bool = False, 

635 optout: bool = False, 

636 noloop: bool = False, 

637 **kwargs, 

638 ) -> ResponseT: 

639 """ 

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

641 for server assisted client side caching. 

642 

643 ``on`` indicate for tracking on or tracking off. The default is on. 

644 

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

646 the specified ID. 

647 

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

649 invalidation messages are reported for all the prefixes 

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

651 

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

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

654 after a CLIENT CACHING yes command. 

655 

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

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

658 CLIENT CACHING no command. 

659 

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

661 connection itself. 

662 

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

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

665 

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

667 """ 

668 

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

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

671 

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

673 if clientid is not None: 

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

675 for p in prefix: 

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

677 if bcast: 

678 pieces.append("BCAST") 

679 if optin: 

680 pieces.append("OPTIN") 

681 if optout: 

682 pieces.append("OPTOUT") 

683 if noloop: 

684 pieces.append("NOLOOP") 

685 

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

687 

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

689 """ 

690 Returns the information about the current client connection's 

691 use of the server assisted client side cache. 

692 

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

694 """ 

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

696 

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

698 """ 

699 Sets the current connection name 

700 

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

702 

703 .. note:: 

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

705 

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

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

708 """ 

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

710 

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

712 """ 

713 Sets the current connection library name or version 

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

715 """ 

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

717 

718 def client_unblock( 

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

720 ) -> ResponseT: 

721 """ 

722 Unblocks a connection by its client id. 

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

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

725 regular timeout mechanism. 

726 

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

728 """ 

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

730 if error: 

731 args.append(b"ERROR") 

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

733 

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

735 """ 

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

737 

738 

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

740 

741 Args: 

742 timeout: milliseconds to pause clients 

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

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

745 a write command. 

746 

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

748 

749 * EVAL/EVALSHA: Will block client for all scripts. 

750 * PUBLISH: Will block client. 

751 * PFCOUNT: Will block client. 

752 * WAIT: Acknowledgments will be delayed, so this command will 

753 appear blocked. 

754 """ 

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

756 if not isinstance(timeout, int): 

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

758 if not all: 

759 args.append("WRITE") 

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

761 

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

763 """ 

764 Unpause all redis clients 

765 

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

767 """ 

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

769 

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

771 """ 

772 Sets the client eviction mode for the current connection. 

773 

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

775 """ 

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

777 

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

779 """ 

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

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

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

783 # unless it sends the TOUCH command. 

784 

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

786 """ 

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

788 

789 def command(self, **kwargs): 

790 """ 

791 Returns dict reply of details about all Redis commands. 

792 

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

794 """ 

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

796 

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

798 raise NotImplementedError( 

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

800 ) 

801 

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

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

804 

805 def command_list( 

806 self, 

807 module: Optional[str] = None, 

808 category: Optional[str] = None, 

809 pattern: Optional[str] = None, 

810 ) -> ResponseT: 

811 """ 

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

813 You can use one of the following filters: 

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

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

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

817 

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

819 """ 

820 pieces = [] 

821 if module is not None: 

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

823 if category is not None: 

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

825 if pattern is not None: 

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

827 

828 if pieces: 

829 pieces.insert(0, "FILTERBY") 

830 

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

832 

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

834 """ 

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

836 

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

838 """ 

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

840 

841 def command_docs(self, *args): 

842 """ 

843 This function throws a NotImplementedError since it is intentionally 

844 not supported. 

845 """ 

846 raise NotImplementedError( 

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

848 ) 

849 

850 def config_get( 

851 self, pattern: PatternT = "*", *args: PatternT, **kwargs 

852 ) -> ResponseT: 

853 """ 

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

855 

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

857 """ 

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

859 

860 def config_set( 

861 self, 

862 name: KeyT, 

863 value: EncodableT, 

864 *args: Union[KeyT, EncodableT], 

865 **kwargs, 

866 ) -> ResponseT: 

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

868 

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

870 """ 

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

872 

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

874 """ 

875 Reset runtime statistics 

876 

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

878 """ 

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

880 

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

882 """ 

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

884 

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

886 """ 

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

888 

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

890 """ 

891 Returns the number of keys in the current database 

892 

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

894 """ 

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

896 

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

898 """ 

899 Returns version specific meta information about a given key 

900 

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

902 """ 

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

904 

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

906 raise NotImplementedError( 

907 """ 

908 DEBUG SEGFAULT is intentionally not implemented in the client. 

909 

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

911 """ 

912 ) 

913 

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

915 """ 

916 Echo the string back from the server 

917 

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

919 """ 

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

921 

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

923 """ 

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

925 

926 ``asynchronous`` indicates whether the operation is 

927 executed asynchronously by the server. 

928 

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

930 """ 

931 args = [] 

932 if asynchronous: 

933 args.append(b"ASYNC") 

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

935 

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

937 """ 

938 Delete all keys in the current database. 

939 

940 ``asynchronous`` indicates whether the operation is 

941 executed asynchronously by the server. 

942 

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

944 """ 

945 args = [] 

946 if asynchronous: 

947 args.append(b"ASYNC") 

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

949 

950 def sync(self) -> ResponseT: 

951 """ 

952 Initiates a replication stream from the master. 

953 

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

955 """ 

956 from redis.client import NEVER_DECODE 

957 

958 options = {} 

959 options[NEVER_DECODE] = [] 

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

961 

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

963 """ 

964 Initiates a replication stream from the master. 

965 Newer version for `sync`. 

966 

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

968 """ 

969 from redis.client import NEVER_DECODE 

970 

971 options = {} 

972 options[NEVER_DECODE] = [] 

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

974 

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

976 """ 

977 Swap two databases 

978 

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

980 """ 

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

982 

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

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

985 

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

987 """ 

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

989 

990 def info(self, section: Optional[str] = None, *args: str, **kwargs) -> ResponseT: 

991 """ 

992 Returns a dictionary containing information about the Redis server 

993 

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

995 of information 

996 

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

998 and will generate ResponseError 

999 

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

1001 """ 

1002 if section is None: 

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

1004 else: 

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

1006 

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

1008 """ 

1009 Return a Python datetime object representing the last time the 

1010 Redis database was saved to disk 

1011 

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

1013 """ 

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

1015 

1016 def latency_doctor(self): 

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

1018 This function is best used within the redis-cli. 

1019 

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

1021 """ 

1022 raise NotImplementedError( 

1023 """ 

1024 LATENCY DOCTOR is intentionally not implemented in the client. 

1025 

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

1027 """ 

1028 ) 

1029 

1030 def latency_graph(self): 

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

1032 This function is best used within the redis-cli. 

1033 

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

1035 """ 

1036 raise NotImplementedError( 

1037 """ 

1038 LATENCY GRAPH is intentionally not implemented in the client. 

1039 

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

1041 """ 

1042 ) 

1043 

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

1045 """ 

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

1047 

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

1049 """ 

1050 if version_numbers: 

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

1052 else: 

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

1054 

1055 def reset(self) -> ResponseT: 

1056 """Perform a full reset on the connection's server-side context. 

1057 

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

1059 """ 

1060 return self.execute_command("RESET") 

1061 

1062 def migrate( 

1063 self, 

1064 host: str, 

1065 port: int, 

1066 keys: KeysT, 

1067 destination_db: int, 

1068 timeout: int, 

1069 copy: bool = False, 

1070 replace: bool = False, 

1071 auth: Optional[str] = None, 

1072 **kwargs, 

1073 ) -> ResponseT: 

1074 """ 

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

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

1077 

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

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

1080 command is interrupted. 

1081 

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

1083 the source server. 

1084 

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

1086 on the destination server if they exist. 

1087 

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

1089 the password provided. 

1090 

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

1092 """ 

1093 keys = list_or_args(keys, []) 

1094 if not keys: 

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

1096 pieces = [] 

1097 if copy: 

1098 pieces.append(b"COPY") 

1099 if replace: 

1100 pieces.append(b"REPLACE") 

1101 if auth: 

1102 pieces.append(b"AUTH") 

1103 pieces.append(auth) 

1104 pieces.append(b"KEYS") 

1105 pieces.extend(keys) 

1106 return self.execute_command( 

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

1108 ) 

1109 

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

1111 """ 

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

1113 """ 

1114 return self.execute_command( 

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

1116 ) 

1117 

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

1119 raise NotImplementedError( 

1120 """ 

1121 MEMORY DOCTOR is intentionally not implemented in the client. 

1122 

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

1124 """ 

1125 ) 

1126 

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

1128 raise NotImplementedError( 

1129 """ 

1130 MEMORY HELP is intentionally not implemented in the client. 

1131 

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

1133 """ 

1134 ) 

1135 

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

1137 """ 

1138 Return a dictionary of memory stats 

1139 

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

1141 """ 

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

1143 

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

1145 """ 

1146 Return an internal statistics report from the memory allocator. 

1147 

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

1149 """ 

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

1151 

1152 def memory_usage( 

1153 self, key: KeyT, samples: Optional[int] = None, **kwargs 

1154 ) -> ResponseT: 

1155 """ 

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

1157 administrative overheads. 

1158 

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

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

1161 all elements. 

1162 

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

1164 """ 

1165 args = [] 

1166 if isinstance(samples, int): 

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

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

1169 

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

1171 """ 

1172 Attempts to purge dirty pages for reclamation by allocator 

1173 

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

1175 """ 

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

1177 

1178 def latency_histogram(self, *args): 

1179 """ 

1180 This function throws a NotImplementedError since it is intentionally 

1181 not supported. 

1182 """ 

1183 raise NotImplementedError( 

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

1185 ) 

1186 

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

1188 """ 

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

1190 

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

1192 """ 

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

1194 

1195 def latency_latest(self) -> ResponseT: 

1196 """ 

1197 Reports the latest latency events logged. 

1198 

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

1200 """ 

1201 return self.execute_command("LATENCY LATEST") 

1202 

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

1204 """ 

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

1206 

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

1208 """ 

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

1210 

1211 def ping(self, **kwargs) -> Union[Awaitable[bool], bool]: 

1212 """ 

1213 Ping the Redis server to test connectivity. 

1214 

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

1216 responds with "PONG". 

1217 

1218 This command is useful for: 

1219 - Testing whether a connection is still alive 

1220 - Verifying the server's ability to serve data 

1221 

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

1223 """ 

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

1225 

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

1227 """ 

1228 Ask the server to close the connection. 

1229 

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

1231 """ 

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

1233 

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

1235 """ 

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

1237 

1238 Examples of valid arguments include: 

1239 

1240 NO ONE (set no replication) 

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

1242 

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

1244 """ 

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

1246 

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

1248 """ 

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

1250 blocking until the save is complete 

1251 

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

1253 """ 

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

1255 

1256 def shutdown( 

1257 self, 

1258 save: bool = False, 

1259 nosave: bool = False, 

1260 now: bool = False, 

1261 force: bool = False, 

1262 abort: bool = False, 

1263 **kwargs, 

1264 ) -> None: 

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

1266 data will be flushed before shutdown. 

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

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

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

1270 are configured. 

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

1272 the shutdown sequence. 

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

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

1275 

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

1277 """ 

1278 if save and nosave: 

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

1280 args = ["SHUTDOWN"] 

1281 if save: 

1282 args.append("SAVE") 

1283 if nosave: 

1284 args.append("NOSAVE") 

1285 if now: 

1286 args.append("NOW") 

1287 if force: 

1288 args.append("FORCE") 

1289 if abort: 

1290 args.append("ABORT") 

1291 try: 

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

1293 except ConnectionError: 

1294 # a ConnectionError here is expected 

1295 return 

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

1297 

1298 def slaveof( 

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

1300 ) -> ResponseT: 

1301 """ 

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

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

1304 instance is promoted to a master instead. 

1305 

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

1307 """ 

1308 if host is None and port is None: 

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

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

1311 

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

1313 """ 

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

1315 most recent ``num`` items. 

1316 

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

1318 """ 

1319 from redis.client import NEVER_DECODE 

1320 

1321 args = ["SLOWLOG GET"] 

1322 if num is not None: 

1323 args.append(num) 

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

1325 if decode_responses is True: 

1326 kwargs[NEVER_DECODE] = [] 

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

1328 

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

1330 """ 

1331 Get the number of items in the slowlog 

1332 

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

1334 """ 

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

1336 

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

1338 """ 

1339 Remove all items in the slowlog 

1340 

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

1342 """ 

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

1344 

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

1346 """ 

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

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

1349 

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

1351 """ 

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

1353 

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

1355 """ 

1356 Redis synchronous replication 

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

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

1359 reached. 

1360 

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

1362 """ 

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

1364 

1365 def waitaof( 

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

1367 ) -> ResponseT: 

1368 """ 

1369 This command blocks the current client until all previous write 

1370 commands by that client are acknowledged as having been fsynced 

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

1372 of replicas. 

1373 

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

1375 """ 

1376 return self.execute_command( 

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

1378 ) 

1379 

1380 def hello(self): 

1381 """ 

1382 This function throws a NotImplementedError since it is intentionally 

1383 not supported. 

1384 """ 

1385 raise NotImplementedError( 

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

1387 ) 

1388 

1389 def failover(self): 

1390 """ 

1391 This function throws a NotImplementedError since it is intentionally 

1392 not supported. 

1393 """ 

1394 raise NotImplementedError( 

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

1396 ) 

1397 

1398 

1399class AsyncManagementCommands(ManagementCommands): 

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

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

1402 

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

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

1405 

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

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

1408 

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

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

1411 

1412 async def shutdown( 

1413 self, 

1414 save: bool = False, 

1415 nosave: bool = False, 

1416 now: bool = False, 

1417 force: bool = False, 

1418 abort: bool = False, 

1419 **kwargs, 

1420 ) -> None: 

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

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

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

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

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

1426 

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

1428 """ 

1429 if save and nosave: 

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

1431 args = ["SHUTDOWN"] 

1432 if save: 

1433 args.append("SAVE") 

1434 if nosave: 

1435 args.append("NOSAVE") 

1436 if now: 

1437 args.append("NOW") 

1438 if force: 

1439 args.append("FORCE") 

1440 if abort: 

1441 args.append("ABORT") 

1442 try: 

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

1444 except ConnectionError: 

1445 # a ConnectionError here is expected 

1446 return 

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

1448 

1449 

1450class BitFieldOperation: 

1451 """ 

1452 Command builder for BITFIELD commands. 

1453 """ 

1454 

1455 def __init__( 

1456 self, 

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

1458 key: str, 

1459 default_overflow: Optional[str] = None, 

1460 ): 

1461 self.client = client 

1462 self.key = key 

1463 self._default_overflow = default_overflow 

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

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

1466 self._last_overflow = "WRAP" 

1467 self.reset() 

1468 

1469 def reset(self): 

1470 """ 

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

1472 """ 

1473 self.operations = [] 

1474 self._last_overflow = "WRAP" 

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

1476 

1477 def overflow(self, overflow: str): 

1478 """ 

1479 Update the overflow algorithm of successive INCRBY operations 

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

1481 Redis docs for descriptions of these algorithmsself. 

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

1483 """ 

1484 overflow = overflow.upper() 

1485 if overflow != self._last_overflow: 

1486 self._last_overflow = overflow 

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

1488 return self 

1489 

1490 def incrby( 

1491 self, 

1492 fmt: str, 

1493 offset: BitfieldOffsetT, 

1494 increment: int, 

1495 overflow: Optional[str] = None, 

1496 ): 

1497 """ 

1498 Increment a bitfield by a given amount. 

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

1500 for an unsigned 8-bit integer. 

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

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

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

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

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

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

1507 descriptions of these algorithms. 

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

1509 """ 

1510 if overflow is not None: 

1511 self.overflow(overflow) 

1512 

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

1514 return self 

1515 

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

1517 """ 

1518 Get the value of a given bitfield. 

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

1520 an unsigned 8-bit integer. 

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

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

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

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

1525 """ 

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

1527 return self 

1528 

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

1530 """ 

1531 Set the value of a given bitfield. 

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

1533 an unsigned 8-bit integer. 

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

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

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

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

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

1539 """ 

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

1541 return self 

1542 

1543 @property 

1544 def command(self): 

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

1546 for ops in self.operations: 

1547 cmd.extend(ops) 

1548 return cmd 

1549 

1550 def execute(self) -> ResponseT: 

1551 """ 

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

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

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

1555 will be present within the pipeline's execute. 

1556 """ 

1557 command = self.command 

1558 self.reset() 

1559 return self.client.execute_command(*command) 

1560 

1561 

1562class BasicKeyCommands(CommandsProtocol): 

1563 """ 

1564 Redis basic key-based commands 

1565 """ 

1566 

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

1568 """ 

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

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

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

1572 

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

1574 """ 

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

1576 

1577 def bitcount( 

1578 self, 

1579 key: KeyT, 

1580 start: Optional[int] = None, 

1581 end: Optional[int] = None, 

1582 mode: Optional[str] = None, 

1583 ) -> ResponseT: 

1584 """ 

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

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

1587 

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

1589 """ 

1590 params = [key] 

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

1592 params.append(start) 

1593 params.append(end) 

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

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

1596 if mode is not None: 

1597 params.append(mode) 

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

1599 

1600 def bitfield( 

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

1602 key: KeyT, 

1603 default_overflow: Optional[str] = None, 

1604 ) -> BitFieldOperation: 

1605 """ 

1606 Return a BitFieldOperation instance to conveniently construct one or 

1607 more bitfield operations on ``key``. 

1608 

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

1610 """ 

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

1612 

1613 def bitfield_ro( 

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

1615 key: KeyT, 

1616 encoding: str, 

1617 offset: BitfieldOffsetT, 

1618 items: Optional[list] = None, 

1619 ) -> ResponseT: 

1620 """ 

1621 Return an array of the specified bitfield values 

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

1623 parameters and remaining values are result of corresponding 

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

1625 Read-only variant of the BITFIELD command. 

1626 

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

1628 """ 

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

1630 

1631 items = items or [] 

1632 for encoding, offset in items: 

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

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

1635 

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

1637 """ 

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

1639 store the result in ``dest``. 

1640 

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

1642 """ 

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

1644 

1645 def bitpos( 

1646 self, 

1647 key: KeyT, 

1648 bit: int, 

1649 start: Optional[int] = None, 

1650 end: Optional[int] = None, 

1651 mode: Optional[str] = None, 

1652 ) -> ResponseT: 

1653 """ 

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

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

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

1657 means to look at the first three bytes. 

1658 

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

1660 """ 

1661 if bit not in (0, 1): 

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

1663 params = [key, bit] 

1664 

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

1666 

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

1668 params.append(end) 

1669 elif start is None and end is not None: 

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

1671 

1672 if mode is not None: 

1673 params.append(mode) 

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

1675 

1676 def copy( 

1677 self, 

1678 source: str, 

1679 destination: str, 

1680 destination_db: Optional[str] = None, 

1681 replace: bool = False, 

1682 ) -> ResponseT: 

1683 """ 

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

1685 

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

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

1688 

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

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

1691 the ``destination`` key already exists. 

1692 

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

1694 """ 

1695 params = [source, destination] 

1696 if destination_db is not None: 

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

1698 if replace: 

1699 params.append("REPLACE") 

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

1701 

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

1703 """ 

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

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

1706 

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

1708 """ 

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

1710 

1711 decr = decrby 

1712 

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

1714 """ 

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

1716 """ 

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

1718 

1719 def __delitem__(self, name: KeyT): 

1720 self.delete(name) 

1721 

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

1723 """ 

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

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

1726 

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

1728 """ 

1729 from redis.client import NEVER_DECODE 

1730 

1731 options = {} 

1732 options[NEVER_DECODE] = [] 

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

1734 

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

1736 """ 

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

1738 

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

1740 """ 

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

1742 

1743 __contains__ = exists 

1744 

1745 def expire( 

1746 self, 

1747 name: KeyT, 

1748 time: ExpiryT, 

1749 nx: bool = False, 

1750 xx: bool = False, 

1751 gt: bool = False, 

1752 lt: bool = False, 

1753 ) -> ResponseT: 

1754 """ 

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

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

1757 object. 

1758 

1759 Valid options are: 

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

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

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

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

1764 

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

1766 """ 

1767 if isinstance(time, datetime.timedelta): 

1768 time = int(time.total_seconds()) 

1769 

1770 exp_option = list() 

1771 if nx: 

1772 exp_option.append("NX") 

1773 if xx: 

1774 exp_option.append("XX") 

1775 if gt: 

1776 exp_option.append("GT") 

1777 if lt: 

1778 exp_option.append("LT") 

1779 

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

1781 

1782 def expireat( 

1783 self, 

1784 name: KeyT, 

1785 when: AbsExpiryT, 

1786 nx: bool = False, 

1787 xx: bool = False, 

1788 gt: bool = False, 

1789 lt: bool = False, 

1790 ) -> ResponseT: 

1791 """ 

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

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

1794 datetime object. 

1795 

1796 Valid options are: 

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

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

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

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

1801 

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

1803 """ 

1804 if isinstance(when, datetime.datetime): 

1805 when = int(when.timestamp()) 

1806 

1807 exp_option = list() 

1808 if nx: 

1809 exp_option.append("NX") 

1810 if xx: 

1811 exp_option.append("XX") 

1812 if gt: 

1813 exp_option.append("GT") 

1814 if lt: 

1815 exp_option.append("LT") 

1816 

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

1818 

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

1820 """ 

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

1822 at which the given key will expire. 

1823 

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

1825 """ 

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

1827 

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

1829 """ 

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

1831 

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

1833 """ 

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

1835 

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

1837 """ 

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

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

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

1841 is a string). 

1842 

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

1844 """ 

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

1846 

1847 def getex( 

1848 self, 

1849 name: KeyT, 

1850 ex: Optional[ExpiryT] = None, 

1851 px: Optional[ExpiryT] = None, 

1852 exat: Optional[AbsExpiryT] = None, 

1853 pxat: Optional[AbsExpiryT] = None, 

1854 persist: bool = False, 

1855 ) -> ResponseT: 

1856 """ 

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

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

1859 additional options. All time parameters can be given as 

1860 datetime.timedelta or integers. 

1861 

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

1863 

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

1865 

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

1867 specified in unix time. 

1868 

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

1870 specified in unix time. 

1871 

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

1873 

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

1875 """ 

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

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

1878 raise DataError( 

1879 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

1881 ) 

1882 

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

1884 

1885 if persist: 

1886 exp_options.append("PERSIST") 

1887 

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

1889 

1890 def __getitem__(self, name: KeyT): 

1891 """ 

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

1893 doesn't exist. 

1894 """ 

1895 value = self.get(name) 

1896 if value is not None: 

1897 return value 

1898 raise KeyError(name) 

1899 

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

1901 """ 

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

1903 

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

1905 """ 

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

1907 

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

1909 """ 

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

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

1912 

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

1914 """ 

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

1916 

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

1918 """ 

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

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

1921 

1922 As per Redis 6.2, GETSET is considered deprecated. 

1923 Please use SET with GET parameter in new code. 

1924 

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

1926 """ 

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

1928 

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

1930 """ 

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

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

1933 

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

1935 """ 

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

1937 

1938 incr = incrby 

1939 

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

1941 """ 

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

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

1944 

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

1946 """ 

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

1948 

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

1950 """ 

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

1952 

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

1954 """ 

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

1956 

1957 def lmove( 

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

1959 ) -> ResponseT: 

1960 """ 

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

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

1963 Returns the element being popped and pushed. 

1964 

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

1966 """ 

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

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

1969 

1970 def blmove( 

1971 self, 

1972 first_list: str, 

1973 second_list: str, 

1974 timeout: int, 

1975 src: str = "LEFT", 

1976 dest: str = "RIGHT", 

1977 ) -> ResponseT: 

1978 """ 

1979 Blocking version of lmove. 

1980 

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

1982 """ 

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

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

1985 

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

1987 """ 

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

1989 

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

1991 """ 

1992 from redis.client import EMPTY_RESPONSE 

1993 

1994 args = list_or_args(keys, args) 

1995 options = {} 

1996 if not args: 

1997 options[EMPTY_RESPONSE] = [] 

1998 options["keys"] = args 

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

2000 

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

2002 """ 

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

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

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

2006 

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

2008 """ 

2009 items = [] 

2010 for pair in mapping.items(): 

2011 items.extend(pair) 

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

2013 

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

2015 """ 

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

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

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

2019 Returns a boolean indicating if the operation was successful. 

2020 

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

2022 """ 

2023 items = [] 

2024 for pair in mapping.items(): 

2025 items.extend(pair) 

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

2027 

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

2029 """ 

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

2031 

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

2033 """ 

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

2035 

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

2037 """ 

2038 Removes an expiration on ``name`` 

2039 

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

2041 """ 

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

2043 

2044 def pexpire( 

2045 self, 

2046 name: KeyT, 

2047 time: ExpiryT, 

2048 nx: bool = False, 

2049 xx: bool = False, 

2050 gt: bool = False, 

2051 lt: bool = False, 

2052 ) -> ResponseT: 

2053 """ 

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

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

2056 integer or a Python timedelta object. 

2057 

2058 Valid options are: 

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

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

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

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

2063 

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

2065 """ 

2066 if isinstance(time, datetime.timedelta): 

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

2068 

2069 exp_option = list() 

2070 if nx: 

2071 exp_option.append("NX") 

2072 if xx: 

2073 exp_option.append("XX") 

2074 if gt: 

2075 exp_option.append("GT") 

2076 if lt: 

2077 exp_option.append("LT") 

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

2079 

2080 def pexpireat( 

2081 self, 

2082 name: KeyT, 

2083 when: AbsExpiryT, 

2084 nx: bool = False, 

2085 xx: bool = False, 

2086 gt: bool = False, 

2087 lt: bool = False, 

2088 ) -> ResponseT: 

2089 """ 

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

2091 can be represented as an integer representing unix time in 

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

2093 

2094 Valid options are: 

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

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

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

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

2099 

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

2101 """ 

2102 if isinstance(when, datetime.datetime): 

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

2104 exp_option = list() 

2105 if nx: 

2106 exp_option.append("NX") 

2107 if xx: 

2108 exp_option.append("XX") 

2109 if gt: 

2110 exp_option.append("GT") 

2111 if lt: 

2112 exp_option.append("LT") 

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

2114 

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

2116 """ 

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

2118 at which the given key will expire. 

2119 

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

2121 """ 

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

2123 

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

2125 """ 

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

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

2128 timedelta object 

2129 

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

2131 """ 

2132 if isinstance(time_ms, datetime.timedelta): 

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

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

2135 

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

2137 """ 

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

2139 

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

2141 """ 

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

2143 

2144 def hrandfield( 

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

2146 ) -> ResponseT: 

2147 """ 

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

2149 

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

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

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

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

2154 specified count. 

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

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

2157 

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

2159 """ 

2160 params = [] 

2161 if count is not None: 

2162 params.append(count) 

2163 if withvalues: 

2164 params.append("WITHVALUES") 

2165 

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

2167 

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

2169 """ 

2170 Returns the name of a random key 

2171 

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

2173 """ 

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

2175 

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

2177 """ 

2178 Rename key ``src`` to ``dst`` 

2179 

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

2181 """ 

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

2183 

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

2185 """ 

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

2187 

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

2189 """ 

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

2191 

2192 def restore( 

2193 self, 

2194 name: KeyT, 

2195 ttl: float, 

2196 value: EncodableT, 

2197 replace: bool = False, 

2198 absttl: bool = False, 

2199 idletime: Optional[int] = None, 

2200 frequency: Optional[int] = None, 

2201 ) -> ResponseT: 

2202 """ 

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

2204 using DUMP. 

2205 

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

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

2208 

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

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

2211 greater). 

2212 

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

2214 key must be idle, prior to execution. 

2215 

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

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

2218 

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

2220 """ 

2221 params = [name, ttl, value] 

2222 if replace: 

2223 params.append("REPLACE") 

2224 if absttl: 

2225 params.append("ABSTTL") 

2226 if idletime is not None: 

2227 params.append("IDLETIME") 

2228 try: 

2229 params.append(int(idletime)) 

2230 except ValueError: 

2231 raise DataError("idletimemust be an integer") 

2232 

2233 if frequency is not None: 

2234 params.append("FREQ") 

2235 try: 

2236 params.append(int(frequency)) 

2237 except ValueError: 

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

2239 

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

2241 

2242 def set( 

2243 self, 

2244 name: KeyT, 

2245 value: EncodableT, 

2246 ex: Optional[ExpiryT] = None, 

2247 px: Optional[ExpiryT] = None, 

2248 nx: bool = False, 

2249 xx: bool = False, 

2250 keepttl: bool = False, 

2251 get: bool = False, 

2252 exat: Optional[AbsExpiryT] = None, 

2253 pxat: Optional[AbsExpiryT] = None, 

2254 ) -> ResponseT: 

2255 """ 

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

2257 

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

2259 

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

2261 

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

2263 if it does not exist. 

2264 

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

2266 if it already exists. 

2267 

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

2269 (Available since Redis 6.0) 

2270 

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

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

2273 (Available since Redis 6.2) 

2274 

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

2276 specified in unix time. 

2277 

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

2279 specified in unix time. 

2280 

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

2282 """ 

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

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

2285 raise DataError( 

2286 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

2288 ) 

2289 

2290 if nx and xx: 

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

2292 

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

2294 options = {} 

2295 

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

2297 

2298 if keepttl: 

2299 pieces.append("KEEPTTL") 

2300 

2301 if nx: 

2302 pieces.append("NX") 

2303 if xx: 

2304 pieces.append("XX") 

2305 

2306 if get: 

2307 pieces.append("GET") 

2308 options["get"] = True 

2309 

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

2311 

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

2313 self.set(name, value) 

2314 

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

2316 """ 

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

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

2319 

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

2321 """ 

2322 value = value and 1 or 0 

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

2324 

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

2326 """ 

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

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

2329 timedelta object. 

2330 

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

2332 """ 

2333 if isinstance(time, datetime.timedelta): 

2334 time = int(time.total_seconds()) 

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

2336 

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

2338 """ 

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

2340 

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

2342 """ 

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

2344 

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

2346 """ 

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

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

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

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

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

2352 of what's being injected. 

2353 

2354 Returns the length of the new string. 

2355 

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

2357 """ 

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

2359 

2360 def stralgo( 

2361 self, 

2362 algo: Literal["LCS"], 

2363 value1: KeyT, 

2364 value2: KeyT, 

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

2366 len: bool = False, 

2367 idx: bool = False, 

2368 minmatchlen: Optional[int] = None, 

2369 withmatchlen: bool = False, 

2370 **kwargs, 

2371 ) -> ResponseT: 

2372 """ 

2373 Implements complex algorithms that operate on strings. 

2374 Right now the only algorithm implemented is the LCS algorithm 

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

2376 implemented in the future. 

2377 

2378 ``algo`` Right now must be LCS 

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

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

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

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

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

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

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

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

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

2388 

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

2390 """ 

2391 # check validity 

2392 supported_algo = ["LCS"] 

2393 if algo not in supported_algo: 

2394 supported_algos_str = ", ".join(supported_algo) 

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

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

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

2398 if len and idx: 

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

2400 

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

2402 if len: 

2403 pieces.append(b"LEN") 

2404 if idx: 

2405 pieces.append(b"IDX") 

2406 try: 

2407 int(minmatchlen) 

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

2409 except TypeError: 

2410 pass 

2411 if withmatchlen: 

2412 pieces.append(b"WITHMATCHLEN") 

2413 

2414 return self.execute_command( 

2415 "STRALGO", 

2416 *pieces, 

2417 len=len, 

2418 idx=idx, 

2419 minmatchlen=minmatchlen, 

2420 withmatchlen=withmatchlen, 

2421 **kwargs, 

2422 ) 

2423 

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

2425 """ 

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

2427 

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

2429 """ 

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

2431 

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

2433 """ 

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

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

2436 """ 

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

2438 

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

2440 """ 

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

2442 if it does not exist. 

2443 

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

2445 """ 

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

2447 

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

2449 """ 

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

2451 

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

2453 """ 

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

2455 

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

2457 """ 

2458 Returns the type of key ``name`` 

2459 

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

2461 """ 

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

2463 

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

2465 """ 

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

2467 

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

2469 """ 

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

2471 

2472 def unwatch(self) -> None: 

2473 """ 

2474 Unwatches all previously watched keys for a transaction 

2475 

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

2477 """ 

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

2479 

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

2481 """ 

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

2483 

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

2485 """ 

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

2487 

2488 def lcs( 

2489 self, 

2490 key1: str, 

2491 key2: str, 

2492 len: Optional[bool] = False, 

2493 idx: Optional[bool] = False, 

2494 minmatchlen: Optional[int] = 0, 

2495 withmatchlen: Optional[bool] = False, 

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

2497 """ 

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

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

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

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

2502 the given ``minmatchlen``. 

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

2504 For more information, see https://redis.io/commands/lcs 

2505 """ 

2506 pieces = [key1, key2] 

2507 if len: 

2508 pieces.append("LEN") 

2509 if idx: 

2510 pieces.append("IDX") 

2511 if minmatchlen != 0: 

2512 pieces.extend(["MINMATCHLEN", minmatchlen]) 

2513 if withmatchlen: 

2514 pieces.append("WITHMATCHLEN") 

2515 return self.execute_command("LCS", *pieces, keys=[key1, key2]) 

2516 

2517 

2518class AsyncBasicKeyCommands(BasicKeyCommands): 

2519 def __delitem__(self, name: KeyT): 

2520 raise TypeError("Async Redis client does not support class deletion") 

2521 

2522 def __contains__(self, name: KeyT): 

2523 raise TypeError("Async Redis client does not support class inclusion") 

2524 

2525 def __getitem__(self, name: KeyT): 

2526 raise TypeError("Async Redis client does not support class retrieval") 

2527 

2528 def __setitem__(self, name: KeyT, value: EncodableT): 

2529 raise TypeError("Async Redis client does not support class assignment") 

2530 

2531 async def watch(self, *names: KeyT) -> None: 

2532 return super().watch(*names) 

2533 

2534 async def unwatch(self) -> None: 

2535 return super().unwatch() 

2536 

2537 

2538class ListCommands(CommandsProtocol): 

2539 """ 

2540 Redis commands for List data type. 

2541 see: https://redis.io/topics/data-types#lists 

2542 """ 

2543 

2544 def blpop( 

2545 self, keys: List, timeout: Optional[Number] = 0 

2546 ) -> Union[Awaitable[list], list]: 

2547 """ 

2548 LPOP a value off of the first non-empty list 

2549 named in the ``keys`` list. 

2550 

2551 If none of the lists in ``keys`` has a value to LPOP, then block 

2552 for ``timeout`` seconds, or until a value gets pushed on to one 

2553 of the lists. 

2554 

2555 If timeout is 0, then block indefinitely. 

2556 

2557 For more information, see https://redis.io/commands/blpop 

2558 """ 

2559 if timeout is None: 

2560 timeout = 0 

2561 keys = list_or_args(keys, None) 

2562 keys.append(timeout) 

2563 return self.execute_command("BLPOP", *keys) 

2564 

2565 def brpop( 

2566 self, keys: List, timeout: Optional[Number] = 0 

2567 ) -> Union[Awaitable[list], list]: 

2568 """ 

2569 RPOP a value off of the first non-empty list 

2570 named in the ``keys`` list. 

2571 

2572 If none of the lists in ``keys`` has a value to RPOP, then block 

2573 for ``timeout`` seconds, or until a value gets pushed on to one 

2574 of the lists. 

2575 

2576 If timeout is 0, then block indefinitely. 

2577 

2578 For more information, see https://redis.io/commands/brpop 

2579 """ 

2580 if timeout is None: 

2581 timeout = 0 

2582 keys = list_or_args(keys, None) 

2583 keys.append(timeout) 

2584 return self.execute_command("BRPOP", *keys) 

2585 

2586 def brpoplpush( 

2587 self, src: str, dst: str, timeout: Optional[Number] = 0 

2588 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

2589 """ 

2590 Pop a value off the tail of ``src``, push it on the head of ``dst`` 

2591 and then return it. 

2592 

2593 This command blocks until a value is in ``src`` or until ``timeout`` 

2594 seconds elapse, whichever is first. A ``timeout`` value of 0 blocks 

2595 forever. 

2596 

2597 For more information, see https://redis.io/commands/brpoplpush 

2598 """ 

2599 if timeout is None: 

2600 timeout = 0 

2601 return self.execute_command("BRPOPLPUSH", src, dst, timeout) 

2602 

2603 def blmpop( 

2604 self, 

2605 timeout: float, 

2606 numkeys: int, 

2607 *args: str, 

2608 direction: str, 

2609 count: Optional[int] = 1, 

2610 ) -> Optional[list]: 

2611 """ 

2612 Pop ``count`` values (default 1) from first non-empty in the list 

2613 of provided key names. 

2614 

2615 When all lists are empty this command blocks the connection until another 

2616 client pushes to it or until the timeout, timeout of 0 blocks indefinitely 

2617 

2618 For more information, see https://redis.io/commands/blmpop 

2619 """ 

2620 cmd_args = [timeout, numkeys, *args, direction, "COUNT", count] 

2621 

2622 return self.execute_command("BLMPOP", *cmd_args) 

2623 

2624 def lmpop( 

2625 self, 

2626 num_keys: int, 

2627 *args: str, 

2628 direction: str, 

2629 count: Optional[int] = 1, 

2630 ) -> Union[Awaitable[list], list]: 

2631 """ 

2632 Pop ``count`` values (default 1) first non-empty list key from the list 

2633 of args provided key names. 

2634 

2635 For more information, see https://redis.io/commands/lmpop 

2636 """ 

2637 cmd_args = [num_keys] + list(args) + [direction] 

2638 if count != 1: 

2639 cmd_args.extend(["COUNT", count]) 

2640 

2641 return self.execute_command("LMPOP", *cmd_args) 

2642 

2643 def lindex( 

2644 self, name: str, index: int 

2645 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

2646 """ 

2647 Return the item from list ``name`` at position ``index`` 

2648 

2649 Negative indexes are supported and will return an item at the 

2650 end of the list 

2651 

2652 For more information, see https://redis.io/commands/lindex 

2653 """ 

2654 return self.execute_command("LINDEX", name, index, keys=[name]) 

2655 

2656 def linsert( 

2657 self, name: str, where: str, refvalue: str, value: str 

2658 ) -> Union[Awaitable[int], int]: 

2659 """ 

2660 Insert ``value`` in list ``name`` either immediately before or after 

2661 [``where``] ``refvalue`` 

2662 

2663 Returns the new length of the list on success or -1 if ``refvalue`` 

2664 is not in the list. 

2665 

2666 For more information, see https://redis.io/commands/linsert 

2667 """ 

2668 return self.execute_command("LINSERT", name, where, refvalue, value) 

2669 

2670 def llen(self, name: str) -> Union[Awaitable[int], int]: 

2671 """ 

2672 Return the length of the list ``name`` 

2673 

2674 For more information, see https://redis.io/commands/llen 

2675 """ 

2676 return self.execute_command("LLEN", name, keys=[name]) 

2677 

2678 def lpop( 

2679 self, 

2680 name: str, 

2681 count: Optional[int] = None, 

2682 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]: 

2683 """ 

2684 Removes and returns the first elements of the list ``name``. 

2685 

2686 By default, the command pops a single element from the beginning of 

2687 the list. When provided with the optional ``count`` argument, the reply 

2688 will consist of up to count elements, depending on the list's length. 

2689 

2690 For more information, see https://redis.io/commands/lpop 

2691 """ 

2692 if count is not None: 

2693 return self.execute_command("LPOP", name, count) 

2694 else: 

2695 return self.execute_command("LPOP", name) 

2696 

2697 def lpush(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2698 """ 

2699 Push ``values`` onto the head of the list ``name`` 

2700 

2701 For more information, see https://redis.io/commands/lpush 

2702 """ 

2703 return self.execute_command("LPUSH", name, *values) 

2704 

2705 def lpushx(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2706 """ 

2707 Push ``value`` onto the head of the list ``name`` if ``name`` exists 

2708 

2709 For more information, see https://redis.io/commands/lpushx 

2710 """ 

2711 return self.execute_command("LPUSHX", name, *values) 

2712 

2713 def lrange(self, name: str, start: int, end: int) -> Union[Awaitable[list], list]: 

2714 """ 

2715 Return a slice of the list ``name`` between 

2716 position ``start`` and ``end`` 

2717 

2718 ``start`` and ``end`` can be negative numbers just like 

2719 Python slicing notation 

2720 

2721 For more information, see https://redis.io/commands/lrange 

2722 """ 

2723 return self.execute_command("LRANGE", name, start, end, keys=[name]) 

2724 

2725 def lrem(self, name: str, count: int, value: str) -> Union[Awaitable[int], int]: 

2726 """ 

2727 Remove the first ``count`` occurrences of elements equal to ``value`` 

2728 from the list stored at ``name``. 

2729 

2730 The count argument influences the operation in the following ways: 

2731 count > 0: Remove elements equal to value moving from head to tail. 

2732 count < 0: Remove elements equal to value moving from tail to head. 

2733 count = 0: Remove all elements equal to value. 

2734 

2735 For more information, see https://redis.io/commands/lrem 

2736 """ 

2737 return self.execute_command("LREM", name, count, value) 

2738 

2739 def lset(self, name: str, index: int, value: str) -> Union[Awaitable[str], str]: 

2740 """ 

2741 Set element at ``index`` of list ``name`` to ``value`` 

2742 

2743 For more information, see https://redis.io/commands/lset 

2744 """ 

2745 return self.execute_command("LSET", name, index, value) 

2746 

2747 def ltrim(self, name: str, start: int, end: int) -> Union[Awaitable[str], str]: 

2748 """ 

2749 Trim the list ``name``, removing all values not within the slice 

2750 between ``start`` and ``end`` 

2751 

2752 ``start`` and ``end`` can be negative numbers just like 

2753 Python slicing notation 

2754 

2755 For more information, see https://redis.io/commands/ltrim 

2756 """ 

2757 return self.execute_command("LTRIM", name, start, end) 

2758 

2759 def rpop( 

2760 self, 

2761 name: str, 

2762 count: Optional[int] = None, 

2763 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]: 

2764 """ 

2765 Removes and returns the last elements of the list ``name``. 

2766 

2767 By default, the command pops a single element from the end of the list. 

2768 When provided with the optional ``count`` argument, the reply will 

2769 consist of up to count elements, depending on the list's length. 

2770 

2771 For more information, see https://redis.io/commands/rpop 

2772 """ 

2773 if count is not None: 

2774 return self.execute_command("RPOP", name, count) 

2775 else: 

2776 return self.execute_command("RPOP", name) 

2777 

2778 def rpoplpush(self, src: str, dst: str) -> Union[Awaitable[str], str]: 

2779 """ 

2780 RPOP a value off of the ``src`` list and atomically LPUSH it 

2781 on to the ``dst`` list. Returns the value. 

2782 

2783 For more information, see https://redis.io/commands/rpoplpush 

2784 """ 

2785 return self.execute_command("RPOPLPUSH", src, dst) 

2786 

2787 def rpush(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]: 

2788 """ 

2789 Push ``values`` onto the tail of the list ``name`` 

2790 

2791 For more information, see https://redis.io/commands/rpush 

2792 """ 

2793 return self.execute_command("RPUSH", name, *values) 

2794 

2795 def rpushx(self, name: str, *values: str) -> Union[Awaitable[int], int]: 

2796 """ 

2797 Push ``value`` onto the tail of the list ``name`` if ``name`` exists 

2798 

2799 For more information, see https://redis.io/commands/rpushx 

2800 """ 

2801 return self.execute_command("RPUSHX", name, *values) 

2802 

2803 def lpos( 

2804 self, 

2805 name: str, 

2806 value: str, 

2807 rank: Optional[int] = None, 

2808 count: Optional[int] = None, 

2809 maxlen: Optional[int] = None, 

2810 ) -> Union[str, List, None]: 

2811 """ 

2812 Get position of ``value`` within the list ``name`` 

2813 

2814 If specified, ``rank`` indicates the "rank" of the first element to 

2815 return in case there are multiple copies of ``value`` in the list. 

2816 By default, LPOS returns the position of the first occurrence of 

2817 ``value`` in the list. When ``rank`` 2, LPOS returns the position of 

2818 the second ``value`` in the list. If ``rank`` is negative, LPOS 

2819 searches the list in reverse. For example, -1 would return the 

2820 position of the last occurrence of ``value`` and -2 would return the 

2821 position of the next to last occurrence of ``value``. 

2822 

2823 If specified, ``count`` indicates that LPOS should return a list of 

2824 up to ``count`` positions. A ``count`` of 2 would return a list of 

2825 up to 2 positions. A ``count`` of 0 returns a list of all positions 

2826 matching ``value``. When ``count`` is specified and but ``value`` 

2827 does not exist in the list, an empty list is returned. 

2828 

2829 If specified, ``maxlen`` indicates the maximum number of list 

2830 elements to scan. A ``maxlen`` of 1000 will only return the 

2831 position(s) of items within the first 1000 entries in the list. 

2832 A ``maxlen`` of 0 (the default) will scan the entire list. 

2833 

2834 For more information, see https://redis.io/commands/lpos 

2835 """ 

2836 pieces: list[EncodableT] = [name, value] 

2837 if rank is not None: 

2838 pieces.extend(["RANK", rank]) 

2839 

2840 if count is not None: 

2841 pieces.extend(["COUNT", count]) 

2842 

2843 if maxlen is not None: 

2844 pieces.extend(["MAXLEN", maxlen]) 

2845 

2846 return self.execute_command("LPOS", *pieces, keys=[name]) 

2847 

2848 def sort( 

2849 self, 

2850 name: str, 

2851 start: Optional[int] = None, 

2852 num: Optional[int] = None, 

2853 by: Optional[str] = None, 

2854 get: Optional[List[str]] = None, 

2855 desc: bool = False, 

2856 alpha: bool = False, 

2857 store: Optional[str] = None, 

2858 groups: Optional[bool] = False, 

2859 ) -> Union[List, int]: 

2860 """ 

2861 Sort and return the list, set or sorted set at ``name``. 

2862 

2863 ``start`` and ``num`` allow for paging through the sorted data 

2864 

2865 ``by`` allows using an external key to weight and sort the items. 

2866 Use an "*" to indicate where in the key the item value is located 

2867 

2868 ``get`` allows for returning items from external keys rather than the 

2869 sorted data itself. Use an "*" to indicate where in the key 

2870 the item value is located 

2871 

2872 ``desc`` allows for reversing the sort 

2873 

2874 ``alpha`` allows for sorting lexicographically rather than numerically 

2875 

2876 ``store`` allows for storing the result of the sort into 

2877 the key ``store`` 

2878 

2879 ``groups`` if set to True and if ``get`` contains at least two 

2880 elements, sort will return a list of tuples, each containing the 

2881 values fetched from the arguments to ``get``. 

2882 

2883 For more information, see https://redis.io/commands/sort 

2884 """ 

2885 if (start is not None and num is None) or (num is not None and start is None): 

2886 raise DataError("``start`` and ``num`` must both be specified") 

2887 

2888 pieces: list[EncodableT] = [name] 

2889 if by is not None: 

2890 pieces.extend([b"BY", by]) 

2891 if start is not None and num is not None: 

2892 pieces.extend([b"LIMIT", start, num]) 

2893 if get is not None: 

2894 # If get is a string assume we want to get a single value. 

2895 # Otherwise assume it's an interable and we want to get multiple 

2896 # values. We can't just iterate blindly because strings are 

2897 # iterable. 

2898 if isinstance(get, (bytes, str)): 

2899 pieces.extend([b"GET", get]) 

2900 else: 

2901 for g in get: 

2902 pieces.extend([b"GET", g]) 

2903 if desc: 

2904 pieces.append(b"DESC") 

2905 if alpha: 

2906 pieces.append(b"ALPHA") 

2907 if store is not None: 

2908 pieces.extend([b"STORE", store]) 

2909 if groups: 

2910 if not get or isinstance(get, (bytes, str)) or len(get) < 2: 

2911 raise DataError( 

2912 'when using "groups" the "get" argument ' 

2913 "must be specified and contain at least " 

2914 "two keys" 

2915 ) 

2916 

2917 options = {"groups": len(get) if groups else None} 

2918 options["keys"] = [name] 

2919 return self.execute_command("SORT", *pieces, **options) 

2920 

2921 def sort_ro( 

2922 self, 

2923 key: str, 

2924 start: Optional[int] = None, 

2925 num: Optional[int] = None, 

2926 by: Optional[str] = None, 

2927 get: Optional[List[str]] = None, 

2928 desc: bool = False, 

2929 alpha: bool = False, 

2930 ) -> list: 

2931 """ 

2932 Returns the elements contained in the list, set or sorted set at key. 

2933 (read-only variant of the SORT command) 

2934 

2935 ``start`` and ``num`` allow for paging through the sorted data 

2936 

2937 ``by`` allows using an external key to weight and sort the items. 

2938 Use an "*" to indicate where in the key the item value is located 

2939 

2940 ``get`` allows for returning items from external keys rather than the 

2941 sorted data itself. Use an "*" to indicate where in the key 

2942 the item value is located 

2943 

2944 ``desc`` allows for reversing the sort 

2945 

2946 ``alpha`` allows for sorting lexicographically rather than numerically 

2947 

2948 For more information, see https://redis.io/commands/sort_ro 

2949 """ 

2950 return self.sort( 

2951 key, start=start, num=num, by=by, get=get, desc=desc, alpha=alpha 

2952 ) 

2953 

2954 

2955AsyncListCommands = ListCommands 

2956 

2957 

2958class ScanCommands(CommandsProtocol): 

2959 """ 

2960 Redis SCAN commands. 

2961 see: https://redis.io/commands/scan 

2962 """ 

2963 

2964 def scan( 

2965 self, 

2966 cursor: int = 0, 

2967 match: Union[PatternT, None] = None, 

2968 count: Optional[int] = None, 

2969 _type: Optional[str] = None, 

2970 **kwargs, 

2971 ) -> ResponseT: 

2972 """ 

2973 Incrementally return lists of key names. Also return a cursor 

2974 indicating the scan position. 

2975 

2976 ``match`` allows for filtering the keys by pattern 

2977 

2978 ``count`` provides a hint to Redis about the number of keys to 

2979 return per batch. 

2980 

2981 ``_type`` filters the returned values by a particular Redis type. 

2982 Stock Redis instances allow for the following types: 

2983 HASH, LIST, SET, STREAM, STRING, ZSET 

2984 Additionally, Redis modules can expose other types as well. 

2985 

2986 For more information, see https://redis.io/commands/scan 

2987 """ 

2988 pieces: list[EncodableT] = [cursor] 

2989 if match is not None: 

2990 pieces.extend([b"MATCH", match]) 

2991 if count is not None: 

2992 pieces.extend([b"COUNT", count]) 

2993 if _type is not None: 

2994 pieces.extend([b"TYPE", _type]) 

2995 return self.execute_command("SCAN", *pieces, **kwargs) 

2996 

2997 def scan_iter( 

2998 self, 

2999 match: Union[PatternT, None] = None, 

3000 count: Optional[int] = None, 

3001 _type: Optional[str] = None, 

3002 **kwargs, 

3003 ) -> Iterator: 

3004 """ 

3005 Make an iterator using the SCAN command so that the client doesn't 

3006 need to remember the cursor position. 

3007 

3008 ``match`` allows for filtering the keys by pattern 

3009 

3010 ``count`` provides a hint to Redis about the number of keys to 

3011 return per batch. 

3012 

3013 ``_type`` filters the returned values by a particular Redis type. 

3014 Stock Redis instances allow for the following types: 

3015 HASH, LIST, SET, STREAM, STRING, ZSET 

3016 Additionally, Redis modules can expose other types as well. 

3017 """ 

3018 cursor = "0" 

3019 while cursor != 0: 

3020 cursor, data = self.scan( 

3021 cursor=cursor, match=match, count=count, _type=_type, **kwargs 

3022 ) 

3023 yield from data 

3024 

3025 def sscan( 

3026 self, 

3027 name: KeyT, 

3028 cursor: int = 0, 

3029 match: Union[PatternT, None] = None, 

3030 count: Optional[int] = None, 

3031 ) -> ResponseT: 

3032 """ 

3033 Incrementally return lists of elements in a set. Also return a cursor 

3034 indicating the scan position. 

3035 

3036 ``match`` allows for filtering the keys by pattern 

3037 

3038 ``count`` allows for hint the minimum number of returns 

3039 

3040 For more information, see https://redis.io/commands/sscan 

3041 """ 

3042 pieces: list[EncodableT] = [name, cursor] 

3043 if match is not None: 

3044 pieces.extend([b"MATCH", match]) 

3045 if count is not None: 

3046 pieces.extend([b"COUNT", count]) 

3047 return self.execute_command("SSCAN", *pieces) 

3048 

3049 def sscan_iter( 

3050 self, 

3051 name: KeyT, 

3052 match: Union[PatternT, None] = None, 

3053 count: Optional[int] = None, 

3054 ) -> Iterator: 

3055 """ 

3056 Make an iterator using the SSCAN command so that the client doesn't 

3057 need to remember the cursor position. 

3058 

3059 ``match`` allows for filtering the keys by pattern 

3060 

3061 ``count`` allows for hint the minimum number of returns 

3062 """ 

3063 cursor = "0" 

3064 while cursor != 0: 

3065 cursor, data = self.sscan(name, cursor=cursor, match=match, count=count) 

3066 yield from data 

3067 

3068 def hscan( 

3069 self, 

3070 name: KeyT, 

3071 cursor: int = 0, 

3072 match: Union[PatternT, None] = None, 

3073 count: Optional[int] = None, 

3074 no_values: Union[bool, None] = None, 

3075 ) -> ResponseT: 

3076 """ 

3077 Incrementally return key/value slices in a hash. Also return a cursor 

3078 indicating the scan position. 

3079 

3080 ``match`` allows for filtering the keys by pattern 

3081 

3082 ``count`` allows for hint the minimum number of returns 

3083 

3084 ``no_values`` indicates to return only the keys, without values. 

3085 

3086 For more information, see https://redis.io/commands/hscan 

3087 """ 

3088 pieces: list[EncodableT] = [name, cursor] 

3089 if match is not None: 

3090 pieces.extend([b"MATCH", match]) 

3091 if count is not None: 

3092 pieces.extend([b"COUNT", count]) 

3093 if no_values is not None: 

3094 pieces.extend([b"NOVALUES"]) 

3095 return self.execute_command("HSCAN", *pieces, no_values=no_values) 

3096 

3097 def hscan_iter( 

3098 self, 

3099 name: str, 

3100 match: Union[PatternT, None] = None, 

3101 count: Optional[int] = None, 

3102 no_values: Union[bool, None] = None, 

3103 ) -> Iterator: 

3104 """ 

3105 Make an iterator using the HSCAN command so that the client doesn't 

3106 need to remember the cursor position. 

3107 

3108 ``match`` allows for filtering the keys by pattern 

3109 

3110 ``count`` allows for hint the minimum number of returns 

3111 

3112 ``no_values`` indicates to return only the keys, without values 

3113 """ 

3114 cursor = "0" 

3115 while cursor != 0: 

3116 cursor, data = self.hscan( 

3117 name, cursor=cursor, match=match, count=count, no_values=no_values 

3118 ) 

3119 if no_values: 

3120 yield from data 

3121 else: 

3122 yield from data.items() 

3123 

3124 def zscan( 

3125 self, 

3126 name: KeyT, 

3127 cursor: int = 0, 

3128 match: Union[PatternT, None] = None, 

3129 count: Optional[int] = None, 

3130 score_cast_func: Union[type, Callable] = float, 

3131 ) -> ResponseT: 

3132 """ 

3133 Incrementally return lists of elements in a sorted set. Also return a 

3134 cursor indicating the scan position. 

3135 

3136 ``match`` allows for filtering the keys by pattern 

3137 

3138 ``count`` allows for hint the minimum number of returns 

3139 

3140 ``score_cast_func`` a callable used to cast the score return value 

3141 

3142 For more information, see https://redis.io/commands/zscan 

3143 """ 

3144 pieces = [name, cursor] 

3145 if match is not None: 

3146 pieces.extend([b"MATCH", match]) 

3147 if count is not None: 

3148 pieces.extend([b"COUNT", count]) 

3149 options = {"score_cast_func": score_cast_func} 

3150 return self.execute_command("ZSCAN", *pieces, **options) 

3151 

3152 def zscan_iter( 

3153 self, 

3154 name: KeyT, 

3155 match: Union[PatternT, None] = None, 

3156 count: Optional[int] = None, 

3157 score_cast_func: Union[type, Callable] = float, 

3158 ) -> Iterator: 

3159 """ 

3160 Make an iterator using the ZSCAN command so that the client doesn't 

3161 need to remember the cursor position. 

3162 

3163 ``match`` allows for filtering the keys by pattern 

3164 

3165 ``count`` allows for hint the minimum number of returns 

3166 

3167 ``score_cast_func`` a callable used to cast the score return value 

3168 """ 

3169 cursor = "0" 

3170 while cursor != 0: 

3171 cursor, data = self.zscan( 

3172 name, 

3173 cursor=cursor, 

3174 match=match, 

3175 count=count, 

3176 score_cast_func=score_cast_func, 

3177 ) 

3178 yield from data 

3179 

3180 

3181class AsyncScanCommands(ScanCommands): 

3182 async def scan_iter( 

3183 self, 

3184 match: Union[PatternT, None] = None, 

3185 count: Optional[int] = None, 

3186 _type: Optional[str] = None, 

3187 **kwargs, 

3188 ) -> AsyncIterator: 

3189 """ 

3190 Make an iterator using the SCAN command so that the client doesn't 

3191 need to remember the cursor position. 

3192 

3193 ``match`` allows for filtering the keys by pattern 

3194 

3195 ``count`` provides a hint to Redis about the number of keys to 

3196 return per batch. 

3197 

3198 ``_type`` filters the returned values by a particular Redis type. 

3199 Stock Redis instances allow for the following types: 

3200 HASH, LIST, SET, STREAM, STRING, ZSET 

3201 Additionally, Redis modules can expose other types as well. 

3202 """ 

3203 cursor = "0" 

3204 while cursor != 0: 

3205 cursor, data = await self.scan( 

3206 cursor=cursor, match=match, count=count, _type=_type, **kwargs 

3207 ) 

3208 for d in data: 

3209 yield d 

3210 

3211 async def sscan_iter( 

3212 self, 

3213 name: KeyT, 

3214 match: Union[PatternT, None] = None, 

3215 count: Optional[int] = None, 

3216 ) -> AsyncIterator: 

3217 """ 

3218 Make an iterator using the SSCAN 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.sscan( 

3228 name, cursor=cursor, match=match, count=count 

3229 ) 

3230 for d in data: 

3231 yield d 

3232 

3233 async def hscan_iter( 

3234 self, 

3235 name: str, 

3236 match: Union[PatternT, None] = None, 

3237 count: Optional[int] = None, 

3238 no_values: Union[bool, None] = None, 

3239 ) -> AsyncIterator: 

3240 """ 

3241 Make an iterator using the HSCAN 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 ``no_values`` indicates to return only the keys, without values 

3249 """ 

3250 cursor = "0" 

3251 while cursor != 0: 

3252 cursor, data = await self.hscan( 

3253 name, cursor=cursor, match=match, count=count, no_values=no_values 

3254 ) 

3255 if no_values: 

3256 for it in data: 

3257 yield it 

3258 else: 

3259 for it in data.items(): 

3260 yield it 

3261 

3262 async def zscan_iter( 

3263 self, 

3264 name: KeyT, 

3265 match: Union[PatternT, None] = None, 

3266 count: Optional[int] = None, 

3267 score_cast_func: Union[type, Callable] = float, 

3268 ) -> AsyncIterator: 

3269 """ 

3270 Make an iterator using the ZSCAN command so that the client doesn't 

3271 need to remember the cursor position. 

3272 

3273 ``match`` allows for filtering the keys by pattern 

3274 

3275 ``count`` allows for hint the minimum number of returns 

3276 

3277 ``score_cast_func`` a callable used to cast the score return value 

3278 """ 

3279 cursor = "0" 

3280 while cursor != 0: 

3281 cursor, data = await self.zscan( 

3282 name, 

3283 cursor=cursor, 

3284 match=match, 

3285 count=count, 

3286 score_cast_func=score_cast_func, 

3287 ) 

3288 for d in data: 

3289 yield d 

3290 

3291 

3292class SetCommands(CommandsProtocol): 

3293 """ 

3294 Redis commands for Set data type. 

3295 see: https://redis.io/topics/data-types#sets 

3296 """ 

3297 

3298 def sadd(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]: 

3299 """ 

3300 Add ``value(s)`` to set ``name`` 

3301 

3302 For more information, see https://redis.io/commands/sadd 

3303 """ 

3304 return self.execute_command("SADD", name, *values) 

3305 

3306 def scard(self, name: KeyT) -> Union[Awaitable[int], int]: 

3307 """ 

3308 Return the number of elements in set ``name`` 

3309 

3310 For more information, see https://redis.io/commands/scard 

3311 """ 

3312 return self.execute_command("SCARD", name, keys=[name]) 

3313 

3314 def sdiff(self, keys: List, *args: List) -> Union[Awaitable[list], list]: 

3315 """ 

3316 Return the difference of sets specified by ``keys`` 

3317 

3318 For more information, see https://redis.io/commands/sdiff 

3319 """ 

3320 args = list_or_args(keys, args) 

3321 return self.execute_command("SDIFF", *args, keys=args) 

3322 

3323 def sdiffstore( 

3324 self, dest: str, keys: List, *args: List 

3325 ) -> Union[Awaitable[int], int]: 

3326 """ 

3327 Store the difference of sets specified by ``keys`` into a new 

3328 set named ``dest``. Returns the number of keys in the new set. 

3329 

3330 For more information, see https://redis.io/commands/sdiffstore 

3331 """ 

3332 args = list_or_args(keys, args) 

3333 return self.execute_command("SDIFFSTORE", dest, *args) 

3334 

3335 def sinter(self, keys: List, *args: List) -> Union[Awaitable[list], list]: 

3336 """ 

3337 Return the intersection of sets specified by ``keys`` 

3338 

3339 For more information, see https://redis.io/commands/sinter 

3340 """ 

3341 args = list_or_args(keys, args) 

3342 return self.execute_command("SINTER", *args, keys=args) 

3343 

3344 def sintercard( 

3345 self, numkeys: int, keys: List[KeyT], limit: int = 0 

3346 ) -> Union[Awaitable[int], int]: 

3347 """ 

3348 Return the cardinality of the intersect of multiple sets specified by ``keys``. 

3349 

3350 When LIMIT provided (defaults to 0 and means unlimited), if the intersection 

3351 cardinality reaches limit partway through the computation, the algorithm will 

3352 exit and yield limit as the cardinality 

3353 

3354 For more information, see https://redis.io/commands/sintercard 

3355 """ 

3356 args = [numkeys, *keys, "LIMIT", limit] 

3357 return self.execute_command("SINTERCARD", *args, keys=keys) 

3358 

3359 def sinterstore( 

3360 self, dest: KeyT, keys: List, *args: List 

3361 ) -> Union[Awaitable[int], int]: 

3362 """ 

3363 Store the intersection of sets specified by ``keys`` into a new 

3364 set named ``dest``. Returns the number of keys in the new set. 

3365 

3366 For more information, see https://redis.io/commands/sinterstore 

3367 """ 

3368 args = list_or_args(keys, args) 

3369 return self.execute_command("SINTERSTORE", dest, *args) 

3370 

3371 def sismember( 

3372 self, name: KeyT, value: str 

3373 ) -> Union[Awaitable[Union[Literal[0], Literal[1]]], Union[Literal[0], Literal[1]]]: 

3374 """ 

3375 Return whether ``value`` is a member of set ``name``: 

3376 - 1 if the value is a member of the set. 

3377 - 0 if the value is not a member of the set or if key does not exist. 

3378 

3379 For more information, see https://redis.io/commands/sismember 

3380 """ 

3381 return self.execute_command("SISMEMBER", name, value, keys=[name]) 

3382 

3383 def smembers(self, name: KeyT) -> Union[Awaitable[Set], Set]: 

3384 """ 

3385 Return all members of the set ``name`` 

3386 

3387 For more information, see https://redis.io/commands/smembers 

3388 """ 

3389 return self.execute_command("SMEMBERS", name, keys=[name]) 

3390 

3391 def smismember( 

3392 self, name: KeyT, values: List, *args: List 

3393 ) -> Union[ 

3394 Awaitable[List[Union[Literal[0], Literal[1]]]], 

3395 List[Union[Literal[0], Literal[1]]], 

3396 ]: 

3397 """ 

3398 Return whether each value in ``values`` is a member of the set ``name`` 

3399 as a list of ``int`` in the order of ``values``: 

3400 - 1 if the value is a member of the set. 

3401 - 0 if the value is not a member of the set or if key does not exist. 

3402 

3403 For more information, see https://redis.io/commands/smismember 

3404 """ 

3405 args = list_or_args(values, args) 

3406 return self.execute_command("SMISMEMBER", name, *args, keys=[name]) 

3407 

3408 def smove(self, src: KeyT, dst: KeyT, value: str) -> Union[Awaitable[bool], bool]: 

3409 """ 

3410 Move ``value`` from set ``src`` to set ``dst`` atomically 

3411 

3412 For more information, see https://redis.io/commands/smove 

3413 """ 

3414 return self.execute_command("SMOVE", src, dst, value) 

3415 

3416 def spop(self, name: KeyT, count: Optional[int] = None) -> Union[str, List, None]: 

3417 """ 

3418 Remove and return a random member of set ``name`` 

3419 

3420 For more information, see https://redis.io/commands/spop 

3421 """ 

3422 args = (count is not None) and [count] or [] 

3423 return self.execute_command("SPOP", name, *args) 

3424 

3425 def srandmember( 

3426 self, name: KeyT, number: Optional[int] = None 

3427 ) -> Union[str, List, None]: 

3428 """ 

3429 If ``number`` is None, returns a random member of set ``name``. 

3430 

3431 If ``number`` is supplied, returns a list of ``number`` random 

3432 members of set ``name``. Note this is only available when running 

3433 Redis 2.6+. 

3434 

3435 For more information, see https://redis.io/commands/srandmember 

3436 """ 

3437 args = (number is not None) and [number] or [] 

3438 return self.execute_command("SRANDMEMBER", name, *args) 

3439 

3440 def srem(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]: 

3441 """ 

3442 Remove ``values`` from set ``name`` 

3443 

3444 For more information, see https://redis.io/commands/srem 

3445 """ 

3446 return self.execute_command("SREM", name, *values) 

3447 

3448 def sunion(self, keys: List, *args: List) -> Union[Awaitable[List], List]: 

3449 """ 

3450 Return the union of sets specified by ``keys`` 

3451 

3452 For more information, see https://redis.io/commands/sunion 

3453 """ 

3454 args = list_or_args(keys, args) 

3455 return self.execute_command("SUNION", *args, keys=args) 

3456 

3457 def sunionstore( 

3458 self, dest: KeyT, keys: List, *args: List 

3459 ) -> Union[Awaitable[int], int]: 

3460 """ 

3461 Store the union of sets specified by ``keys`` into a new 

3462 set named ``dest``. Returns the number of keys in the new set. 

3463 

3464 For more information, see https://redis.io/commands/sunionstore 

3465 """ 

3466 args = list_or_args(keys, args) 

3467 return self.execute_command("SUNIONSTORE", dest, *args) 

3468 

3469 

3470AsyncSetCommands = SetCommands 

3471 

3472 

3473class StreamCommands(CommandsProtocol): 

3474 """ 

3475 Redis commands for Stream data type. 

3476 see: https://redis.io/topics/streams-intro 

3477 """ 

3478 

3479 def xack(self, name: KeyT, groupname: GroupT, *ids: StreamIdT) -> ResponseT: 

3480 """ 

3481 Acknowledges the successful processing of one or more messages. 

3482 

3483 Args: 

3484 name: name of the stream. 

3485 groupname: name of the consumer group. 

3486 *ids: message ids to acknowledge. 

3487 

3488 For more information, see https://redis.io/commands/xack 

3489 """ 

3490 return self.execute_command("XACK", name, groupname, *ids) 

3491 

3492 def xackdel( 

3493 self, 

3494 name: KeyT, 

3495 groupname: GroupT, 

3496 *ids: StreamIdT, 

3497 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF", 

3498 ) -> ResponseT: 

3499 """ 

3500 Combines the functionality of XACK and XDEL. Acknowledges the specified 

3501 message IDs in the given consumer group and simultaneously attempts to 

3502 delete the corresponding entries from the stream. 

3503 """ 

3504 if not ids: 

3505 raise DataError("XACKDEL requires at least one message ID") 

3506 

3507 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

3508 raise DataError("XACKDEL ref_policy must be one of: KEEPREF, DELREF, ACKED") 

3509 

3510 pieces = [name, groupname, ref_policy, "IDS", len(ids)] 

3511 pieces.extend(ids) 

3512 return self.execute_command("XACKDEL", *pieces) 

3513 

3514 def xadd( 

3515 self, 

3516 name: KeyT, 

3517 fields: Dict[FieldT, EncodableT], 

3518 id: StreamIdT = "*", 

3519 maxlen: Optional[int] = None, 

3520 approximate: bool = True, 

3521 nomkstream: bool = False, 

3522 minid: Union[StreamIdT, None] = None, 

3523 limit: Optional[int] = None, 

3524 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None, 

3525 ) -> ResponseT: 

3526 """ 

3527 Add to a stream. 

3528 name: name of the stream 

3529 fields: dict of field/value pairs to insert into the stream 

3530 id: Location to insert this record. By default it is appended. 

3531 maxlen: truncate old stream members beyond this size. 

3532 Can't be specified with minid. 

3533 approximate: actual stream length may be slightly more than maxlen 

3534 nomkstream: When set to true, do not make a stream 

3535 minid: the minimum id in the stream to query. 

3536 Can't be specified with maxlen. 

3537 limit: specifies the maximum number of entries to retrieve 

3538 ref_policy: optional reference policy for consumer groups when trimming: 

3539 - KEEPREF (default): When trimming, preserves references in consumer groups' PEL 

3540 - DELREF: When trimming, removes all references from consumer groups' PEL 

3541 - ACKED: When trimming, only removes entries acknowledged by all consumer groups 

3542 

3543 For more information, see https://redis.io/commands/xadd 

3544 """ 

3545 pieces: list[EncodableT] = [] 

3546 if maxlen is not None and minid is not None: 

3547 raise DataError("Only one of ```maxlen``` or ```minid``` may be specified") 

3548 

3549 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

3550 raise DataError("XADD ref_policy must be one of: KEEPREF, DELREF, ACKED") 

3551 

3552 if maxlen is not None: 

3553 if not isinstance(maxlen, int) or maxlen < 0: 

3554 raise DataError("XADD maxlen must be non-negative integer") 

3555 pieces.append(b"MAXLEN") 

3556 if approximate: 

3557 pieces.append(b"~") 

3558 pieces.append(str(maxlen)) 

3559 if minid is not None: 

3560 pieces.append(b"MINID") 

3561 if approximate: 

3562 pieces.append(b"~") 

3563 pieces.append(minid) 

3564 if limit is not None: 

3565 pieces.extend([b"LIMIT", limit]) 

3566 if nomkstream: 

3567 pieces.append(b"NOMKSTREAM") 

3568 if ref_policy is not None: 

3569 pieces.append(ref_policy) 

3570 pieces.append(id) 

3571 if not isinstance(fields, dict) or len(fields) == 0: 

3572 raise DataError("XADD fields must be a non-empty dict") 

3573 for pair in fields.items(): 

3574 pieces.extend(pair) 

3575 return self.execute_command("XADD", name, *pieces) 

3576 

3577 def xautoclaim( 

3578 self, 

3579 name: KeyT, 

3580 groupname: GroupT, 

3581 consumername: ConsumerT, 

3582 min_idle_time: int, 

3583 start_id: StreamIdT = "0-0", 

3584 count: Optional[int] = None, 

3585 justid: bool = False, 

3586 ) -> ResponseT: 

3587 """ 

3588 Transfers ownership of pending stream entries that match the specified 

3589 criteria. Conceptually, equivalent to calling XPENDING and then XCLAIM, 

3590 but provides a more straightforward way to deal with message delivery 

3591 failures via SCAN-like semantics. 

3592 name: name of the stream. 

3593 groupname: name of the consumer group. 

3594 consumername: name of a consumer that claims the message. 

3595 min_idle_time: filter messages that were idle less than this amount of 

3596 milliseconds. 

3597 start_id: filter messages with equal or greater ID. 

3598 count: optional integer, upper limit of the number of entries that the 

3599 command attempts to claim. Set to 100 by default. 

3600 justid: optional boolean, false by default. Return just an array of IDs 

3601 of messages successfully claimed, without returning the actual message 

3602 

3603 For more information, see https://redis.io/commands/xautoclaim 

3604 """ 

3605 try: 

3606 if int(min_idle_time) < 0: 

3607 raise DataError( 

3608 "XAUTOCLAIM min_idle_time must be a nonnegative integer" 

3609 ) 

3610 except TypeError: 

3611 pass 

3612 

3613 kwargs = {} 

3614 pieces = [name, groupname, consumername, min_idle_time, start_id] 

3615 

3616 try: 

3617 if int(count) < 0: 

3618 raise DataError("XPENDING count must be a integer >= 0") 

3619 pieces.extend([b"COUNT", count]) 

3620 except TypeError: 

3621 pass 

3622 if justid: 

3623 pieces.append(b"JUSTID") 

3624 kwargs["parse_justid"] = True 

3625 

3626 return self.execute_command("XAUTOCLAIM", *pieces, **kwargs) 

3627 

3628 def xclaim( 

3629 self, 

3630 name: KeyT, 

3631 groupname: GroupT, 

3632 consumername: ConsumerT, 

3633 min_idle_time: int, 

3634 message_ids: Union[List[StreamIdT], Tuple[StreamIdT]], 

3635 idle: Optional[int] = None, 

3636 time: Optional[int] = None, 

3637 retrycount: Optional[int] = None, 

3638 force: bool = False, 

3639 justid: bool = False, 

3640 ) -> ResponseT: 

3641 """ 

3642 Changes the ownership of a pending message. 

3643 

3644 name: name of the stream. 

3645 

3646 groupname: name of the consumer group. 

3647 

3648 consumername: name of a consumer that claims the message. 

3649 

3650 min_idle_time: filter messages that were idle less than this amount of 

3651 milliseconds 

3652 

3653 message_ids: non-empty list or tuple of message IDs to claim 

3654 

3655 idle: optional. Set the idle time (last time it was delivered) of the 

3656 message in ms 

3657 

3658 time: optional integer. This is the same as idle but instead of a 

3659 relative amount of milliseconds, it sets the idle time to a specific 

3660 Unix time (in milliseconds). 

3661 

3662 retrycount: optional integer. set the retry counter to the specified 

3663 value. This counter is incremented every time a message is delivered 

3664 again. 

3665 

3666 force: optional boolean, false by default. Creates the pending message 

3667 entry in the PEL even if certain specified IDs are not already in the 

3668 PEL assigned to a different client. 

3669 

3670 justid: optional boolean, false by default. Return just an array of IDs 

3671 of messages successfully claimed, without returning the actual message 

3672 

3673 For more information, see https://redis.io/commands/xclaim 

3674 """ 

3675 if not isinstance(min_idle_time, int) or min_idle_time < 0: 

3676 raise DataError("XCLAIM min_idle_time must be a non negative integer") 

3677 if not isinstance(message_ids, (list, tuple)) or not message_ids: 

3678 raise DataError( 

3679 "XCLAIM message_ids must be a non empty list or " 

3680 "tuple of message IDs to claim" 

3681 ) 

3682 

3683 kwargs = {} 

3684 pieces: list[EncodableT] = [name, groupname, consumername, str(min_idle_time)] 

3685 pieces.extend(list(message_ids)) 

3686 

3687 if idle is not None: 

3688 if not isinstance(idle, int): 

3689 raise DataError("XCLAIM idle must be an integer") 

3690 pieces.extend((b"IDLE", str(idle))) 

3691 if time is not None: 

3692 if not isinstance(time, int): 

3693 raise DataError("XCLAIM time must be an integer") 

3694 pieces.extend((b"TIME", str(time))) 

3695 if retrycount is not None: 

3696 if not isinstance(retrycount, int): 

3697 raise DataError("XCLAIM retrycount must be an integer") 

3698 pieces.extend((b"RETRYCOUNT", str(retrycount))) 

3699 

3700 if force: 

3701 if not isinstance(force, bool): 

3702 raise DataError("XCLAIM force must be a boolean") 

3703 pieces.append(b"FORCE") 

3704 if justid: 

3705 if not isinstance(justid, bool): 

3706 raise DataError("XCLAIM justid must be a boolean") 

3707 pieces.append(b"JUSTID") 

3708 kwargs["parse_justid"] = True 

3709 return self.execute_command("XCLAIM", *pieces, **kwargs) 

3710 

3711 def xdel(self, name: KeyT, *ids: StreamIdT) -> ResponseT: 

3712 """ 

3713 Deletes one or more messages from a stream. 

3714 

3715 Args: 

3716 name: name of the stream. 

3717 *ids: message ids to delete. 

3718 

3719 For more information, see https://redis.io/commands/xdel 

3720 """ 

3721 return self.execute_command("XDEL", name, *ids) 

3722 

3723 def xdelex( 

3724 self, 

3725 name: KeyT, 

3726 *ids: StreamIdT, 

3727 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF", 

3728 ) -> ResponseT: 

3729 """ 

3730 Extended version of XDEL that provides more control over how message entries 

3731 are deleted concerning consumer groups. 

3732 """ 

3733 if not ids: 

3734 raise DataError("XDELEX requires at least one message ID") 

3735 

3736 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

3737 raise DataError("XDELEX ref_policy must be one of: KEEPREF, DELREF, ACKED") 

3738 

3739 pieces = [name, ref_policy, "IDS", len(ids)] 

3740 pieces.extend(ids) 

3741 return self.execute_command("XDELEX", *pieces) 

3742 

3743 def xgroup_create( 

3744 self, 

3745 name: KeyT, 

3746 groupname: GroupT, 

3747 id: StreamIdT = "$", 

3748 mkstream: bool = False, 

3749 entries_read: Optional[int] = None, 

3750 ) -> ResponseT: 

3751 """ 

3752 Create a new consumer group associated with a stream. 

3753 name: name of the stream. 

3754 groupname: name of the consumer group. 

3755 id: ID of the last item in the stream to consider already delivered. 

3756 

3757 For more information, see https://redis.io/commands/xgroup-create 

3758 """ 

3759 pieces: list[EncodableT] = ["XGROUP CREATE", name, groupname, id] 

3760 if mkstream: 

3761 pieces.append(b"MKSTREAM") 

3762 if entries_read is not None: 

3763 pieces.extend(["ENTRIESREAD", entries_read]) 

3764 

3765 return self.execute_command(*pieces) 

3766 

3767 def xgroup_delconsumer( 

3768 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

3769 ) -> ResponseT: 

3770 """ 

3771 Remove a specific consumer from a consumer group. 

3772 Returns the number of pending messages that the consumer had before it 

3773 was deleted. 

3774 name: name of the stream. 

3775 groupname: name of the consumer group. 

3776 consumername: name of consumer to delete 

3777 

3778 For more information, see https://redis.io/commands/xgroup-delconsumer 

3779 """ 

3780 return self.execute_command("XGROUP DELCONSUMER", name, groupname, consumername) 

3781 

3782 def xgroup_destroy(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3783 """ 

3784 Destroy a consumer group. 

3785 name: name of the stream. 

3786 groupname: name of the consumer group. 

3787 

3788 For more information, see https://redis.io/commands/xgroup-destroy 

3789 """ 

3790 return self.execute_command("XGROUP DESTROY", name, groupname) 

3791 

3792 def xgroup_createconsumer( 

3793 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

3794 ) -> ResponseT: 

3795 """ 

3796 Consumers in a consumer group are auto-created every time a new 

3797 consumer name is mentioned by some command. 

3798 They can be explicitly created by using this command. 

3799 name: name of the stream. 

3800 groupname: name of the consumer group. 

3801 consumername: name of consumer to create. 

3802 

3803 See: https://redis.io/commands/xgroup-createconsumer 

3804 """ 

3805 return self.execute_command( 

3806 "XGROUP CREATECONSUMER", name, groupname, consumername 

3807 ) 

3808 

3809 def xgroup_setid( 

3810 self, 

3811 name: KeyT, 

3812 groupname: GroupT, 

3813 id: StreamIdT, 

3814 entries_read: Optional[int] = None, 

3815 ) -> ResponseT: 

3816 """ 

3817 Set the consumer group last delivered ID to something else. 

3818 name: name of the stream. 

3819 groupname: name of the consumer group. 

3820 id: ID of the last item in the stream to consider already delivered. 

3821 

3822 For more information, see https://redis.io/commands/xgroup-setid 

3823 """ 

3824 pieces = [name, groupname, id] 

3825 if entries_read is not None: 

3826 pieces.extend(["ENTRIESREAD", entries_read]) 

3827 return self.execute_command("XGROUP SETID", *pieces) 

3828 

3829 def xinfo_consumers(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3830 """ 

3831 Returns general information about the consumers in the group. 

3832 name: name of the stream. 

3833 groupname: name of the consumer group. 

3834 

3835 For more information, see https://redis.io/commands/xinfo-consumers 

3836 """ 

3837 return self.execute_command("XINFO CONSUMERS", name, groupname) 

3838 

3839 def xinfo_groups(self, name: KeyT) -> ResponseT: 

3840 """ 

3841 Returns general information about the consumer groups of the stream. 

3842 name: name of the stream. 

3843 

3844 For more information, see https://redis.io/commands/xinfo-groups 

3845 """ 

3846 return self.execute_command("XINFO GROUPS", name) 

3847 

3848 def xinfo_stream(self, name: KeyT, full: bool = False) -> ResponseT: 

3849 """ 

3850 Returns general information about the stream. 

3851 name: name of the stream. 

3852 full: optional boolean, false by default. Return full summary 

3853 

3854 For more information, see https://redis.io/commands/xinfo-stream 

3855 """ 

3856 pieces = [name] 

3857 options = {} 

3858 if full: 

3859 pieces.append(b"FULL") 

3860 options = {"full": full} 

3861 return self.execute_command("XINFO STREAM", *pieces, **options) 

3862 

3863 def xlen(self, name: KeyT) -> ResponseT: 

3864 """ 

3865 Returns the number of elements in a given stream. 

3866 

3867 For more information, see https://redis.io/commands/xlen 

3868 """ 

3869 return self.execute_command("XLEN", name, keys=[name]) 

3870 

3871 def xpending(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3872 """ 

3873 Returns information about pending messages of a group. 

3874 name: name of the stream. 

3875 groupname: name of the consumer group. 

3876 

3877 For more information, see https://redis.io/commands/xpending 

3878 """ 

3879 return self.execute_command("XPENDING", name, groupname, keys=[name]) 

3880 

3881 def xpending_range( 

3882 self, 

3883 name: KeyT, 

3884 groupname: GroupT, 

3885 min: StreamIdT, 

3886 max: StreamIdT, 

3887 count: int, 

3888 consumername: Union[ConsumerT, None] = None, 

3889 idle: Optional[int] = None, 

3890 ) -> ResponseT: 

3891 """ 

3892 Returns information about pending messages, in a range. 

3893 

3894 name: name of the stream. 

3895 groupname: name of the consumer group. 

3896 idle: available from version 6.2. filter entries by their 

3897 idle-time, given in milliseconds (optional). 

3898 min: minimum stream ID. 

3899 max: maximum stream ID. 

3900 count: number of messages to return 

3901 consumername: name of a consumer to filter by (optional). 

3902 """ 

3903 if {min, max, count} == {None}: 

3904 if idle is not None or consumername is not None: 

3905 raise DataError( 

3906 "if XPENDING is provided with idle time" 

3907 " or consumername, it must be provided" 

3908 " with min, max and count parameters" 

3909 ) 

3910 return self.xpending(name, groupname) 

3911 

3912 pieces = [name, groupname] 

3913 if min is None or max is None or count is None: 

3914 raise DataError( 

3915 "XPENDING must be provided with min, max " 

3916 "and count parameters, or none of them." 

3917 ) 

3918 # idle 

3919 try: 

3920 if int(idle) < 0: 

3921 raise DataError("XPENDING idle must be a integer >= 0") 

3922 pieces.extend(["IDLE", idle]) 

3923 except TypeError: 

3924 pass 

3925 # count 

3926 try: 

3927 if int(count) < 0: 

3928 raise DataError("XPENDING count must be a integer >= 0") 

3929 pieces.extend([min, max, count]) 

3930 except TypeError: 

3931 pass 

3932 # consumername 

3933 if consumername: 

3934 pieces.append(consumername) 

3935 

3936 return self.execute_command("XPENDING", *pieces, parse_detail=True) 

3937 

3938 def xrange( 

3939 self, 

3940 name: KeyT, 

3941 min: StreamIdT = "-", 

3942 max: StreamIdT = "+", 

3943 count: Optional[int] = None, 

3944 ) -> ResponseT: 

3945 """ 

3946 Read stream values within an interval. 

3947 

3948 name: name of the stream. 

3949 

3950 start: first stream ID. defaults to '-', 

3951 meaning the earliest available. 

3952 

3953 finish: last stream ID. defaults to '+', 

3954 meaning the latest available. 

3955 

3956 count: if set, only return this many items, beginning with the 

3957 earliest available. 

3958 

3959 For more information, see https://redis.io/commands/xrange 

3960 """ 

3961 pieces = [min, max] 

3962 if count is not None: 

3963 if not isinstance(count, int) or count < 1: 

3964 raise DataError("XRANGE count must be a positive integer") 

3965 pieces.append(b"COUNT") 

3966 pieces.append(str(count)) 

3967 

3968 return self.execute_command("XRANGE", name, *pieces, keys=[name]) 

3969 

3970 def xread( 

3971 self, 

3972 streams: Dict[KeyT, StreamIdT], 

3973 count: Optional[int] = None, 

3974 block: Optional[int] = None, 

3975 ) -> ResponseT: 

3976 """ 

3977 Block and monitor multiple streams for new data. 

3978 

3979 streams: a dict of stream names to stream IDs, where 

3980 IDs indicate the last ID already seen. 

3981 

3982 count: if set, only return this many items, beginning with the 

3983 earliest available. 

3984 

3985 block: number of milliseconds to wait, if nothing already present. 

3986 

3987 For more information, see https://redis.io/commands/xread 

3988 """ 

3989 pieces = [] 

3990 if block is not None: 

3991 if not isinstance(block, int) or block < 0: 

3992 raise DataError("XREAD block must be a non-negative integer") 

3993 pieces.append(b"BLOCK") 

3994 pieces.append(str(block)) 

3995 if count is not None: 

3996 if not isinstance(count, int) or count < 1: 

3997 raise DataError("XREAD count must be a positive integer") 

3998 pieces.append(b"COUNT") 

3999 pieces.append(str(count)) 

4000 if not isinstance(streams, dict) or len(streams) == 0: 

4001 raise DataError("XREAD streams must be a non empty dict") 

4002 pieces.append(b"STREAMS") 

4003 keys, values = zip(*streams.items()) 

4004 pieces.extend(keys) 

4005 pieces.extend(values) 

4006 return self.execute_command("XREAD", *pieces, keys=keys) 

4007 

4008 def xreadgroup( 

4009 self, 

4010 groupname: str, 

4011 consumername: str, 

4012 streams: Dict[KeyT, StreamIdT], 

4013 count: Optional[int] = None, 

4014 block: Optional[int] = None, 

4015 noack: bool = False, 

4016 ) -> ResponseT: 

4017 """ 

4018 Read from a stream via a consumer group. 

4019 

4020 groupname: name of the consumer group. 

4021 

4022 consumername: name of the requesting consumer. 

4023 

4024 streams: a dict of stream names to stream IDs, where 

4025 IDs indicate the last ID already seen. 

4026 

4027 count: if set, only return this many items, beginning with the 

4028 earliest available. 

4029 

4030 block: number of milliseconds to wait, if nothing already present. 

4031 noack: do not add messages to the PEL 

4032 

4033 For more information, see https://redis.io/commands/xreadgroup 

4034 """ 

4035 pieces: list[EncodableT] = [b"GROUP", groupname, consumername] 

4036 if count is not None: 

4037 if not isinstance(count, int) or count < 1: 

4038 raise DataError("XREADGROUP count must be a positive integer") 

4039 pieces.append(b"COUNT") 

4040 pieces.append(str(count)) 

4041 if block is not None: 

4042 if not isinstance(block, int) or block < 0: 

4043 raise DataError("XREADGROUP block must be a non-negative integer") 

4044 pieces.append(b"BLOCK") 

4045 pieces.append(str(block)) 

4046 if noack: 

4047 pieces.append(b"NOACK") 

4048 if not isinstance(streams, dict) or len(streams) == 0: 

4049 raise DataError("XREADGROUP streams must be a non empty dict") 

4050 pieces.append(b"STREAMS") 

4051 pieces.extend(streams.keys()) 

4052 pieces.extend(streams.values()) 

4053 return self.execute_command("XREADGROUP", *pieces) 

4054 

4055 def xrevrange( 

4056 self, 

4057 name: KeyT, 

4058 max: StreamIdT = "+", 

4059 min: StreamIdT = "-", 

4060 count: Optional[int] = None, 

4061 ) -> ResponseT: 

4062 """ 

4063 Read stream values within an interval, in reverse order. 

4064 

4065 name: name of the stream 

4066 

4067 start: first stream ID. defaults to '+', 

4068 meaning the latest available. 

4069 

4070 finish: last stream ID. defaults to '-', 

4071 meaning the earliest available. 

4072 

4073 count: if set, only return this many items, beginning with the 

4074 latest available. 

4075 

4076 For more information, see https://redis.io/commands/xrevrange 

4077 """ 

4078 pieces: list[EncodableT] = [max, min] 

4079 if count is not None: 

4080 if not isinstance(count, int) or count < 1: 

4081 raise DataError("XREVRANGE count must be a positive integer") 

4082 pieces.append(b"COUNT") 

4083 pieces.append(str(count)) 

4084 

4085 return self.execute_command("XREVRANGE", name, *pieces, keys=[name]) 

4086 

4087 def xtrim( 

4088 self, 

4089 name: KeyT, 

4090 maxlen: Optional[int] = None, 

4091 approximate: bool = True, 

4092 minid: Union[StreamIdT, None] = None, 

4093 limit: Optional[int] = None, 

4094 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None, 

4095 ) -> ResponseT: 

4096 """ 

4097 Trims old messages from a stream. 

4098 name: name of the stream. 

4099 maxlen: truncate old stream messages beyond this size 

4100 Can't be specified with minid. 

4101 approximate: actual stream length may be slightly more than maxlen 

4102 minid: the minimum id in the stream to query 

4103 Can't be specified with maxlen. 

4104 limit: specifies the maximum number of entries to retrieve 

4105 ref_policy: optional reference policy for consumer groups: 

4106 - KEEPREF (default): Trims entries but preserves references in consumer groups' PEL 

4107 - DELREF: Trims entries and removes all references from consumer groups' PEL 

4108 - ACKED: Only trims entries that were read and acknowledged by all consumer groups 

4109 

4110 For more information, see https://redis.io/commands/xtrim 

4111 """ 

4112 pieces: list[EncodableT] = [] 

4113 if maxlen is not None and minid is not None: 

4114 raise DataError("Only one of ``maxlen`` or ``minid`` may be specified") 

4115 

4116 if maxlen is None and minid is None: 

4117 raise DataError("One of ``maxlen`` or ``minid`` must be specified") 

4118 

4119 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

4120 raise DataError("XTRIM ref_policy must be one of: KEEPREF, DELREF, ACKED") 

4121 

4122 if maxlen is not None: 

4123 pieces.append(b"MAXLEN") 

4124 if minid is not None: 

4125 pieces.append(b"MINID") 

4126 if approximate: 

4127 pieces.append(b"~") 

4128 if maxlen is not None: 

4129 pieces.append(maxlen) 

4130 if minid is not None: 

4131 pieces.append(minid) 

4132 if limit is not None: 

4133 pieces.append(b"LIMIT") 

4134 pieces.append(limit) 

4135 if ref_policy is not None: 

4136 pieces.append(ref_policy) 

4137 

4138 return self.execute_command("XTRIM", name, *pieces) 

4139 

4140 

4141AsyncStreamCommands = StreamCommands 

4142 

4143 

4144class SortedSetCommands(CommandsProtocol): 

4145 """ 

4146 Redis commands for Sorted Sets data type. 

4147 see: https://redis.io/topics/data-types-intro#redis-sorted-sets 

4148 """ 

4149 

4150 def zadd( 

4151 self, 

4152 name: KeyT, 

4153 mapping: Mapping[AnyKeyT, EncodableT], 

4154 nx: bool = False, 

4155 xx: bool = False, 

4156 ch: bool = False, 

4157 incr: bool = False, 

4158 gt: bool = False, 

4159 lt: bool = False, 

4160 ) -> ResponseT: 

4161 """ 

4162 Set any number of element-name, score pairs to the key ``name``. Pairs 

4163 are specified as a dict of element-names keys to score values. 

4164 

4165 ``nx`` forces ZADD to only create new elements and not to update 

4166 scores for elements that already exist. 

4167 

4168 ``xx`` forces ZADD to only update scores of elements that already 

4169 exist. New elements will not be added. 

4170 

4171 ``ch`` modifies the return value to be the numbers of elements changed. 

4172 Changed elements include new elements that were added and elements 

4173 whose scores changed. 

4174 

4175 ``incr`` modifies ZADD to behave like ZINCRBY. In this mode only a 

4176 single element/score pair can be specified and the score is the amount 

4177 the existing score will be incremented by. When using this mode the 

4178 return value of ZADD will be the new score of the element. 

4179 

4180 ``lt`` only updates existing elements if the new score is less than 

4181 the current score. This flag doesn't prevent adding new elements. 

4182 

4183 ``gt`` only updates existing elements if the new score is greater than 

4184 the current score. This flag doesn't prevent adding new elements. 

4185 

4186 The return value of ZADD varies based on the mode specified. With no 

4187 options, ZADD returns the number of new elements added to the sorted 

4188 set. 

4189 

4190 ``nx``, ``lt``, and ``gt`` are mutually exclusive options. 

4191 

4192 See: https://redis.io/commands/ZADD 

4193 """ 

4194 if not mapping: 

4195 raise DataError("ZADD requires at least one element/score pair") 

4196 if nx and xx: 

4197 raise DataError("ZADD allows either 'nx' or 'xx', not both") 

4198 if gt and lt: 

4199 raise DataError("ZADD allows either 'gt' or 'lt', not both") 

4200 if incr and len(mapping) != 1: 

4201 raise DataError( 

4202 "ZADD option 'incr' only works when passing a single element/score pair" 

4203 ) 

4204 if nx and (gt or lt): 

4205 raise DataError("Only one of 'nx', 'lt', or 'gr' may be defined.") 

4206 

4207 pieces: list[EncodableT] = [] 

4208 options = {} 

4209 if nx: 

4210 pieces.append(b"NX") 

4211 if xx: 

4212 pieces.append(b"XX") 

4213 if ch: 

4214 pieces.append(b"CH") 

4215 if incr: 

4216 pieces.append(b"INCR") 

4217 options["as_score"] = True 

4218 if gt: 

4219 pieces.append(b"GT") 

4220 if lt: 

4221 pieces.append(b"LT") 

4222 for pair in mapping.items(): 

4223 pieces.append(pair[1]) 

4224 pieces.append(pair[0]) 

4225 return self.execute_command("ZADD", name, *pieces, **options) 

4226 

4227 def zcard(self, name: KeyT) -> ResponseT: 

4228 """ 

4229 Return the number of elements in the sorted set ``name`` 

4230 

4231 For more information, see https://redis.io/commands/zcard 

4232 """ 

4233 return self.execute_command("ZCARD", name, keys=[name]) 

4234 

4235 def zcount(self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT) -> ResponseT: 

4236 """ 

4237 Returns the number of elements in the sorted set at key ``name`` with 

4238 a score between ``min`` and ``max``. 

4239 

4240 For more information, see https://redis.io/commands/zcount 

4241 """ 

4242 return self.execute_command("ZCOUNT", name, min, max, keys=[name]) 

4243 

4244 def zdiff(self, keys: KeysT, withscores: bool = False) -> ResponseT: 

4245 """ 

4246 Returns the difference between the first and all successive input 

4247 sorted sets provided in ``keys``. 

4248 

4249 For more information, see https://redis.io/commands/zdiff 

4250 """ 

4251 pieces = [len(keys), *keys] 

4252 if withscores: 

4253 pieces.append("WITHSCORES") 

4254 return self.execute_command("ZDIFF", *pieces, keys=keys) 

4255 

4256 def zdiffstore(self, dest: KeyT, keys: KeysT) -> ResponseT: 

4257 """ 

4258 Computes the difference between the first and all successive input 

4259 sorted sets provided in ``keys`` and stores the result in ``dest``. 

4260 

4261 For more information, see https://redis.io/commands/zdiffstore 

4262 """ 

4263 pieces = [len(keys), *keys] 

4264 return self.execute_command("ZDIFFSTORE", dest, *pieces) 

4265 

4266 def zincrby(self, name: KeyT, amount: float, value: EncodableT) -> ResponseT: 

4267 """ 

4268 Increment the score of ``value`` in sorted set ``name`` by ``amount`` 

4269 

4270 For more information, see https://redis.io/commands/zincrby 

4271 """ 

4272 return self.execute_command("ZINCRBY", name, amount, value) 

4273 

4274 def zinter( 

4275 self, keys: KeysT, aggregate: Optional[str] = None, withscores: bool = False 

4276 ) -> ResponseT: 

4277 """ 

4278 Return the intersect of multiple sorted sets specified by ``keys``. 

4279 With the ``aggregate`` option, it is possible to specify how the 

4280 results of the union are aggregated. This option defaults to SUM, 

4281 where the score of an element is summed across the inputs where it 

4282 exists. When this option is set to either MIN or MAX, the resulting 

4283 set will contain the minimum or maximum score of an element across 

4284 the inputs where it exists. 

4285 

4286 For more information, see https://redis.io/commands/zinter 

4287 """ 

4288 return self._zaggregate("ZINTER", None, keys, aggregate, withscores=withscores) 

4289 

4290 def zinterstore( 

4291 self, 

4292 dest: KeyT, 

4293 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4294 aggregate: Optional[str] = None, 

4295 ) -> ResponseT: 

4296 """ 

4297 Intersect multiple sorted sets specified by ``keys`` into a new 

4298 sorted set, ``dest``. Scores in the destination will be aggregated 

4299 based on the ``aggregate``. This option defaults to SUM, where the 

4300 score of an element is summed across the inputs where it exists. 

4301 When this option is set to either MIN or MAX, the resulting set will 

4302 contain the minimum or maximum score of an element across the inputs 

4303 where it exists. 

4304 

4305 For more information, see https://redis.io/commands/zinterstore 

4306 """ 

4307 return self._zaggregate("ZINTERSTORE", dest, keys, aggregate) 

4308 

4309 def zintercard( 

4310 self, numkeys: int, keys: List[str], limit: int = 0 

4311 ) -> Union[Awaitable[int], int]: 

4312 """ 

4313 Return the cardinality of the intersect of multiple sorted sets 

4314 specified by ``keys``. 

4315 When LIMIT provided (defaults to 0 and means unlimited), if the intersection 

4316 cardinality reaches limit partway through the computation, the algorithm will 

4317 exit and yield limit as the cardinality 

4318 

4319 For more information, see https://redis.io/commands/zintercard 

4320 """ 

4321 args = [numkeys, *keys, "LIMIT", limit] 

4322 return self.execute_command("ZINTERCARD", *args, keys=keys) 

4323 

4324 def zlexcount(self, name, min, max): 

4325 """ 

4326 Return the number of items in the sorted set ``name`` between the 

4327 lexicographical range ``min`` and ``max``. 

4328 

4329 For more information, see https://redis.io/commands/zlexcount 

4330 """ 

4331 return self.execute_command("ZLEXCOUNT", name, min, max, keys=[name]) 

4332 

4333 def zpopmax(self, name: KeyT, count: Optional[int] = None) -> ResponseT: 

4334 """ 

4335 Remove and return up to ``count`` members with the highest scores 

4336 from the sorted set ``name``. 

4337 

4338 For more information, see https://redis.io/commands/zpopmax 

4339 """ 

4340 args = (count is not None) and [count] or [] 

4341 options = {"withscores": True} 

4342 return self.execute_command("ZPOPMAX", name, *args, **options) 

4343 

4344 def zpopmin(self, name: KeyT, count: Optional[int] = None) -> ResponseT: 

4345 """ 

4346 Remove and return up to ``count`` members with the lowest scores 

4347 from the sorted set ``name``. 

4348 

4349 For more information, see https://redis.io/commands/zpopmin 

4350 """ 

4351 args = (count is not None) and [count] or [] 

4352 options = {"withscores": True} 

4353 return self.execute_command("ZPOPMIN", name, *args, **options) 

4354 

4355 def zrandmember( 

4356 self, key: KeyT, count: Optional[int] = None, withscores: bool = False 

4357 ) -> ResponseT: 

4358 """ 

4359 Return a random element from the sorted set value stored at key. 

4360 

4361 ``count`` if the argument is positive, return an array of distinct 

4362 fields. If called with a negative count, the behavior changes and 

4363 the command is allowed to return the same field multiple times. 

4364 In this case, the number of returned fields is the absolute value 

4365 of the specified count. 

4366 

4367 ``withscores`` The optional WITHSCORES modifier changes the reply so it 

4368 includes the respective scores of the randomly selected elements from 

4369 the sorted set. 

4370 

4371 For more information, see https://redis.io/commands/zrandmember 

4372 """ 

4373 params = [] 

4374 if count is not None: 

4375 params.append(count) 

4376 if withscores: 

4377 params.append("WITHSCORES") 

4378 

4379 return self.execute_command("ZRANDMEMBER", key, *params) 

4380 

4381 def bzpopmax(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4382 """ 

4383 ZPOPMAX a value off of the first non-empty sorted set 

4384 named in the ``keys`` list. 

4385 

4386 If none of the sorted sets in ``keys`` has a value to ZPOPMAX, 

4387 then block for ``timeout`` seconds, or until a member gets added 

4388 to one of the sorted sets. 

4389 

4390 If timeout is 0, then block indefinitely. 

4391 

4392 For more information, see https://redis.io/commands/bzpopmax 

4393 """ 

4394 if timeout is None: 

4395 timeout = 0 

4396 keys = list_or_args(keys, None) 

4397 keys.append(timeout) 

4398 return self.execute_command("BZPOPMAX", *keys) 

4399 

4400 def bzpopmin(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4401 """ 

4402 ZPOPMIN a value off of the first non-empty sorted set 

4403 named in the ``keys`` list. 

4404 

4405 If none of the sorted sets in ``keys`` has a value to ZPOPMIN, 

4406 then block for ``timeout`` seconds, or until a member gets added 

4407 to one of the sorted sets. 

4408 

4409 If timeout is 0, then block indefinitely. 

4410 

4411 For more information, see https://redis.io/commands/bzpopmin 

4412 """ 

4413 if timeout is None: 

4414 timeout = 0 

4415 keys: list[EncodableT] = list_or_args(keys, None) 

4416 keys.append(timeout) 

4417 return self.execute_command("BZPOPMIN", *keys) 

4418 

4419 def zmpop( 

4420 self, 

4421 num_keys: int, 

4422 keys: List[str], 

4423 min: Optional[bool] = False, 

4424 max: Optional[bool] = False, 

4425 count: Optional[int] = 1, 

4426 ) -> Union[Awaitable[list], list]: 

4427 """ 

4428 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4429 named in the ``keys`` list. 

4430 For more information, see https://redis.io/commands/zmpop 

4431 """ 

4432 args = [num_keys] + keys 

4433 if (min and max) or (not min and not max): 

4434 raise DataError 

4435 elif min: 

4436 args.append("MIN") 

4437 else: 

4438 args.append("MAX") 

4439 if count != 1: 

4440 args.extend(["COUNT", count]) 

4441 

4442 return self.execute_command("ZMPOP", *args) 

4443 

4444 def bzmpop( 

4445 self, 

4446 timeout: float, 

4447 numkeys: int, 

4448 keys: List[str], 

4449 min: Optional[bool] = False, 

4450 max: Optional[bool] = False, 

4451 count: Optional[int] = 1, 

4452 ) -> Optional[list]: 

4453 """ 

4454 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4455 named in the ``keys`` list. 

4456 

4457 If none of the sorted sets in ``keys`` has a value to pop, 

4458 then block for ``timeout`` seconds, or until a member gets added 

4459 to one of the sorted sets. 

4460 

4461 If timeout is 0, then block indefinitely. 

4462 

4463 For more information, see https://redis.io/commands/bzmpop 

4464 """ 

4465 args = [timeout, numkeys, *keys] 

4466 if (min and max) or (not min and not max): 

4467 raise DataError("Either min or max, but not both must be set") 

4468 elif min: 

4469 args.append("MIN") 

4470 else: 

4471 args.append("MAX") 

4472 args.extend(["COUNT", count]) 

4473 

4474 return self.execute_command("BZMPOP", *args) 

4475 

4476 def _zrange( 

4477 self, 

4478 command, 

4479 dest: Union[KeyT, None], 

4480 name: KeyT, 

4481 start: int, 

4482 end: int, 

4483 desc: bool = False, 

4484 byscore: bool = False, 

4485 bylex: bool = False, 

4486 withscores: bool = False, 

4487 score_cast_func: Union[type, Callable, None] = float, 

4488 offset: Optional[int] = None, 

4489 num: Optional[int] = None, 

4490 ) -> ResponseT: 

4491 if byscore and bylex: 

4492 raise DataError("``byscore`` and ``bylex`` can not be specified together.") 

4493 if (offset is not None and num is None) or (num is not None and offset is None): 

4494 raise DataError("``offset`` and ``num`` must both be specified.") 

4495 if bylex and withscores: 

4496 raise DataError( 

4497 "``withscores`` not supported in combination with ``bylex``." 

4498 ) 

4499 pieces = [command] 

4500 if dest: 

4501 pieces.append(dest) 

4502 pieces.extend([name, start, end]) 

4503 if byscore: 

4504 pieces.append("BYSCORE") 

4505 if bylex: 

4506 pieces.append("BYLEX") 

4507 if desc: 

4508 pieces.append("REV") 

4509 if offset is not None and num is not None: 

4510 pieces.extend(["LIMIT", offset, num]) 

4511 if withscores: 

4512 pieces.append("WITHSCORES") 

4513 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4514 options["keys"] = [name] 

4515 return self.execute_command(*pieces, **options) 

4516 

4517 def zrange( 

4518 self, 

4519 name: KeyT, 

4520 start: int, 

4521 end: int, 

4522 desc: bool = False, 

4523 withscores: bool = False, 

4524 score_cast_func: Union[type, Callable] = float, 

4525 byscore: bool = False, 

4526 bylex: bool = False, 

4527 offset: Optional[int] = None, 

4528 num: Optional[int] = None, 

4529 ) -> ResponseT: 

4530 """ 

4531 Return a range of values from sorted set ``name`` between 

4532 ``start`` and ``end`` sorted in ascending order. 

4533 

4534 ``start`` and ``end`` can be negative, indicating the end of the range. 

4535 

4536 ``desc`` a boolean indicating whether to sort the results in reversed 

4537 order. 

4538 

4539 ``withscores`` indicates to return the scores along with the values. 

4540 The return type is a list of (value, score) pairs. 

4541 

4542 ``score_cast_func`` a callable used to cast the score return value. 

4543 

4544 ``byscore`` when set to True, returns the range of elements from the 

4545 sorted set having scores equal or between ``start`` and ``end``. 

4546 

4547 ``bylex`` when set to True, returns the range of elements from the 

4548 sorted set between the ``start`` and ``end`` lexicographical closed 

4549 range intervals. 

4550 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

4551 whether the range interval is exclusive or inclusive, respectively. 

4552 

4553 ``offset`` and ``num`` are specified, then return a slice of the range. 

4554 Can't be provided when using ``bylex``. 

4555 

4556 For more information, see https://redis.io/commands/zrange 

4557 """ 

4558 # Need to support ``desc`` also when using old redis version 

4559 # because it was supported in 3.5.3 (of redis-py) 

4560 if not byscore and not bylex and (offset is None and num is None) and desc: 

4561 return self.zrevrange(name, start, end, withscores, score_cast_func) 

4562 

4563 return self._zrange( 

4564 "ZRANGE", 

4565 None, 

4566 name, 

4567 start, 

4568 end, 

4569 desc, 

4570 byscore, 

4571 bylex, 

4572 withscores, 

4573 score_cast_func, 

4574 offset, 

4575 num, 

4576 ) 

4577 

4578 def zrevrange( 

4579 self, 

4580 name: KeyT, 

4581 start: int, 

4582 end: int, 

4583 withscores: bool = False, 

4584 score_cast_func: Union[type, Callable] = float, 

4585 ) -> ResponseT: 

4586 """ 

4587 Return a range of values from sorted set ``name`` between 

4588 ``start`` and ``end`` sorted in descending order. 

4589 

4590 ``start`` and ``end`` can be negative, indicating the end of the range. 

4591 

4592 ``withscores`` indicates to return the scores along with the values 

4593 The return type is a list of (value, score) pairs 

4594 

4595 ``score_cast_func`` a callable used to cast the score return value 

4596 

4597 For more information, see https://redis.io/commands/zrevrange 

4598 """ 

4599 pieces = ["ZREVRANGE", name, start, end] 

4600 if withscores: 

4601 pieces.append(b"WITHSCORES") 

4602 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4603 options["keys"] = name 

4604 return self.execute_command(*pieces, **options) 

4605 

4606 def zrangestore( 

4607 self, 

4608 dest: KeyT, 

4609 name: KeyT, 

4610 start: int, 

4611 end: int, 

4612 byscore: bool = False, 

4613 bylex: bool = False, 

4614 desc: bool = False, 

4615 offset: Optional[int] = None, 

4616 num: Optional[int] = None, 

4617 ) -> ResponseT: 

4618 """ 

4619 Stores in ``dest`` the result of a range of values from sorted set 

4620 ``name`` between ``start`` and ``end`` sorted in ascending order. 

4621 

4622 ``start`` and ``end`` can be negative, indicating the end of the range. 

4623 

4624 ``byscore`` when set to True, returns the range of elements from the 

4625 sorted set having scores equal or between ``start`` and ``end``. 

4626 

4627 ``bylex`` when set to True, returns the range of elements from the 

4628 sorted set between the ``start`` and ``end`` lexicographical closed 

4629 range intervals. 

4630 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

4631 whether the range interval is exclusive or inclusive, respectively. 

4632 

4633 ``desc`` a boolean indicating whether to sort the results in reversed 

4634 order. 

4635 

4636 ``offset`` and ``num`` are specified, then return a slice of the range. 

4637 Can't be provided when using ``bylex``. 

4638 

4639 For more information, see https://redis.io/commands/zrangestore 

4640 """ 

4641 return self._zrange( 

4642 "ZRANGESTORE", 

4643 dest, 

4644 name, 

4645 start, 

4646 end, 

4647 desc, 

4648 byscore, 

4649 bylex, 

4650 False, 

4651 None, 

4652 offset, 

4653 num, 

4654 ) 

4655 

4656 def zrangebylex( 

4657 self, 

4658 name: KeyT, 

4659 min: EncodableT, 

4660 max: EncodableT, 

4661 start: Optional[int] = None, 

4662 num: Optional[int] = None, 

4663 ) -> ResponseT: 

4664 """ 

4665 Return the lexicographical range of values from sorted set ``name`` 

4666 between ``min`` and ``max``. 

4667 

4668 If ``start`` and ``num`` are specified, then return a slice of the 

4669 range. 

4670 

4671 For more information, see https://redis.io/commands/zrangebylex 

4672 """ 

4673 if (start is not None and num is None) or (num is not None and start is None): 

4674 raise DataError("``start`` and ``num`` must both be specified") 

4675 pieces = ["ZRANGEBYLEX", name, min, max] 

4676 if start is not None and num is not None: 

4677 pieces.extend([b"LIMIT", start, num]) 

4678 return self.execute_command(*pieces, keys=[name]) 

4679 

4680 def zrevrangebylex( 

4681 self, 

4682 name: KeyT, 

4683 max: EncodableT, 

4684 min: EncodableT, 

4685 start: Optional[int] = None, 

4686 num: Optional[int] = None, 

4687 ) -> ResponseT: 

4688 """ 

4689 Return the reversed lexicographical range of values from sorted set 

4690 ``name`` between ``max`` and ``min``. 

4691 

4692 If ``start`` and ``num`` are specified, then return a slice of the 

4693 range. 

4694 

4695 For more information, see https://redis.io/commands/zrevrangebylex 

4696 """ 

4697 if (start is not None and num is None) or (num is not None and start is None): 

4698 raise DataError("``start`` and ``num`` must both be specified") 

4699 pieces = ["ZREVRANGEBYLEX", name, max, min] 

4700 if start is not None and num is not None: 

4701 pieces.extend(["LIMIT", start, num]) 

4702 return self.execute_command(*pieces, keys=[name]) 

4703 

4704 def zrangebyscore( 

4705 self, 

4706 name: KeyT, 

4707 min: ZScoreBoundT, 

4708 max: ZScoreBoundT, 

4709 start: Optional[int] = None, 

4710 num: Optional[int] = None, 

4711 withscores: bool = False, 

4712 score_cast_func: Union[type, Callable] = float, 

4713 ) -> ResponseT: 

4714 """ 

4715 Return a range of values from the sorted set ``name`` with scores 

4716 between ``min`` and ``max``. 

4717 

4718 If ``start`` and ``num`` are specified, then return a slice 

4719 of the range. 

4720 

4721 ``withscores`` indicates to return the scores along with the values. 

4722 The return type is a list of (value, score) pairs 

4723 

4724 `score_cast_func`` a callable used to cast the score return value 

4725 

4726 For more information, see https://redis.io/commands/zrangebyscore 

4727 """ 

4728 if (start is not None and num is None) or (num is not None and start is None): 

4729 raise DataError("``start`` and ``num`` must both be specified") 

4730 pieces = ["ZRANGEBYSCORE", name, min, max] 

4731 if start is not None and num is not None: 

4732 pieces.extend(["LIMIT", start, num]) 

4733 if withscores: 

4734 pieces.append("WITHSCORES") 

4735 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4736 options["keys"] = [name] 

4737 return self.execute_command(*pieces, **options) 

4738 

4739 def zrevrangebyscore( 

4740 self, 

4741 name: KeyT, 

4742 max: ZScoreBoundT, 

4743 min: ZScoreBoundT, 

4744 start: Optional[int] = None, 

4745 num: Optional[int] = None, 

4746 withscores: bool = False, 

4747 score_cast_func: Union[type, Callable] = float, 

4748 ): 

4749 """ 

4750 Return a range of values from the sorted set ``name`` with scores 

4751 between ``min`` and ``max`` in descending order. 

4752 

4753 If ``start`` and ``num`` are specified, then return a slice 

4754 of the range. 

4755 

4756 ``withscores`` indicates to return the scores along with the values. 

4757 The return type is a list of (value, score) pairs 

4758 

4759 ``score_cast_func`` a callable used to cast the score return value 

4760 

4761 For more information, see https://redis.io/commands/zrevrangebyscore 

4762 """ 

4763 if (start is not None and num is None) or (num is not None and start is None): 

4764 raise DataError("``start`` and ``num`` must both be specified") 

4765 pieces = ["ZREVRANGEBYSCORE", name, max, min] 

4766 if start is not None and num is not None: 

4767 pieces.extend(["LIMIT", start, num]) 

4768 if withscores: 

4769 pieces.append("WITHSCORES") 

4770 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4771 options["keys"] = [name] 

4772 return self.execute_command(*pieces, **options) 

4773 

4774 def zrank( 

4775 self, 

4776 name: KeyT, 

4777 value: EncodableT, 

4778 withscore: bool = False, 

4779 score_cast_func: Union[type, Callable] = float, 

4780 ) -> ResponseT: 

4781 """ 

4782 Returns a 0-based value indicating the rank of ``value`` in sorted set 

4783 ``name``. 

4784 The optional WITHSCORE argument supplements the command's 

4785 reply with the score of the element returned. 

4786 

4787 ``score_cast_func`` a callable used to cast the score return value 

4788 

4789 For more information, see https://redis.io/commands/zrank 

4790 """ 

4791 pieces = ["ZRANK", name, value] 

4792 if withscore: 

4793 pieces.append("WITHSCORE") 

4794 

4795 options = {"withscore": withscore, "score_cast_func": score_cast_func} 

4796 

4797 return self.execute_command(*pieces, **options) 

4798 

4799 def zrem(self, name: KeyT, *values: FieldT) -> ResponseT: 

4800 """ 

4801 Remove member ``values`` from sorted set ``name`` 

4802 

4803 For more information, see https://redis.io/commands/zrem 

4804 """ 

4805 return self.execute_command("ZREM", name, *values) 

4806 

4807 def zremrangebylex(self, name: KeyT, min: EncodableT, max: EncodableT) -> ResponseT: 

4808 """ 

4809 Remove all elements in the sorted set ``name`` between the 

4810 lexicographical range specified by ``min`` and ``max``. 

4811 

4812 Returns the number of elements removed. 

4813 

4814 For more information, see https://redis.io/commands/zremrangebylex 

4815 """ 

4816 return self.execute_command("ZREMRANGEBYLEX", name, min, max) 

4817 

4818 def zremrangebyrank(self, name: KeyT, min: int, max: int) -> ResponseT: 

4819 """ 

4820 Remove all elements in the sorted set ``name`` with ranks between 

4821 ``min`` and ``max``. Values are 0-based, ordered from smallest score 

4822 to largest. Values can be negative indicating the highest scores. 

4823 Returns the number of elements removed 

4824 

4825 For more information, see https://redis.io/commands/zremrangebyrank 

4826 """ 

4827 return self.execute_command("ZREMRANGEBYRANK", name, min, max) 

4828 

4829 def zremrangebyscore( 

4830 self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT 

4831 ) -> ResponseT: 

4832 """ 

4833 Remove all elements in the sorted set ``name`` with scores 

4834 between ``min`` and ``max``. Returns the number of elements removed. 

4835 

4836 For more information, see https://redis.io/commands/zremrangebyscore 

4837 """ 

4838 return self.execute_command("ZREMRANGEBYSCORE", name, min, max) 

4839 

4840 def zrevrank( 

4841 self, 

4842 name: KeyT, 

4843 value: EncodableT, 

4844 withscore: bool = False, 

4845 score_cast_func: Union[type, Callable] = float, 

4846 ) -> ResponseT: 

4847 """ 

4848 Returns a 0-based value indicating the descending rank of 

4849 ``value`` in sorted set ``name``. 

4850 The optional ``withscore`` argument supplements the command's 

4851 reply with the score of the element returned. 

4852 

4853 ``score_cast_func`` a callable used to cast the score return value 

4854 

4855 For more information, see https://redis.io/commands/zrevrank 

4856 """ 

4857 pieces = ["ZREVRANK", name, value] 

4858 if withscore: 

4859 pieces.append("WITHSCORE") 

4860 

4861 options = {"withscore": withscore, "score_cast_func": score_cast_func} 

4862 

4863 return self.execute_command(*pieces, **options) 

4864 

4865 def zscore(self, name: KeyT, value: EncodableT) -> ResponseT: 

4866 """ 

4867 Return the score of element ``value`` in sorted set ``name`` 

4868 

4869 For more information, see https://redis.io/commands/zscore 

4870 """ 

4871 return self.execute_command("ZSCORE", name, value, keys=[name]) 

4872 

4873 def zunion( 

4874 self, 

4875 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4876 aggregate: Optional[str] = None, 

4877 withscores: bool = False, 

4878 score_cast_func: Union[type, Callable] = float, 

4879 ) -> ResponseT: 

4880 """ 

4881 Return the union of multiple sorted sets specified by ``keys``. 

4882 ``keys`` can be provided as dictionary of keys and their weights. 

4883 Scores will be aggregated based on the ``aggregate``, or SUM if 

4884 none is provided. 

4885 

4886 ``score_cast_func`` a callable used to cast the score return value 

4887 

4888 For more information, see https://redis.io/commands/zunion 

4889 """ 

4890 return self._zaggregate( 

4891 "ZUNION", 

4892 None, 

4893 keys, 

4894 aggregate, 

4895 withscores=withscores, 

4896 score_cast_func=score_cast_func, 

4897 ) 

4898 

4899 def zunionstore( 

4900 self, 

4901 dest: KeyT, 

4902 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4903 aggregate: Optional[str] = None, 

4904 ) -> ResponseT: 

4905 """ 

4906 Union multiple sorted sets specified by ``keys`` into 

4907 a new sorted set, ``dest``. Scores in the destination will be 

4908 aggregated based on the ``aggregate``, or SUM if none is provided. 

4909 

4910 For more information, see https://redis.io/commands/zunionstore 

4911 """ 

4912 return self._zaggregate("ZUNIONSTORE", dest, keys, aggregate) 

4913 

4914 def zmscore(self, key: KeyT, members: List[str]) -> ResponseT: 

4915 """ 

4916 Returns the scores associated with the specified members 

4917 in the sorted set stored at key. 

4918 ``members`` should be a list of the member name. 

4919 Return type is a list of score. 

4920 If the member does not exist, a None will be returned 

4921 in corresponding position. 

4922 

4923 For more information, see https://redis.io/commands/zmscore 

4924 """ 

4925 if not members: 

4926 raise DataError("ZMSCORE members must be a non-empty list") 

4927 pieces = [key] + members 

4928 return self.execute_command("ZMSCORE", *pieces, keys=[key]) 

4929 

4930 def _zaggregate( 

4931 self, 

4932 command: str, 

4933 dest: Union[KeyT, None], 

4934 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4935 aggregate: Optional[str] = None, 

4936 **options, 

4937 ) -> ResponseT: 

4938 pieces: list[EncodableT] = [command] 

4939 if dest is not None: 

4940 pieces.append(dest) 

4941 pieces.append(len(keys)) 

4942 if isinstance(keys, dict): 

4943 keys, weights = keys.keys(), keys.values() 

4944 else: 

4945 weights = None 

4946 pieces.extend(keys) 

4947 if weights: 

4948 pieces.append(b"WEIGHTS") 

4949 pieces.extend(weights) 

4950 if aggregate: 

4951 if aggregate.upper() in ["SUM", "MIN", "MAX"]: 

4952 pieces.append(b"AGGREGATE") 

4953 pieces.append(aggregate) 

4954 else: 

4955 raise DataError("aggregate can be sum, min or max.") 

4956 if options.get("withscores", False): 

4957 pieces.append(b"WITHSCORES") 

4958 options["keys"] = keys 

4959 return self.execute_command(*pieces, **options) 

4960 

4961 

4962AsyncSortedSetCommands = SortedSetCommands 

4963 

4964 

4965class HyperlogCommands(CommandsProtocol): 

4966 """ 

4967 Redis commands of HyperLogLogs data type. 

4968 see: https://redis.io/topics/data-types-intro#hyperloglogs 

4969 """ 

4970 

4971 def pfadd(self, name: KeyT, *values: FieldT) -> ResponseT: 

4972 """ 

4973 Adds the specified elements to the specified HyperLogLog. 

4974 

4975 For more information, see https://redis.io/commands/pfadd 

4976 """ 

4977 return self.execute_command("PFADD", name, *values) 

4978 

4979 def pfcount(self, *sources: KeyT) -> ResponseT: 

4980 """ 

4981 Return the approximated cardinality of 

4982 the set observed by the HyperLogLog at key(s). 

4983 

4984 For more information, see https://redis.io/commands/pfcount 

4985 """ 

4986 return self.execute_command("PFCOUNT", *sources) 

4987 

4988 def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT: 

4989 """ 

4990 Merge N different HyperLogLogs into a single one. 

4991 

4992 For more information, see https://redis.io/commands/pfmerge 

4993 """ 

4994 return self.execute_command("PFMERGE", dest, *sources) 

4995 

4996 

4997AsyncHyperlogCommands = HyperlogCommands 

4998 

4999 

5000class HashDataPersistOptions(Enum): 

5001 # set the value for each provided key to each 

5002 # provided value only if all do not already exist. 

5003 FNX = "FNX" 

5004 

5005 # set the value for each provided key to each 

5006 # provided value only if all already exist. 

5007 FXX = "FXX" 

5008 

5009 

5010class HashCommands(CommandsProtocol): 

5011 """ 

5012 Redis commands for Hash data type. 

5013 see: https://redis.io/topics/data-types-intro#redis-hashes 

5014 """ 

5015 

5016 def hdel(self, name: str, *keys: str) -> Union[Awaitable[int], int]: 

5017 """ 

5018 Delete ``keys`` from hash ``name`` 

5019 

5020 For more information, see https://redis.io/commands/hdel 

5021 """ 

5022 return self.execute_command("HDEL", name, *keys) 

5023 

5024 def hexists(self, name: str, key: str) -> Union[Awaitable[bool], bool]: 

5025 """ 

5026 Returns a boolean indicating if ``key`` exists within hash ``name`` 

5027 

5028 For more information, see https://redis.io/commands/hexists 

5029 """ 

5030 return self.execute_command("HEXISTS", name, key, keys=[name]) 

5031 

5032 def hget( 

5033 self, name: str, key: str 

5034 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

5035 """ 

5036 Return the value of ``key`` within the hash ``name`` 

5037 

5038 For more information, see https://redis.io/commands/hget 

5039 """ 

5040 return self.execute_command("HGET", name, key, keys=[name]) 

5041 

5042 def hgetall(self, name: str) -> Union[Awaitable[dict], dict]: 

5043 """ 

5044 Return a Python dict of the hash's name/value pairs 

5045 

5046 For more information, see https://redis.io/commands/hgetall 

5047 """ 

5048 return self.execute_command("HGETALL", name, keys=[name]) 

5049 

5050 def hgetdel( 

5051 self, name: str, *keys: str 

5052 ) -> Union[ 

5053 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]] 

5054 ]: 

5055 """ 

5056 Return the value of ``key`` within the hash ``name`` and 

5057 delete the field in the hash. 

5058 This command is similar to HGET, except for the fact that it also deletes 

5059 the key on success from the hash with the provided ```name```. 

5060 

5061 Available since Redis 8.0 

5062 For more information, see https://redis.io/commands/hgetdel 

5063 """ 

5064 if len(keys) == 0: 

5065 raise DataError("'hgetdel' should have at least one key provided") 

5066 

5067 return self.execute_command("HGETDEL", name, "FIELDS", len(keys), *keys) 

5068 

5069 def hgetex( 

5070 self, 

5071 name: KeyT, 

5072 *keys: str, 

5073 ex: Optional[ExpiryT] = None, 

5074 px: Optional[ExpiryT] = None, 

5075 exat: Optional[AbsExpiryT] = None, 

5076 pxat: Optional[AbsExpiryT] = None, 

5077 persist: bool = False, 

5078 ) -> Union[ 

5079 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]] 

5080 ]: 

5081 """ 

5082 Return the values of ``key`` and ``keys`` within the hash ``name`` 

5083 and optionally set their expiration. 

5084 

5085 ``ex`` sets an expire flag on ``kyes`` for ``ex`` seconds. 

5086 

5087 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds. 

5088 

5089 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds, 

5090 specified in unix time. 

5091 

5092 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds, 

5093 specified in unix time. 

5094 

5095 ``persist`` remove the time to live associated with the ``keys``. 

5096 

5097 Available since Redis 8.0 

5098 For more information, see https://redis.io/commands/hgetex 

5099 """ 

5100 if not keys: 

5101 raise DataError("'hgetex' should have at least one key provided") 

5102 

5103 opset = {ex, px, exat, pxat} 

5104 if len(opset) > 2 or len(opset) > 1 and persist: 

5105 raise DataError( 

5106 "``ex``, ``px``, ``exat``, ``pxat``, " 

5107 "and ``persist`` are mutually exclusive." 

5108 ) 

5109 

5110 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat) 

5111 

5112 if persist: 

5113 exp_options.append("PERSIST") 

5114 

5115 return self.execute_command( 

5116 "HGETEX", 

5117 name, 

5118 *exp_options, 

5119 "FIELDS", 

5120 len(keys), 

5121 *keys, 

5122 ) 

5123 

5124 def hincrby( 

5125 self, name: str, key: str, amount: int = 1 

5126 ) -> Union[Awaitable[int], int]: 

5127 """ 

5128 Increment the value of ``key`` in hash ``name`` by ``amount`` 

5129 

5130 For more information, see https://redis.io/commands/hincrby 

5131 """ 

5132 return self.execute_command("HINCRBY", name, key, amount) 

5133 

5134 def hincrbyfloat( 

5135 self, name: str, key: str, amount: float = 1.0 

5136 ) -> Union[Awaitable[float], float]: 

5137 """ 

5138 Increment the value of ``key`` in hash ``name`` by floating ``amount`` 

5139 

5140 For more information, see https://redis.io/commands/hincrbyfloat 

5141 """ 

5142 return self.execute_command("HINCRBYFLOAT", name, key, amount) 

5143 

5144 def hkeys(self, name: str) -> Union[Awaitable[List], List]: 

5145 """ 

5146 Return the list of keys within hash ``name`` 

5147 

5148 For more information, see https://redis.io/commands/hkeys 

5149 """ 

5150 return self.execute_command("HKEYS", name, keys=[name]) 

5151 

5152 def hlen(self, name: str) -> Union[Awaitable[int], int]: 

5153 """ 

5154 Return the number of elements in hash ``name`` 

5155 

5156 For more information, see https://redis.io/commands/hlen 

5157 """ 

5158 return self.execute_command("HLEN", name, keys=[name]) 

5159 

5160 def hset( 

5161 self, 

5162 name: str, 

5163 key: Optional[str] = None, 

5164 value: Optional[str] = None, 

5165 mapping: Optional[dict] = None, 

5166 items: Optional[list] = None, 

5167 ) -> Union[Awaitable[int], int]: 

5168 """ 

5169 Set ``key`` to ``value`` within hash ``name``, 

5170 ``mapping`` accepts a dict of key/value pairs that will be 

5171 added to hash ``name``. 

5172 ``items`` accepts a list of key/value pairs that will be 

5173 added to hash ``name``. 

5174 Returns the number of fields that were added. 

5175 

5176 For more information, see https://redis.io/commands/hset 

5177 """ 

5178 

5179 if key is None and not mapping and not items: 

5180 raise DataError("'hset' with no key value pairs") 

5181 

5182 pieces = [] 

5183 if items: 

5184 pieces.extend(items) 

5185 if key is not None: 

5186 pieces.extend((key, value)) 

5187 if mapping: 

5188 for pair in mapping.items(): 

5189 pieces.extend(pair) 

5190 

5191 return self.execute_command("HSET", name, *pieces) 

5192 

5193 def hsetex( 

5194 self, 

5195 name: str, 

5196 key: Optional[str] = None, 

5197 value: Optional[str] = None, 

5198 mapping: Optional[dict] = None, 

5199 items: Optional[list] = None, 

5200 ex: Optional[ExpiryT] = None, 

5201 px: Optional[ExpiryT] = None, 

5202 exat: Optional[AbsExpiryT] = None, 

5203 pxat: Optional[AbsExpiryT] = None, 

5204 data_persist_option: Optional[HashDataPersistOptions] = None, 

5205 keepttl: bool = False, 

5206 ) -> Union[Awaitable[int], int]: 

5207 """ 

5208 Set ``key`` to ``value`` within hash ``name`` 

5209 

5210 ``mapping`` accepts a dict of key/value pairs that will be 

5211 added to hash ``name``. 

5212 

5213 ``items`` accepts a list of key/value pairs that will be 

5214 added to hash ``name``. 

5215 

5216 ``ex`` sets an expire flag on ``keys`` for ``ex`` seconds. 

5217 

5218 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds. 

5219 

5220 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds, 

5221 specified in unix time. 

5222 

5223 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds, 

5224 specified in unix time. 

5225 

5226 ``data_persist_option`` can be set to ``FNX`` or ``FXX`` to control the 

5227 behavior of the command. 

5228 ``FNX`` will set the value for each provided key to each 

5229 provided value only if all do not already exist. 

5230 ``FXX`` will set the value for each provided key to each 

5231 provided value only if all already exist. 

5232 

5233 ``keepttl`` if True, retain the time to live associated with the keys. 

5234 

5235 Returns the number of fields that were added. 

5236 

5237 Available since Redis 8.0 

5238 For more information, see https://redis.io/commands/hsetex 

5239 """ 

5240 if key is None and not mapping and not items: 

5241 raise DataError("'hsetex' with no key value pairs") 

5242 

5243 if items and len(items) % 2 != 0: 

5244 raise DataError( 

5245 "'hsetex' with odd number of items. " 

5246 "'items' must contain a list of key/value pairs." 

5247 ) 

5248 

5249 opset = {ex, px, exat, pxat} 

5250 if len(opset) > 2 or len(opset) > 1 and keepttl: 

5251 raise DataError( 

5252 "``ex``, ``px``, ``exat``, ``pxat``, " 

5253 "and ``keepttl`` are mutually exclusive." 

5254 ) 

5255 

5256 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat) 

5257 if data_persist_option: 

5258 exp_options.append(data_persist_option.value) 

5259 

5260 if keepttl: 

5261 exp_options.append("KEEPTTL") 

5262 

5263 pieces = [] 

5264 if items: 

5265 pieces.extend(items) 

5266 if key is not None: 

5267 pieces.extend((key, value)) 

5268 if mapping: 

5269 for pair in mapping.items(): 

5270 pieces.extend(pair) 

5271 

5272 return self.execute_command( 

5273 "HSETEX", name, *exp_options, "FIELDS", int(len(pieces) / 2), *pieces 

5274 ) 

5275 

5276 def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool]: 

5277 """ 

5278 Set ``key`` to ``value`` within hash ``name`` if ``key`` does not 

5279 exist. Returns 1 if HSETNX created a field, otherwise 0. 

5280 

5281 For more information, see https://redis.io/commands/hsetnx 

5282 """ 

5283 return self.execute_command("HSETNX", name, key, value) 

5284 

5285 @deprecated_function( 

5286 version="4.0.0", 

5287 reason="Use 'hset' instead.", 

5288 name="hmset", 

5289 ) 

5290 def hmset(self, name: str, mapping: dict) -> Union[Awaitable[str], str]: 

5291 """ 

5292 Set key to value within hash ``name`` for each corresponding 

5293 key and value from the ``mapping`` dict. 

5294 

5295 For more information, see https://redis.io/commands/hmset 

5296 """ 

5297 if not mapping: 

5298 raise DataError("'hmset' with 'mapping' of length 0") 

5299 items = [] 

5300 for pair in mapping.items(): 

5301 items.extend(pair) 

5302 return self.execute_command("HMSET", name, *items) 

5303 

5304 def hmget(self, name: str, keys: List, *args: List) -> Union[Awaitable[List], List]: 

5305 """ 

5306 Returns a list of values ordered identically to ``keys`` 

5307 

5308 For more information, see https://redis.io/commands/hmget 

5309 """ 

5310 args = list_or_args(keys, args) 

5311 return self.execute_command("HMGET", name, *args, keys=[name]) 

5312 

5313 def hvals(self, name: str) -> Union[Awaitable[List], List]: 

5314 """ 

5315 Return the list of values within hash ``name`` 

5316 

5317 For more information, see https://redis.io/commands/hvals 

5318 """ 

5319 return self.execute_command("HVALS", name, keys=[name]) 

5320 

5321 def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]: 

5322 """ 

5323 Return the number of bytes stored in the value of ``key`` 

5324 within hash ``name`` 

5325 

5326 For more information, see https://redis.io/commands/hstrlen 

5327 """ 

5328 return self.execute_command("HSTRLEN", name, key, keys=[name]) 

5329 

5330 def hexpire( 

5331 self, 

5332 name: KeyT, 

5333 seconds: ExpiryT, 

5334 *fields: str, 

5335 nx: bool = False, 

5336 xx: bool = False, 

5337 gt: bool = False, 

5338 lt: bool = False, 

5339 ) -> ResponseT: 

5340 """ 

5341 Sets or updates the expiration time for fields within a hash key, using relative 

5342 time in seconds. 

5343 

5344 If a field already has an expiration time, the behavior of the update can be 

5345 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5346 

5347 The return value provides detailed information about the outcome for each field. 

5348 

5349 For more information, see https://redis.io/commands/hexpire 

5350 

5351 Args: 

5352 name: The name of the hash key. 

5353 seconds: Expiration time in seconds, relative. Can be an integer, or a 

5354 Python `timedelta` object. 

5355 fields: List of fields within the hash to apply the expiration time to. 

5356 nx: Set expiry only when the field has no expiry. 

5357 xx: Set expiry only when the field has an existing expiry. 

5358 gt: Set expiry only when the new expiry is greater than the current one. 

5359 lt: Set expiry only when the new expiry is less than the current one. 

5360 

5361 Returns: 

5362 Returns a list which contains for each field in the request: 

5363 - `-2` if the field does not exist, or if the key does not exist. 

5364 - `0` if the specified NX | XX | GT | LT condition was not met. 

5365 - `1` if the expiration time was set or updated. 

5366 - `2` if the field was deleted because the specified expiration time is 

5367 in the past. 

5368 """ 

5369 conditions = [nx, xx, gt, lt] 

5370 if sum(conditions) > 1: 

5371 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5372 

5373 if isinstance(seconds, datetime.timedelta): 

5374 seconds = int(seconds.total_seconds()) 

5375 

5376 options = [] 

5377 if nx: 

5378 options.append("NX") 

5379 if xx: 

5380 options.append("XX") 

5381 if gt: 

5382 options.append("GT") 

5383 if lt: 

5384 options.append("LT") 

5385 

5386 return self.execute_command( 

5387 "HEXPIRE", name, seconds, *options, "FIELDS", len(fields), *fields 

5388 ) 

5389 

5390 def hpexpire( 

5391 self, 

5392 name: KeyT, 

5393 milliseconds: ExpiryT, 

5394 *fields: str, 

5395 nx: bool = False, 

5396 xx: bool = False, 

5397 gt: bool = False, 

5398 lt: bool = False, 

5399 ) -> ResponseT: 

5400 """ 

5401 Sets or updates the expiration time for fields within a hash key, using relative 

5402 time in milliseconds. 

5403 

5404 If a field already has an expiration time, the behavior of the update can be 

5405 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5406 

5407 The return value provides detailed information about the outcome for each field. 

5408 

5409 For more information, see https://redis.io/commands/hpexpire 

5410 

5411 Args: 

5412 name: The name of the hash key. 

5413 milliseconds: Expiration time in milliseconds, relative. Can be an integer, 

5414 or a Python `timedelta` object. 

5415 fields: List of fields within the hash to apply the expiration time to. 

5416 nx: Set expiry only when the field has no expiry. 

5417 xx: Set expiry only when the field has an existing expiry. 

5418 gt: Set expiry only when the new expiry is greater than the current one. 

5419 lt: Set expiry only when the new expiry is less than the current one. 

5420 

5421 Returns: 

5422 Returns a list which contains for each field in the request: 

5423 - `-2` if the field does not exist, or if the key does not exist. 

5424 - `0` if the specified NX | XX | GT | LT condition was not met. 

5425 - `1` if the expiration time was set or updated. 

5426 - `2` if the field was deleted because the specified expiration time is 

5427 in the past. 

5428 """ 

5429 conditions = [nx, xx, gt, lt] 

5430 if sum(conditions) > 1: 

5431 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5432 

5433 if isinstance(milliseconds, datetime.timedelta): 

5434 milliseconds = int(milliseconds.total_seconds() * 1000) 

5435 

5436 options = [] 

5437 if nx: 

5438 options.append("NX") 

5439 if xx: 

5440 options.append("XX") 

5441 if gt: 

5442 options.append("GT") 

5443 if lt: 

5444 options.append("LT") 

5445 

5446 return self.execute_command( 

5447 "HPEXPIRE", name, milliseconds, *options, "FIELDS", len(fields), *fields 

5448 ) 

5449 

5450 def hexpireat( 

5451 self, 

5452 name: KeyT, 

5453 unix_time_seconds: AbsExpiryT, 

5454 *fields: str, 

5455 nx: bool = False, 

5456 xx: bool = False, 

5457 gt: bool = False, 

5458 lt: bool = False, 

5459 ) -> ResponseT: 

5460 """ 

5461 Sets or updates the expiration time for fields within a hash key, using an 

5462 absolute Unix timestamp in seconds. 

5463 

5464 If a field already has an expiration time, the behavior of the update can be 

5465 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5466 

5467 The return value provides detailed information about the outcome for each field. 

5468 

5469 For more information, see https://redis.io/commands/hexpireat 

5470 

5471 Args: 

5472 name: The name of the hash key. 

5473 unix_time_seconds: Expiration time as Unix timestamp in seconds. Can be an 

5474 integer or a Python `datetime` object. 

5475 fields: List of fields within the hash to apply the expiration time to. 

5476 nx: Set expiry only when the field has no expiry. 

5477 xx: Set expiry only when the field has an existing expiration time. 

5478 gt: Set expiry only when the new expiry is greater than the current one. 

5479 lt: Set expiry only when the new expiry is less than the current one. 

5480 

5481 Returns: 

5482 Returns a list which contains for each field in the request: 

5483 - `-2` if the field does not exist, or if the key does not exist. 

5484 - `0` if the specified NX | XX | GT | LT condition was not met. 

5485 - `1` if the expiration time was set or updated. 

5486 - `2` if the field was deleted because the specified expiration time is 

5487 in the past. 

5488 """ 

5489 conditions = [nx, xx, gt, lt] 

5490 if sum(conditions) > 1: 

5491 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5492 

5493 if isinstance(unix_time_seconds, datetime.datetime): 

5494 unix_time_seconds = int(unix_time_seconds.timestamp()) 

5495 

5496 options = [] 

5497 if nx: 

5498 options.append("NX") 

5499 if xx: 

5500 options.append("XX") 

5501 if gt: 

5502 options.append("GT") 

5503 if lt: 

5504 options.append("LT") 

5505 

5506 return self.execute_command( 

5507 "HEXPIREAT", 

5508 name, 

5509 unix_time_seconds, 

5510 *options, 

5511 "FIELDS", 

5512 len(fields), 

5513 *fields, 

5514 ) 

5515 

5516 def hpexpireat( 

5517 self, 

5518 name: KeyT, 

5519 unix_time_milliseconds: AbsExpiryT, 

5520 *fields: str, 

5521 nx: bool = False, 

5522 xx: bool = False, 

5523 gt: bool = False, 

5524 lt: bool = False, 

5525 ) -> ResponseT: 

5526 """ 

5527 Sets or updates the expiration time for fields within a hash key, using an 

5528 absolute Unix timestamp in milliseconds. 

5529 

5530 If a field already has an expiration time, the behavior of the update can be 

5531 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5532 

5533 The return value provides detailed information about the outcome for each field. 

5534 

5535 For more information, see https://redis.io/commands/hpexpireat 

5536 

5537 Args: 

5538 name: The name of the hash key. 

5539 unix_time_milliseconds: Expiration time as Unix timestamp in milliseconds. 

5540 Can be an integer or a Python `datetime` object. 

5541 fields: List of fields within the hash to apply the expiry. 

5542 nx: Set expiry only when the field has no expiry. 

5543 xx: Set expiry only when the field has an existing expiry. 

5544 gt: Set expiry only when the new expiry is greater than the current one. 

5545 lt: Set expiry only when the new expiry is less than the current one. 

5546 

5547 Returns: 

5548 Returns a list which contains for each field in the request: 

5549 - `-2` if the field does not exist, or if the key does not exist. 

5550 - `0` if the specified NX | XX | GT | LT condition was not met. 

5551 - `1` if the expiration time was set or updated. 

5552 - `2` if the field was deleted because the specified expiration time is 

5553 in the past. 

5554 """ 

5555 conditions = [nx, xx, gt, lt] 

5556 if sum(conditions) > 1: 

5557 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5558 

5559 if isinstance(unix_time_milliseconds, datetime.datetime): 

5560 unix_time_milliseconds = int(unix_time_milliseconds.timestamp() * 1000) 

5561 

5562 options = [] 

5563 if nx: 

5564 options.append("NX") 

5565 if xx: 

5566 options.append("XX") 

5567 if gt: 

5568 options.append("GT") 

5569 if lt: 

5570 options.append("LT") 

5571 

5572 return self.execute_command( 

5573 "HPEXPIREAT", 

5574 name, 

5575 unix_time_milliseconds, 

5576 *options, 

5577 "FIELDS", 

5578 len(fields), 

5579 *fields, 

5580 ) 

5581 

5582 def hpersist(self, name: KeyT, *fields: str) -> ResponseT: 

5583 """ 

5584 Removes the expiration time for each specified field in a hash. 

5585 

5586 For more information, see https://redis.io/commands/hpersist 

5587 

5588 Args: 

5589 name: The name of the hash key. 

5590 fields: A list of fields within the hash from which to remove the 

5591 expiration time. 

5592 

5593 Returns: 

5594 Returns a list which contains for each field in the request: 

5595 - `-2` if the field does not exist, or if the key does not exist. 

5596 - `-1` if the field exists but has no associated expiration time. 

5597 - `1` if the expiration time was successfully removed from the field. 

5598 """ 

5599 return self.execute_command("HPERSIST", name, "FIELDS", len(fields), *fields) 

5600 

5601 def hexpiretime(self, key: KeyT, *fields: str) -> ResponseT: 

5602 """ 

5603 Returns the expiration times of hash fields as Unix timestamps in seconds. 

5604 

5605 For more information, see https://redis.io/commands/hexpiretime 

5606 

5607 Args: 

5608 key: The hash key. 

5609 fields: A list of fields within the hash for which to get the expiration 

5610 time. 

5611 

5612 Returns: 

5613 Returns a list which contains for each field in the request: 

5614 - `-2` if the field does not exist, or if the key does not exist. 

5615 - `-1` if the field exists but has no associated expire time. 

5616 - A positive integer representing the expiration Unix timestamp in 

5617 seconds, if the field has an associated expiration time. 

5618 """ 

5619 return self.execute_command( 

5620 "HEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key] 

5621 ) 

5622 

5623 def hpexpiretime(self, key: KeyT, *fields: str) -> ResponseT: 

5624 """ 

5625 Returns the expiration times of hash fields as Unix timestamps in milliseconds. 

5626 

5627 For more information, see https://redis.io/commands/hpexpiretime 

5628 

5629 Args: 

5630 key: The hash key. 

5631 fields: A list of fields within the hash for which to get the expiration 

5632 time. 

5633 

5634 Returns: 

5635 Returns a list which contains for each field in the request: 

5636 - `-2` if the field does not exist, or if the key does not exist. 

5637 - `-1` if the field exists but has no associated expire time. 

5638 - A positive integer representing the expiration Unix timestamp in 

5639 milliseconds, if the field has an associated expiration time. 

5640 """ 

5641 return self.execute_command( 

5642 "HPEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key] 

5643 ) 

5644 

5645 def httl(self, key: KeyT, *fields: str) -> ResponseT: 

5646 """ 

5647 Returns the TTL (Time To Live) in seconds for each specified field within a hash 

5648 key. 

5649 

5650 For more information, see https://redis.io/commands/httl 

5651 

5652 Args: 

5653 key: The hash key. 

5654 fields: A list of fields within the hash for which to get the TTL. 

5655 

5656 Returns: 

5657 Returns a list which contains for each field in the request: 

5658 - `-2` if the field does not exist, or if the key does not exist. 

5659 - `-1` if the field exists but has no associated expire time. 

5660 - A positive integer representing the TTL in seconds if the field has 

5661 an associated expiration time. 

5662 """ 

5663 return self.execute_command( 

5664 "HTTL", key, "FIELDS", len(fields), *fields, keys=[key] 

5665 ) 

5666 

5667 def hpttl(self, key: KeyT, *fields: str) -> ResponseT: 

5668 """ 

5669 Returns the TTL (Time To Live) in milliseconds for each specified field within a 

5670 hash key. 

5671 

5672 For more information, see https://redis.io/commands/hpttl 

5673 

5674 Args: 

5675 key: The hash key. 

5676 fields: A list of fields within the hash for which to get the TTL. 

5677 

5678 Returns: 

5679 Returns a list which contains for each field in the request: 

5680 - `-2` if the field does not exist, or if the key does not exist. 

5681 - `-1` if the field exists but has no associated expire time. 

5682 - A positive integer representing the TTL in milliseconds if the field 

5683 has an associated expiration time. 

5684 """ 

5685 return self.execute_command( 

5686 "HPTTL", key, "FIELDS", len(fields), *fields, keys=[key] 

5687 ) 

5688 

5689 

5690AsyncHashCommands = HashCommands 

5691 

5692 

5693class Script: 

5694 """ 

5695 An executable Lua script object returned by ``register_script`` 

5696 """ 

5697 

5698 def __init__(self, registered_client: "redis.client.Redis", script: ScriptTextT): 

5699 self.registered_client = registered_client 

5700 self.script = script 

5701 # Precalculate and store the SHA1 hex digest of the script. 

5702 

5703 if isinstance(script, str): 

5704 # We need the encoding from the client in order to generate an 

5705 # accurate byte representation of the script 

5706 encoder = self.get_encoder() 

5707 script = encoder.encode(script) 

5708 self.sha = hashlib.sha1(script).hexdigest() 

5709 

5710 def __call__( 

5711 self, 

5712 keys: Union[Sequence[KeyT], None] = None, 

5713 args: Union[Iterable[EncodableT], None] = None, 

5714 client: Union["redis.client.Redis", None] = None, 

5715 ): 

5716 """Execute the script, passing any required ``args``""" 

5717 keys = keys or [] 

5718 args = args or [] 

5719 if client is None: 

5720 client = self.registered_client 

5721 args = tuple(keys) + tuple(args) 

5722 # make sure the Redis server knows about the script 

5723 from redis.client import Pipeline 

5724 

5725 if isinstance(client, Pipeline): 

5726 # Make sure the pipeline can register the script before executing. 

5727 client.scripts.add(self) 

5728 try: 

5729 return client.evalsha(self.sha, len(keys), *args) 

5730 except NoScriptError: 

5731 # Maybe the client is pointed to a different server than the client 

5732 # that created this instance? 

5733 # Overwrite the sha just in case there was a discrepancy. 

5734 self.sha = client.script_load(self.script) 

5735 return client.evalsha(self.sha, len(keys), *args) 

5736 

5737 def get_encoder(self): 

5738 """Get the encoder to encode string scripts into bytes.""" 

5739 try: 

5740 return self.registered_client.get_encoder() 

5741 except AttributeError: 

5742 # DEPRECATED 

5743 # In version <=4.1.2, this was the code we used to get the encoder. 

5744 # However, after 4.1.2 we added support for scripting in clustered 

5745 # redis. ClusteredRedis doesn't have a `.connection_pool` attribute 

5746 # so we changed the Script class to use 

5747 # `self.registered_client.get_encoder` (see above). 

5748 # However, that is technically a breaking change, as consumers who 

5749 # use Scripts directly might inject a `registered_client` that 

5750 # doesn't have a `.get_encoder` field. This try/except prevents us 

5751 # from breaking backward-compatibility. Ideally, it would be 

5752 # removed in the next major release. 

5753 return self.registered_client.connection_pool.get_encoder() 

5754 

5755 

5756class AsyncScript: 

5757 """ 

5758 An executable Lua script object returned by ``register_script`` 

5759 """ 

5760 

5761 def __init__( 

5762 self, 

5763 registered_client: "redis.asyncio.client.Redis", 

5764 script: ScriptTextT, 

5765 ): 

5766 self.registered_client = registered_client 

5767 self.script = script 

5768 # Precalculate and store the SHA1 hex digest of the script. 

5769 

5770 if isinstance(script, str): 

5771 # We need the encoding from the client in order to generate an 

5772 # accurate byte representation of the script 

5773 try: 

5774 encoder = registered_client.connection_pool.get_encoder() 

5775 except AttributeError: 

5776 # Cluster 

5777 encoder = registered_client.get_encoder() 

5778 script = encoder.encode(script) 

5779 self.sha = hashlib.sha1(script).hexdigest() 

5780 

5781 async def __call__( 

5782 self, 

5783 keys: Union[Sequence[KeyT], None] = None, 

5784 args: Union[Iterable[EncodableT], None] = None, 

5785 client: Union["redis.asyncio.client.Redis", None] = None, 

5786 ): 

5787 """Execute the script, passing any required ``args``""" 

5788 keys = keys or [] 

5789 args = args or [] 

5790 if client is None: 

5791 client = self.registered_client 

5792 args = tuple(keys) + tuple(args) 

5793 # make sure the Redis server knows about the script 

5794 from redis.asyncio.client import Pipeline 

5795 

5796 if isinstance(client, Pipeline): 

5797 # Make sure the pipeline can register the script before executing. 

5798 client.scripts.add(self) 

5799 try: 

5800 return await client.evalsha(self.sha, len(keys), *args) 

5801 except NoScriptError: 

5802 # Maybe the client is pointed to a different server than the client 

5803 # that created this instance? 

5804 # Overwrite the sha just in case there was a discrepancy. 

5805 self.sha = await client.script_load(self.script) 

5806 return await client.evalsha(self.sha, len(keys), *args) 

5807 

5808 

5809class PubSubCommands(CommandsProtocol): 

5810 """ 

5811 Redis PubSub commands. 

5812 see https://redis.io/topics/pubsub 

5813 """ 

5814 

5815 def publish(self, channel: ChannelT, message: EncodableT, **kwargs) -> ResponseT: 

5816 """ 

5817 Publish ``message`` on ``channel``. 

5818 Returns the number of subscribers the message was delivered to. 

5819 

5820 For more information, see https://redis.io/commands/publish 

5821 """ 

5822 return self.execute_command("PUBLISH", channel, message, **kwargs) 

5823 

5824 def spublish(self, shard_channel: ChannelT, message: EncodableT) -> ResponseT: 

5825 """ 

5826 Posts a message to the given shard channel. 

5827 Returns the number of clients that received the message 

5828 

5829 For more information, see https://redis.io/commands/spublish 

5830 """ 

5831 return self.execute_command("SPUBLISH", shard_channel, message) 

5832 

5833 def pubsub_channels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

5834 """ 

5835 Return a list of channels that have at least one subscriber 

5836 

5837 For more information, see https://redis.io/commands/pubsub-channels 

5838 """ 

5839 return self.execute_command("PUBSUB CHANNELS", pattern, **kwargs) 

5840 

5841 def pubsub_shardchannels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

5842 """ 

5843 Return a list of shard_channels that have at least one subscriber 

5844 

5845 For more information, see https://redis.io/commands/pubsub-shardchannels 

5846 """ 

5847 return self.execute_command("PUBSUB SHARDCHANNELS", pattern, **kwargs) 

5848 

5849 def pubsub_numpat(self, **kwargs) -> ResponseT: 

5850 """ 

5851 Returns the number of subscriptions to patterns 

5852 

5853 For more information, see https://redis.io/commands/pubsub-numpat 

5854 """ 

5855 return self.execute_command("PUBSUB NUMPAT", **kwargs) 

5856 

5857 def pubsub_numsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

5858 """ 

5859 Return a list of (channel, number of subscribers) tuples 

5860 for each channel given in ``*args`` 

5861 

5862 For more information, see https://redis.io/commands/pubsub-numsub 

5863 """ 

5864 return self.execute_command("PUBSUB NUMSUB", *args, **kwargs) 

5865 

5866 def pubsub_shardnumsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

5867 """ 

5868 Return a list of (shard_channel, number of subscribers) tuples 

5869 for each channel given in ``*args`` 

5870 

5871 For more information, see https://redis.io/commands/pubsub-shardnumsub 

5872 """ 

5873 return self.execute_command("PUBSUB SHARDNUMSUB", *args, **kwargs) 

5874 

5875 

5876AsyncPubSubCommands = PubSubCommands 

5877 

5878 

5879class ScriptCommands(CommandsProtocol): 

5880 """ 

5881 Redis Lua script commands. see: 

5882 https://redis.io/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/ 

5883 """ 

5884 

5885 def _eval( 

5886 self, 

5887 command: str, 

5888 script: str, 

5889 numkeys: int, 

5890 *keys_and_args: Union[KeyT, EncodableT], 

5891 ) -> Union[Awaitable[str], str]: 

5892 return self.execute_command(command, script, numkeys, *keys_and_args) 

5893 

5894 def eval( 

5895 self, script: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT] 

5896 ) -> Union[Awaitable[str], str]: 

5897 """ 

5898 Execute the Lua ``script``, specifying the ``numkeys`` the script 

5899 will touch and the key names and argument values in ``keys_and_args``. 

5900 Returns the result of the script. 

5901 

5902 In practice, use the object returned by ``register_script``. This 

5903 function exists purely for Redis API completion. 

5904 

5905 For more information, see https://redis.io/commands/eval 

5906 """ 

5907 return self._eval("EVAL", script, numkeys, *keys_and_args) 

5908 

5909 def eval_ro( 

5910 self, script: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT] 

5911 ) -> Union[Awaitable[str], str]: 

5912 """ 

5913 The read-only variant of the EVAL command 

5914 

5915 Execute the read-only Lua ``script`` specifying the ``numkeys`` the script 

5916 will touch and the key names and argument values in ``keys_and_args``. 

5917 Returns the result of the script. 

5918 

5919 For more information, see https://redis.io/commands/eval_ro 

5920 """ 

5921 return self._eval("EVAL_RO", script, numkeys, *keys_and_args) 

5922 

5923 def _evalsha( 

5924 self, 

5925 command: str, 

5926 sha: str, 

5927 numkeys: int, 

5928 *keys_and_args: Union[KeyT, EncodableT], 

5929 ) -> Union[Awaitable[str], str]: 

5930 return self.execute_command(command, sha, numkeys, *keys_and_args) 

5931 

5932 def evalsha( 

5933 self, sha: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT] 

5934 ) -> Union[Awaitable[str], str]: 

5935 """ 

5936 Use the ``sha`` to execute a Lua script already registered via EVAL 

5937 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

5938 key names and argument values in ``keys_and_args``. Returns the result 

5939 of the script. 

5940 

5941 In practice, use the object returned by ``register_script``. This 

5942 function exists purely for Redis API completion. 

5943 

5944 For more information, see https://redis.io/commands/evalsha 

5945 """ 

5946 return self._evalsha("EVALSHA", sha, numkeys, *keys_and_args) 

5947 

5948 def evalsha_ro( 

5949 self, sha: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT] 

5950 ) -> Union[Awaitable[str], str]: 

5951 """ 

5952 The read-only variant of the EVALSHA command 

5953 

5954 Use the ``sha`` to execute a read-only Lua script already registered via EVAL 

5955 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

5956 key names and argument values in ``keys_and_args``. Returns the result 

5957 of the script. 

5958 

5959 For more information, see https://redis.io/commands/evalsha_ro 

5960 """ 

5961 return self._evalsha("EVALSHA_RO", sha, numkeys, *keys_and_args) 

5962 

5963 def script_exists(self, *args: str) -> ResponseT: 

5964 """ 

5965 Check if a script exists in the script cache by specifying the SHAs of 

5966 each script as ``args``. Returns a list of boolean values indicating if 

5967 if each already script exists in the cache_data. 

5968 

5969 For more information, see https://redis.io/commands/script-exists 

5970 """ 

5971 return self.execute_command("SCRIPT EXISTS", *args) 

5972 

5973 def script_debug(self, *args) -> None: 

5974 raise NotImplementedError( 

5975 "SCRIPT DEBUG is intentionally not implemented in the client." 

5976 ) 

5977 

5978 def script_flush( 

5979 self, sync_type: Union[Literal["SYNC"], Literal["ASYNC"]] = None 

5980 ) -> ResponseT: 

5981 """Flush all scripts from the script cache_data. 

5982 

5983 ``sync_type`` is by default SYNC (synchronous) but it can also be 

5984 ASYNC. 

5985 

5986 For more information, see https://redis.io/commands/script-flush 

5987 """ 

5988 

5989 # Redis pre 6 had no sync_type. 

5990 if sync_type not in ["SYNC", "ASYNC", None]: 

5991 raise DataError( 

5992 "SCRIPT FLUSH defaults to SYNC in redis > 6.2, or " 

5993 "accepts SYNC/ASYNC. For older versions, " 

5994 "of redis leave as None." 

5995 ) 

5996 if sync_type is None: 

5997 pieces = [] 

5998 else: 

5999 pieces = [sync_type] 

6000 return self.execute_command("SCRIPT FLUSH", *pieces) 

6001 

6002 def script_kill(self) -> ResponseT: 

6003 """ 

6004 Kill the currently executing Lua script 

6005 

6006 For more information, see https://redis.io/commands/script-kill 

6007 """ 

6008 return self.execute_command("SCRIPT KILL") 

6009 

6010 def script_load(self, script: ScriptTextT) -> ResponseT: 

6011 """ 

6012 Load a Lua ``script`` into the script cache_data. Returns the SHA. 

6013 

6014 For more information, see https://redis.io/commands/script-load 

6015 """ 

6016 return self.execute_command("SCRIPT LOAD", script) 

6017 

6018 def register_script(self: "redis.client.Redis", script: ScriptTextT) -> Script: 

6019 """ 

6020 Register a Lua ``script`` specifying the ``keys`` it will touch. 

6021 Returns a Script object that is callable and hides the complexity of 

6022 deal with scripts, keys, and shas. This is the preferred way to work 

6023 with Lua scripts. 

6024 """ 

6025 return Script(self, script) 

6026 

6027 

6028class AsyncScriptCommands(ScriptCommands): 

6029 async def script_debug(self, *args) -> None: 

6030 return super().script_debug() 

6031 

6032 def register_script( 

6033 self: "redis.asyncio.client.Redis", 

6034 script: ScriptTextT, 

6035 ) -> AsyncScript: 

6036 """ 

6037 Register a Lua ``script`` specifying the ``keys`` it will touch. 

6038 Returns a Script object that is callable and hides the complexity of 

6039 deal with scripts, keys, and shas. This is the preferred way to work 

6040 with Lua scripts. 

6041 """ 

6042 return AsyncScript(self, script) 

6043 

6044 

6045class GeoCommands(CommandsProtocol): 

6046 """ 

6047 Redis Geospatial commands. 

6048 see: https://redis.com/redis-best-practices/indexing-patterns/geospatial/ 

6049 """ 

6050 

6051 def geoadd( 

6052 self, 

6053 name: KeyT, 

6054 values: Sequence[EncodableT], 

6055 nx: bool = False, 

6056 xx: bool = False, 

6057 ch: bool = False, 

6058 ) -> ResponseT: 

6059 """ 

6060 Add the specified geospatial items to the specified key identified 

6061 by the ``name`` argument. The Geospatial items are given as ordered 

6062 members of the ``values`` argument, each item or place is formed by 

6063 the triad longitude, latitude and name. 

6064 

6065 Note: You can use ZREM to remove elements. 

6066 

6067 ``nx`` forces ZADD to only create new elements and not to update 

6068 scores for elements that already exist. 

6069 

6070 ``xx`` forces ZADD to only update scores of elements that already 

6071 exist. New elements will not be added. 

6072 

6073 ``ch`` modifies the return value to be the numbers of elements changed. 

6074 Changed elements include new elements that were added and elements 

6075 whose scores changed. 

6076 

6077 For more information, see https://redis.io/commands/geoadd 

6078 """ 

6079 if nx and xx: 

6080 raise DataError("GEOADD allows either 'nx' or 'xx', not both") 

6081 if len(values) % 3 != 0: 

6082 raise DataError("GEOADD requires places with lon, lat and name values") 

6083 pieces = [name] 

6084 if nx: 

6085 pieces.append("NX") 

6086 if xx: 

6087 pieces.append("XX") 

6088 if ch: 

6089 pieces.append("CH") 

6090 pieces.extend(values) 

6091 return self.execute_command("GEOADD", *pieces) 

6092 

6093 def geodist( 

6094 self, name: KeyT, place1: FieldT, place2: FieldT, unit: Optional[str] = None 

6095 ) -> ResponseT: 

6096 """ 

6097 Return the distance between ``place1`` and ``place2`` members of the 

6098 ``name`` key. 

6099 The units must be one of the following : m, km mi, ft. By default 

6100 meters are used. 

6101 

6102 For more information, see https://redis.io/commands/geodist 

6103 """ 

6104 pieces: list[EncodableT] = [name, place1, place2] 

6105 if unit and unit not in ("m", "km", "mi", "ft"): 

6106 raise DataError("GEODIST invalid unit") 

6107 elif unit: 

6108 pieces.append(unit) 

6109 return self.execute_command("GEODIST", *pieces, keys=[name]) 

6110 

6111 def geohash(self, name: KeyT, *values: FieldT) -> ResponseT: 

6112 """ 

6113 Return the geo hash string for each item of ``values`` members of 

6114 the specified key identified by the ``name`` argument. 

6115 

6116 For more information, see https://redis.io/commands/geohash 

6117 """ 

6118 return self.execute_command("GEOHASH", name, *values, keys=[name]) 

6119 

6120 def geopos(self, name: KeyT, *values: FieldT) -> ResponseT: 

6121 """ 

6122 Return the positions of each item of ``values`` as members of 

6123 the specified key identified by the ``name`` argument. Each position 

6124 is represented by the pairs lon and lat. 

6125 

6126 For more information, see https://redis.io/commands/geopos 

6127 """ 

6128 return self.execute_command("GEOPOS", name, *values, keys=[name]) 

6129 

6130 def georadius( 

6131 self, 

6132 name: KeyT, 

6133 longitude: float, 

6134 latitude: float, 

6135 radius: float, 

6136 unit: Optional[str] = None, 

6137 withdist: bool = False, 

6138 withcoord: bool = False, 

6139 withhash: bool = False, 

6140 count: Optional[int] = None, 

6141 sort: Optional[str] = None, 

6142 store: Optional[KeyT] = None, 

6143 store_dist: Optional[KeyT] = None, 

6144 any: bool = False, 

6145 ) -> ResponseT: 

6146 """ 

6147 Return the members of the specified key identified by the 

6148 ``name`` argument which are within the borders of the area specified 

6149 with the ``latitude`` and ``longitude`` location and the maximum 

6150 distance from the center specified by the ``radius`` value. 

6151 

6152 The units must be one of the following : m, km mi, ft. By default 

6153 

6154 ``withdist`` indicates to return the distances of each place. 

6155 

6156 ``withcoord`` indicates to return the latitude and longitude of 

6157 each place. 

6158 

6159 ``withhash`` indicates to return the geohash string of each place. 

6160 

6161 ``count`` indicates to return the number of elements up to N. 

6162 

6163 ``sort`` indicates to return the places in a sorted way, ASC for 

6164 nearest to fairest and DESC for fairest to nearest. 

6165 

6166 ``store`` indicates to save the places names in a sorted set named 

6167 with a specific key, each element of the destination sorted set is 

6168 populated with the score got from the original geo sorted set. 

6169 

6170 ``store_dist`` indicates to save the places names in a sorted set 

6171 named with a specific key, instead of ``store`` the sorted set 

6172 destination score is set with the distance. 

6173 

6174 For more information, see https://redis.io/commands/georadius 

6175 """ 

6176 return self._georadiusgeneric( 

6177 "GEORADIUS", 

6178 name, 

6179 longitude, 

6180 latitude, 

6181 radius, 

6182 unit=unit, 

6183 withdist=withdist, 

6184 withcoord=withcoord, 

6185 withhash=withhash, 

6186 count=count, 

6187 sort=sort, 

6188 store=store, 

6189 store_dist=store_dist, 

6190 any=any, 

6191 ) 

6192 

6193 def georadiusbymember( 

6194 self, 

6195 name: KeyT, 

6196 member: FieldT, 

6197 radius: float, 

6198 unit: Optional[str] = None, 

6199 withdist: bool = False, 

6200 withcoord: bool = False, 

6201 withhash: bool = False, 

6202 count: Optional[int] = None, 

6203 sort: Optional[str] = None, 

6204 store: Union[KeyT, None] = None, 

6205 store_dist: Union[KeyT, None] = None, 

6206 any: bool = False, 

6207 ) -> ResponseT: 

6208 """ 

6209 This command is exactly like ``georadius`` with the sole difference 

6210 that instead of taking, as the center of the area to query, a longitude 

6211 and latitude value, it takes the name of a member already existing 

6212 inside the geospatial index represented by the sorted set. 

6213 

6214 For more information, see https://redis.io/commands/georadiusbymember 

6215 """ 

6216 return self._georadiusgeneric( 

6217 "GEORADIUSBYMEMBER", 

6218 name, 

6219 member, 

6220 radius, 

6221 unit=unit, 

6222 withdist=withdist, 

6223 withcoord=withcoord, 

6224 withhash=withhash, 

6225 count=count, 

6226 sort=sort, 

6227 store=store, 

6228 store_dist=store_dist, 

6229 any=any, 

6230 ) 

6231 

6232 def _georadiusgeneric( 

6233 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

6234 ) -> ResponseT: 

6235 pieces = list(args) 

6236 if kwargs["unit"] and kwargs["unit"] not in ("m", "km", "mi", "ft"): 

6237 raise DataError("GEORADIUS invalid unit") 

6238 elif kwargs["unit"]: 

6239 pieces.append(kwargs["unit"]) 

6240 else: 

6241 pieces.append("m") 

6242 

6243 if kwargs["any"] and kwargs["count"] is None: 

6244 raise DataError("``any`` can't be provided without ``count``") 

6245 

6246 for arg_name, byte_repr in ( 

6247 ("withdist", "WITHDIST"), 

6248 ("withcoord", "WITHCOORD"), 

6249 ("withhash", "WITHHASH"), 

6250 ): 

6251 if kwargs[arg_name]: 

6252 pieces.append(byte_repr) 

6253 

6254 if kwargs["count"] is not None: 

6255 pieces.extend(["COUNT", kwargs["count"]]) 

6256 if kwargs["any"]: 

6257 pieces.append("ANY") 

6258 

6259 if kwargs["sort"]: 

6260 if kwargs["sort"] == "ASC": 

6261 pieces.append("ASC") 

6262 elif kwargs["sort"] == "DESC": 

6263 pieces.append("DESC") 

6264 else: 

6265 raise DataError("GEORADIUS invalid sort") 

6266 

6267 if kwargs["store"] and kwargs["store_dist"]: 

6268 raise DataError("GEORADIUS store and store_dist cant be set together") 

6269 

6270 if kwargs["store"]: 

6271 pieces.extend([b"STORE", kwargs["store"]]) 

6272 

6273 if kwargs["store_dist"]: 

6274 pieces.extend([b"STOREDIST", kwargs["store_dist"]]) 

6275 

6276 return self.execute_command(command, *pieces, **kwargs) 

6277 

6278 def geosearch( 

6279 self, 

6280 name: KeyT, 

6281 member: Union[FieldT, None] = None, 

6282 longitude: Union[float, None] = None, 

6283 latitude: Union[float, None] = None, 

6284 unit: str = "m", 

6285 radius: Union[float, None] = None, 

6286 width: Union[float, None] = None, 

6287 height: Union[float, None] = None, 

6288 sort: Optional[str] = None, 

6289 count: Optional[int] = None, 

6290 any: bool = False, 

6291 withcoord: bool = False, 

6292 withdist: bool = False, 

6293 withhash: bool = False, 

6294 ) -> ResponseT: 

6295 """ 

6296 Return the members of specified key identified by the 

6297 ``name`` argument, which are within the borders of the 

6298 area specified by a given shape. This command extends the 

6299 GEORADIUS command, so in addition to searching within circular 

6300 areas, it supports searching within rectangular areas. 

6301 

6302 This command should be used in place of the deprecated 

6303 GEORADIUS and GEORADIUSBYMEMBER commands. 

6304 

6305 ``member`` Use the position of the given existing 

6306 member in the sorted set. Can't be given with ``longitude`` 

6307 and ``latitude``. 

6308 

6309 ``longitude`` and ``latitude`` Use the position given by 

6310 this coordinates. Can't be given with ``member`` 

6311 ``radius`` Similar to GEORADIUS, search inside circular 

6312 area according the given radius. Can't be given with 

6313 ``height`` and ``width``. 

6314 ``height`` and ``width`` Search inside an axis-aligned 

6315 rectangle, determined by the given height and width. 

6316 Can't be given with ``radius`` 

6317 

6318 ``unit`` must be one of the following : m, km, mi, ft. 

6319 `m` for meters (the default value), `km` for kilometers, 

6320 `mi` for miles and `ft` for feet. 

6321 

6322 ``sort`` indicates to return the places in a sorted way, 

6323 ASC for nearest to furthest and DESC for furthest to nearest. 

6324 

6325 ``count`` limit the results to the first count matching items. 

6326 

6327 ``any`` is set to True, the command will return as soon as 

6328 enough matches are found. Can't be provided without ``count`` 

6329 

6330 ``withdist`` indicates to return the distances of each place. 

6331 ``withcoord`` indicates to return the latitude and longitude of 

6332 each place. 

6333 

6334 ``withhash`` indicates to return the geohash string of each place. 

6335 

6336 For more information, see https://redis.io/commands/geosearch 

6337 """ 

6338 

6339 return self._geosearchgeneric( 

6340 "GEOSEARCH", 

6341 name, 

6342 member=member, 

6343 longitude=longitude, 

6344 latitude=latitude, 

6345 unit=unit, 

6346 radius=radius, 

6347 width=width, 

6348 height=height, 

6349 sort=sort, 

6350 count=count, 

6351 any=any, 

6352 withcoord=withcoord, 

6353 withdist=withdist, 

6354 withhash=withhash, 

6355 store=None, 

6356 store_dist=None, 

6357 ) 

6358 

6359 def geosearchstore( 

6360 self, 

6361 dest: KeyT, 

6362 name: KeyT, 

6363 member: Optional[FieldT] = None, 

6364 longitude: Optional[float] = None, 

6365 latitude: Optional[float] = None, 

6366 unit: str = "m", 

6367 radius: Optional[float] = None, 

6368 width: Optional[float] = None, 

6369 height: Optional[float] = None, 

6370 sort: Optional[str] = None, 

6371 count: Optional[int] = None, 

6372 any: bool = False, 

6373 storedist: bool = False, 

6374 ) -> ResponseT: 

6375 """ 

6376 This command is like GEOSEARCH, but stores the result in 

6377 ``dest``. By default, it stores the results in the destination 

6378 sorted set with their geospatial information. 

6379 if ``store_dist`` set to True, the command will stores the 

6380 items in a sorted set populated with their distance from the 

6381 center of the circle or box, as a floating-point number. 

6382 

6383 For more information, see https://redis.io/commands/geosearchstore 

6384 """ 

6385 return self._geosearchgeneric( 

6386 "GEOSEARCHSTORE", 

6387 dest, 

6388 name, 

6389 member=member, 

6390 longitude=longitude, 

6391 latitude=latitude, 

6392 unit=unit, 

6393 radius=radius, 

6394 width=width, 

6395 height=height, 

6396 sort=sort, 

6397 count=count, 

6398 any=any, 

6399 withcoord=None, 

6400 withdist=None, 

6401 withhash=None, 

6402 store=None, 

6403 store_dist=storedist, 

6404 ) 

6405 

6406 def _geosearchgeneric( 

6407 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

6408 ) -> ResponseT: 

6409 pieces = list(args) 

6410 

6411 # FROMMEMBER or FROMLONLAT 

6412 if kwargs["member"] is None: 

6413 if kwargs["longitude"] is None or kwargs["latitude"] is None: 

6414 raise DataError("GEOSEARCH must have member or longitude and latitude") 

6415 if kwargs["member"]: 

6416 if kwargs["longitude"] or kwargs["latitude"]: 

6417 raise DataError( 

6418 "GEOSEARCH member and longitude or latitude cant be set together" 

6419 ) 

6420 pieces.extend([b"FROMMEMBER", kwargs["member"]]) 

6421 if kwargs["longitude"] is not None and kwargs["latitude"] is not None: 

6422 pieces.extend([b"FROMLONLAT", kwargs["longitude"], kwargs["latitude"]]) 

6423 

6424 # BYRADIUS or BYBOX 

6425 if kwargs["radius"] is None: 

6426 if kwargs["width"] is None or kwargs["height"] is None: 

6427 raise DataError("GEOSEARCH must have radius or width and height") 

6428 if kwargs["unit"] is None: 

6429 raise DataError("GEOSEARCH must have unit") 

6430 if kwargs["unit"].lower() not in ("m", "km", "mi", "ft"): 

6431 raise DataError("GEOSEARCH invalid unit") 

6432 if kwargs["radius"]: 

6433 if kwargs["width"] or kwargs["height"]: 

6434 raise DataError( 

6435 "GEOSEARCH radius and width or height cant be set together" 

6436 ) 

6437 pieces.extend([b"BYRADIUS", kwargs["radius"], kwargs["unit"]]) 

6438 if kwargs["width"] and kwargs["height"]: 

6439 pieces.extend([b"BYBOX", kwargs["width"], kwargs["height"], kwargs["unit"]]) 

6440 

6441 # sort 

6442 if kwargs["sort"]: 

6443 if kwargs["sort"].upper() == "ASC": 

6444 pieces.append(b"ASC") 

6445 elif kwargs["sort"].upper() == "DESC": 

6446 pieces.append(b"DESC") 

6447 else: 

6448 raise DataError("GEOSEARCH invalid sort") 

6449 

6450 # count any 

6451 if kwargs["count"]: 

6452 pieces.extend([b"COUNT", kwargs["count"]]) 

6453 if kwargs["any"]: 

6454 pieces.append(b"ANY") 

6455 elif kwargs["any"]: 

6456 raise DataError("GEOSEARCH ``any`` can't be provided without count") 

6457 

6458 # other properties 

6459 for arg_name, byte_repr in ( 

6460 ("withdist", b"WITHDIST"), 

6461 ("withcoord", b"WITHCOORD"), 

6462 ("withhash", b"WITHHASH"), 

6463 ("store_dist", b"STOREDIST"), 

6464 ): 

6465 if kwargs[arg_name]: 

6466 pieces.append(byte_repr) 

6467 

6468 kwargs["keys"] = [args[0] if command == "GEOSEARCH" else args[1]] 

6469 

6470 return self.execute_command(command, *pieces, **kwargs) 

6471 

6472 

6473AsyncGeoCommands = GeoCommands 

6474 

6475 

6476class ModuleCommands(CommandsProtocol): 

6477 """ 

6478 Redis Module commands. 

6479 see: https://redis.io/topics/modules-intro 

6480 """ 

6481 

6482 def module_load(self, path, *args) -> ResponseT: 

6483 """ 

6484 Loads the module from ``path``. 

6485 Passes all ``*args`` to the module, during loading. 

6486 Raises ``ModuleError`` if a module is not found at ``path``. 

6487 

6488 For more information, see https://redis.io/commands/module-load 

6489 """ 

6490 return self.execute_command("MODULE LOAD", path, *args) 

6491 

6492 def module_loadex( 

6493 self, 

6494 path: str, 

6495 options: Optional[List[str]] = None, 

6496 args: Optional[List[str]] = None, 

6497 ) -> ResponseT: 

6498 """ 

6499 Loads a module from a dynamic library at runtime with configuration directives. 

6500 

6501 For more information, see https://redis.io/commands/module-loadex 

6502 """ 

6503 pieces = [] 

6504 if options is not None: 

6505 pieces.append("CONFIG") 

6506 pieces.extend(options) 

6507 if args is not None: 

6508 pieces.append("ARGS") 

6509 pieces.extend(args) 

6510 

6511 return self.execute_command("MODULE LOADEX", path, *pieces) 

6512 

6513 def module_unload(self, name) -> ResponseT: 

6514 """ 

6515 Unloads the module ``name``. 

6516 Raises ``ModuleError`` if ``name`` is not in loaded modules. 

6517 

6518 For more information, see https://redis.io/commands/module-unload 

6519 """ 

6520 return self.execute_command("MODULE UNLOAD", name) 

6521 

6522 def module_list(self) -> ResponseT: 

6523 """ 

6524 Returns a list of dictionaries containing the name and version of 

6525 all loaded modules. 

6526 

6527 For more information, see https://redis.io/commands/module-list 

6528 """ 

6529 return self.execute_command("MODULE LIST") 

6530 

6531 def command_info(self) -> None: 

6532 raise NotImplementedError( 

6533 "COMMAND INFO is intentionally not implemented in the client." 

6534 ) 

6535 

6536 def command_count(self) -> ResponseT: 

6537 return self.execute_command("COMMAND COUNT") 

6538 

6539 def command_getkeys(self, *args) -> ResponseT: 

6540 return self.execute_command("COMMAND GETKEYS", *args) 

6541 

6542 def command(self) -> ResponseT: 

6543 return self.execute_command("COMMAND") 

6544 

6545 

6546class AsyncModuleCommands(ModuleCommands): 

6547 async def command_info(self) -> None: 

6548 return super().command_info() 

6549 

6550 

6551class ClusterCommands(CommandsProtocol): 

6552 """ 

6553 Class for Redis Cluster commands 

6554 """ 

6555 

6556 def cluster(self, cluster_arg, *args, **kwargs) -> ResponseT: 

6557 return self.execute_command(f"CLUSTER {cluster_arg.upper()}", *args, **kwargs) 

6558 

6559 def readwrite(self, **kwargs) -> ResponseT: 

6560 """ 

6561 Disables read queries for a connection to a Redis Cluster slave node. 

6562 

6563 For more information, see https://redis.io/commands/readwrite 

6564 """ 

6565 return self.execute_command("READWRITE", **kwargs) 

6566 

6567 def readonly(self, **kwargs) -> ResponseT: 

6568 """ 

6569 Enables read queries for a connection to a Redis Cluster replica node. 

6570 

6571 For more information, see https://redis.io/commands/readonly 

6572 """ 

6573 return self.execute_command("READONLY", **kwargs) 

6574 

6575 

6576AsyncClusterCommands = ClusterCommands 

6577 

6578 

6579class FunctionCommands: 

6580 """ 

6581 Redis Function commands 

6582 """ 

6583 

6584 def function_load( 

6585 self, code: str, replace: Optional[bool] = False 

6586 ) -> Union[Awaitable[str], str]: 

6587 """ 

6588 Load a library to Redis. 

6589 :param code: the source code (must start with 

6590 Shebang statement that provides a metadata about the library) 

6591 :param replace: changes the behavior to overwrite the existing library 

6592 with the new contents. 

6593 Return the library name that was loaded. 

6594 

6595 For more information, see https://redis.io/commands/function-load 

6596 """ 

6597 pieces = ["REPLACE"] if replace else [] 

6598 pieces.append(code) 

6599 return self.execute_command("FUNCTION LOAD", *pieces) 

6600 

6601 def function_delete(self, library: str) -> Union[Awaitable[str], str]: 

6602 """ 

6603 Delete the library called ``library`` and all its functions. 

6604 

6605 For more information, see https://redis.io/commands/function-delete 

6606 """ 

6607 return self.execute_command("FUNCTION DELETE", library) 

6608 

6609 def function_flush(self, mode: str = "SYNC") -> Union[Awaitable[str], str]: 

6610 """ 

6611 Deletes all the libraries. 

6612 

6613 For more information, see https://redis.io/commands/function-flush 

6614 """ 

6615 return self.execute_command("FUNCTION FLUSH", mode) 

6616 

6617 def function_list( 

6618 self, library: Optional[str] = "*", withcode: Optional[bool] = False 

6619 ) -> Union[Awaitable[List], List]: 

6620 """ 

6621 Return information about the functions and libraries. 

6622 

6623 Args: 

6624 

6625 library: specify a pattern for matching library names 

6626 withcode: cause the server to include the libraries source implementation 

6627 in the reply 

6628 """ 

6629 args = ["LIBRARYNAME", library] 

6630 if withcode: 

6631 args.append("WITHCODE") 

6632 return self.execute_command("FUNCTION LIST", *args) 

6633 

6634 def _fcall( 

6635 self, command: str, function, numkeys: int, *keys_and_args: Any 

6636 ) -> Union[Awaitable[str], str]: 

6637 return self.execute_command(command, function, numkeys, *keys_and_args) 

6638 

6639 def fcall( 

6640 self, function, numkeys: int, *keys_and_args: Any 

6641 ) -> Union[Awaitable[str], str]: 

6642 """ 

6643 Invoke a function. 

6644 

6645 For more information, see https://redis.io/commands/fcall 

6646 """ 

6647 return self._fcall("FCALL", function, numkeys, *keys_and_args) 

6648 

6649 def fcall_ro( 

6650 self, function, numkeys: int, *keys_and_args: Any 

6651 ) -> Union[Awaitable[str], str]: 

6652 """ 

6653 This is a read-only variant of the FCALL command that cannot 

6654 execute commands that modify data. 

6655 

6656 For more information, see https://redis.io/commands/fcall_ro 

6657 """ 

6658 return self._fcall("FCALL_RO", function, numkeys, *keys_and_args) 

6659 

6660 def function_dump(self) -> Union[Awaitable[str], str]: 

6661 """ 

6662 Return the serialized payload of loaded libraries. 

6663 

6664 For more information, see https://redis.io/commands/function-dump 

6665 """ 

6666 from redis.client import NEVER_DECODE 

6667 

6668 options = {} 

6669 options[NEVER_DECODE] = [] 

6670 

6671 return self.execute_command("FUNCTION DUMP", **options) 

6672 

6673 def function_restore( 

6674 self, payload: str, policy: Optional[str] = "APPEND" 

6675 ) -> Union[Awaitable[str], str]: 

6676 """ 

6677 Restore libraries from the serialized ``payload``. 

6678 You can use the optional policy argument to provide a policy 

6679 for handling existing libraries. 

6680 

6681 For more information, see https://redis.io/commands/function-restore 

6682 """ 

6683 return self.execute_command("FUNCTION RESTORE", payload, policy) 

6684 

6685 def function_kill(self) -> Union[Awaitable[str], str]: 

6686 """ 

6687 Kill a function that is currently executing. 

6688 

6689 For more information, see https://redis.io/commands/function-kill 

6690 """ 

6691 return self.execute_command("FUNCTION KILL") 

6692 

6693 def function_stats(self) -> Union[Awaitable[List], List]: 

6694 """ 

6695 Return information about the function that's currently running 

6696 and information about the available execution engines. 

6697 

6698 For more information, see https://redis.io/commands/function-stats 

6699 """ 

6700 return self.execute_command("FUNCTION STATS") 

6701 

6702 

6703AsyncFunctionCommands = FunctionCommands 

6704 

6705 

6706class DataAccessCommands( 

6707 BasicKeyCommands, 

6708 HyperlogCommands, 

6709 HashCommands, 

6710 GeoCommands, 

6711 ListCommands, 

6712 ScanCommands, 

6713 SetCommands, 

6714 StreamCommands, 

6715 SortedSetCommands, 

6716): 

6717 """ 

6718 A class containing all of the implemented data access redis commands. 

6719 This class is to be used as a mixin for synchronous Redis clients. 

6720 """ 

6721 

6722 

6723class AsyncDataAccessCommands( 

6724 AsyncBasicKeyCommands, 

6725 AsyncHyperlogCommands, 

6726 AsyncHashCommands, 

6727 AsyncGeoCommands, 

6728 AsyncListCommands, 

6729 AsyncScanCommands, 

6730 AsyncSetCommands, 

6731 AsyncStreamCommands, 

6732 AsyncSortedSetCommands, 

6733): 

6734 """ 

6735 A class containing all of the implemented data access redis commands. 

6736 This class is to be used as a mixin for asynchronous Redis clients. 

6737 """ 

6738 

6739 

6740class CoreCommands( 

6741 ACLCommands, 

6742 ClusterCommands, 

6743 DataAccessCommands, 

6744 ManagementCommands, 

6745 ModuleCommands, 

6746 PubSubCommands, 

6747 ScriptCommands, 

6748 FunctionCommands, 

6749): 

6750 """ 

6751 A class containing all of the implemented redis commands. This class is 

6752 to be used as a mixin for synchronous Redis clients. 

6753 """ 

6754 

6755 

6756class AsyncCoreCommands( 

6757 AsyncACLCommands, 

6758 AsyncClusterCommands, 

6759 AsyncDataAccessCommands, 

6760 AsyncManagementCommands, 

6761 AsyncModuleCommands, 

6762 AsyncPubSubCommands, 

6763 AsyncScriptCommands, 

6764 AsyncFunctionCommands, 

6765): 

6766 """ 

6767 A class containing all of the implemented redis commands. This class is 

6768 to be used as a mixin for asynchronous Redis clients. 

6769 """