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

1942 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 DataPersistOptions(Enum): 

1563 # set the value for each provided key to each 

1564 # provided value only if all do not already exist. 

1565 NX = "NX" 

1566 

1567 # set the value for each provided key to each 

1568 # provided value only if all already exist. 

1569 XX = "XX" 

1570 

1571 

1572class BasicKeyCommands(CommandsProtocol): 

1573 """ 

1574 Redis basic key-based commands 

1575 """ 

1576 

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

1578 """ 

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

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

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

1582 

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

1584 """ 

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

1586 

1587 def bitcount( 

1588 self, 

1589 key: KeyT, 

1590 start: Optional[int] = None, 

1591 end: Optional[int] = None, 

1592 mode: Optional[str] = None, 

1593 ) -> ResponseT: 

1594 """ 

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

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

1597 

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

1599 """ 

1600 params = [key] 

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

1602 params.append(start) 

1603 params.append(end) 

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

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

1606 if mode is not None: 

1607 params.append(mode) 

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

1609 

1610 def bitfield( 

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

1612 key: KeyT, 

1613 default_overflow: Optional[str] = None, 

1614 ) -> BitFieldOperation: 

1615 """ 

1616 Return a BitFieldOperation instance to conveniently construct one or 

1617 more bitfield operations on ``key``. 

1618 

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

1620 """ 

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

1622 

1623 def bitfield_ro( 

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

1625 key: KeyT, 

1626 encoding: str, 

1627 offset: BitfieldOffsetT, 

1628 items: Optional[list] = None, 

1629 ) -> ResponseT: 

1630 """ 

1631 Return an array of the specified bitfield values 

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

1633 parameters and remaining values are result of corresponding 

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

1635 Read-only variant of the BITFIELD command. 

1636 

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

1638 """ 

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

1640 

1641 items = items or [] 

1642 for encoding, offset in items: 

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

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

1645 

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

1647 """ 

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

1649 store the result in ``dest``. 

1650 

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

1652 """ 

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

1654 

1655 def bitpos( 

1656 self, 

1657 key: KeyT, 

1658 bit: int, 

1659 start: Optional[int] = None, 

1660 end: Optional[int] = None, 

1661 mode: Optional[str] = None, 

1662 ) -> ResponseT: 

1663 """ 

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

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

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

1667 means to look at the first three bytes. 

1668 

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

1670 """ 

1671 if bit not in (0, 1): 

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

1673 params = [key, bit] 

1674 

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

1676 

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

1678 params.append(end) 

1679 elif start is None and end is not None: 

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

1681 

1682 if mode is not None: 

1683 params.append(mode) 

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

1685 

1686 def copy( 

1687 self, 

1688 source: str, 

1689 destination: str, 

1690 destination_db: Optional[str] = None, 

1691 replace: bool = False, 

1692 ) -> ResponseT: 

1693 """ 

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

1695 

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

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

1698 

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

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

1701 the ``destination`` key already exists. 

1702 

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

1704 """ 

1705 params = [source, destination] 

1706 if destination_db is not None: 

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

1708 if replace: 

1709 params.append("REPLACE") 

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

1711 

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

1713 """ 

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

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

1716 

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

1718 """ 

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

1720 

1721 decr = decrby 

1722 

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

1724 """ 

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

1726 """ 

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

1728 

1729 def __delitem__(self, name: KeyT): 

1730 self.delete(name) 

1731 

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

1733 """ 

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

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

1736 

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

1738 """ 

1739 from redis.client import NEVER_DECODE 

1740 

1741 options = {} 

1742 options[NEVER_DECODE] = [] 

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

1744 

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

1746 """ 

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

1748 

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

1750 """ 

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

1752 

1753 __contains__ = exists 

1754 

1755 def expire( 

1756 self, 

1757 name: KeyT, 

1758 time: ExpiryT, 

1759 nx: bool = False, 

1760 xx: bool = False, 

1761 gt: bool = False, 

1762 lt: bool = False, 

1763 ) -> ResponseT: 

1764 """ 

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

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

1767 object. 

1768 

1769 Valid options are: 

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

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

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

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

1774 

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

1776 """ 

1777 if isinstance(time, datetime.timedelta): 

1778 time = int(time.total_seconds()) 

1779 

1780 exp_option = list() 

1781 if nx: 

1782 exp_option.append("NX") 

1783 if xx: 

1784 exp_option.append("XX") 

1785 if gt: 

1786 exp_option.append("GT") 

1787 if lt: 

1788 exp_option.append("LT") 

1789 

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

1791 

1792 def expireat( 

1793 self, 

1794 name: KeyT, 

1795 when: AbsExpiryT, 

1796 nx: bool = False, 

1797 xx: bool = False, 

1798 gt: bool = False, 

1799 lt: bool = False, 

1800 ) -> ResponseT: 

1801 """ 

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

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

1804 datetime object. 

1805 

1806 Valid options are: 

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

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

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

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

1811 

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

1813 """ 

1814 if isinstance(when, datetime.datetime): 

1815 when = int(when.timestamp()) 

1816 

1817 exp_option = list() 

1818 if nx: 

1819 exp_option.append("NX") 

1820 if xx: 

1821 exp_option.append("XX") 

1822 if gt: 

1823 exp_option.append("GT") 

1824 if lt: 

1825 exp_option.append("LT") 

1826 

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

1828 

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

1830 """ 

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

1832 at which the given key will expire. 

1833 

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

1835 """ 

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

1837 

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

1839 """ 

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

1841 

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

1843 """ 

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

1845 

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

1847 """ 

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

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

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

1851 is a string). 

1852 

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

1854 """ 

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

1856 

1857 def getex( 

1858 self, 

1859 name: KeyT, 

1860 ex: Optional[ExpiryT] = None, 

1861 px: Optional[ExpiryT] = None, 

1862 exat: Optional[AbsExpiryT] = None, 

1863 pxat: Optional[AbsExpiryT] = None, 

1864 persist: bool = False, 

1865 ) -> ResponseT: 

1866 """ 

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

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

1869 additional options. All time parameters can be given as 

1870 datetime.timedelta or integers. 

1871 

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

1873 

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

1875 

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

1877 specified in unix time. 

1878 

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

1880 specified in unix time. 

1881 

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

1883 

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

1885 """ 

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

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

1888 raise DataError( 

1889 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

1891 ) 

1892 

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

1894 

1895 if persist: 

1896 exp_options.append("PERSIST") 

1897 

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

1899 

1900 def __getitem__(self, name: KeyT): 

1901 """ 

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

1903 doesn't exist. 

1904 """ 

1905 value = self.get(name) 

1906 if value is not None: 

1907 return value 

1908 raise KeyError(name) 

1909 

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

1911 """ 

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

1913 

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

1915 """ 

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

1917 

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

1919 """ 

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

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

1922 

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

1924 """ 

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

1926 

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

1928 """ 

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

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

1931 

1932 As per Redis 6.2, GETSET is considered deprecated. 

1933 Please use SET with GET parameter in new code. 

1934 

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

1936 """ 

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

1938 

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

1940 """ 

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

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

1943 

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

1945 """ 

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

1947 

1948 incr = incrby 

1949 

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

1951 """ 

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

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

1954 

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

1956 """ 

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

1958 

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

1960 """ 

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

1962 

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

1964 """ 

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

1966 

1967 def lmove( 

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

1969 ) -> ResponseT: 

1970 """ 

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

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

1973 Returns the element being popped and pushed. 

1974 

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

1976 """ 

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

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

1979 

1980 def blmove( 

1981 self, 

1982 first_list: str, 

1983 second_list: str, 

1984 timeout: int, 

1985 src: str = "LEFT", 

1986 dest: str = "RIGHT", 

1987 ) -> ResponseT: 

1988 """ 

1989 Blocking version of lmove. 

1990 

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

1992 """ 

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

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

1995 

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

1997 """ 

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

1999 

2000 ** Important ** When this method is used with Cluster clients, all keys 

2001 must be in the same hash slot, otherwise a RedisClusterException 

2002 will be raised. 

2003 

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

2005 """ 

2006 from redis.client import EMPTY_RESPONSE 

2007 

2008 args = list_or_args(keys, args) 

2009 options = {} 

2010 if not args: 

2011 options[EMPTY_RESPONSE] = [] 

2012 options["keys"] = args 

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

2014 

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

2016 """ 

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

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

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

2020 

2021 ** Important ** When this method is used with Cluster clients, all keys 

2022 must be in the same hash slot, otherwise a RedisClusterException 

2023 will be raised. 

2024 

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

2026 """ 

2027 items = [] 

2028 for pair in mapping.items(): 

2029 items.extend(pair) 

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

2031 

2032 def msetex( 

2033 self, 

2034 mapping: Mapping[AnyKeyT, EncodableT], 

2035 data_persist_option: Optional[DataPersistOptions] = None, 

2036 ex: Optional[ExpiryT] = None, 

2037 px: Optional[ExpiryT] = None, 

2038 exat: Optional[AbsExpiryT] = None, 

2039 pxat: Optional[AbsExpiryT] = None, 

2040 keepttl: bool = False, 

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

2042 """ 

2043 Sets key/values based on the provided ``mapping`` items. 

2044 

2045 ** Important ** When this method is used with Cluster clients, all keys 

2046 must be in the same hash slot, otherwise a RedisClusterException 

2047 will be raised. 

2048 

2049 ``mapping`` accepts a dict of key/value pairs that will be added to the database. 

2050 

2051 ``data_persist_option`` can be set to ``NX`` or ``XX`` to control the 

2052 behavior of the command. 

2053 ``NX`` will set the value for each provided key to each 

2054 provided value only if all do not already exist. 

2055 ``XX`` will set the value for each provided key to each 

2056 provided value only if all already exist. 

2057 

2058 ``ex`` sets an expire flag on the keys in ``mapping`` for ``ex`` seconds. 

2059 

2060 ``px`` sets an expire flag on the keys in ``mapping`` for ``px`` milliseconds. 

2061 

2062 ``exat`` sets an expire flag on the keys in ``mapping`` for ``exat`` seconds, 

2063 specified in unix time. 

2064 

2065 ``pxat`` sets an expire flag on the keys in ``mapping`` for ``pxat`` milliseconds, 

2066 specified in unix time. 

2067 

2068 ``keepttl`` if True, retain the time to live associated with the keys. 

2069 

2070 Returns the number of fields that were added. 

2071 

2072 Available since Redis 8.4 

2073 For more information, see https://redis.io/commands/msetex 

2074 """ 

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

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

2077 raise DataError( 

2078 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

2080 ) 

2081 

2082 exp_options: list[EncodableT] = [] 

2083 if data_persist_option: 

2084 exp_options.append(data_persist_option.value) 

2085 

2086 exp_options.extend(extract_expire_flags(ex, px, exat, pxat)) 

2087 

2088 if keepttl: 

2089 exp_options.append("KEEPTTL") 

2090 

2091 pieces = ["MSETEX", len(mapping)] 

2092 

2093 for pair in mapping.items(): 

2094 pieces.extend(pair) 

2095 

2096 return self.execute_command(*pieces, *exp_options) 

2097 

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

2099 """ 

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

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

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

2103 Returns a boolean indicating if the operation was successful. 

2104 

2105 ** Important ** When this method is used with Cluster clients, all keys 

2106 must be in the same hash slot, otherwise a RedisClusterException 

2107 will be raised. 

2108 

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

2110 """ 

2111 items = [] 

2112 for pair in mapping.items(): 

2113 items.extend(pair) 

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

2115 

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

2117 """ 

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

2119 

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

2121 """ 

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

2123 

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

2125 """ 

2126 Removes an expiration on ``name`` 

2127 

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

2129 """ 

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

2131 

2132 def pexpire( 

2133 self, 

2134 name: KeyT, 

2135 time: ExpiryT, 

2136 nx: bool = False, 

2137 xx: bool = False, 

2138 gt: bool = False, 

2139 lt: bool = False, 

2140 ) -> ResponseT: 

2141 """ 

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

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

2144 integer or a Python timedelta object. 

2145 

2146 Valid options are: 

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

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

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

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

2151 

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

2153 """ 

2154 if isinstance(time, datetime.timedelta): 

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

2156 

2157 exp_option = list() 

2158 if nx: 

2159 exp_option.append("NX") 

2160 if xx: 

2161 exp_option.append("XX") 

2162 if gt: 

2163 exp_option.append("GT") 

2164 if lt: 

2165 exp_option.append("LT") 

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

2167 

2168 def pexpireat( 

2169 self, 

2170 name: KeyT, 

2171 when: AbsExpiryT, 

2172 nx: bool = False, 

2173 xx: bool = False, 

2174 gt: bool = False, 

2175 lt: bool = False, 

2176 ) -> ResponseT: 

2177 """ 

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

2179 can be represented as an integer representing unix time in 

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

2181 

2182 Valid options are: 

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

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

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

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

2187 

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

2189 """ 

2190 if isinstance(when, datetime.datetime): 

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

2192 exp_option = list() 

2193 if nx: 

2194 exp_option.append("NX") 

2195 if xx: 

2196 exp_option.append("XX") 

2197 if gt: 

2198 exp_option.append("GT") 

2199 if lt: 

2200 exp_option.append("LT") 

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

2202 

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

2204 """ 

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

2206 at which the given key will expire. 

2207 

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

2209 """ 

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

2211 

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

2213 """ 

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

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

2216 timedelta object 

2217 

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

2219 """ 

2220 if isinstance(time_ms, datetime.timedelta): 

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

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

2223 

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

2225 """ 

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

2227 

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

2229 """ 

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

2231 

2232 def hrandfield( 

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

2234 ) -> ResponseT: 

2235 """ 

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

2237 

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

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

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

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

2242 specified count. 

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

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

2245 

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

2247 """ 

2248 params = [] 

2249 if count is not None: 

2250 params.append(count) 

2251 if withvalues: 

2252 params.append("WITHVALUES") 

2253 

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

2255 

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

2257 """ 

2258 Returns the name of a random key 

2259 

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

2261 """ 

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

2263 

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

2265 """ 

2266 Rename key ``src`` to ``dst`` 

2267 

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

2269 """ 

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

2271 

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

2273 """ 

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

2275 

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

2277 """ 

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

2279 

2280 def restore( 

2281 self, 

2282 name: KeyT, 

2283 ttl: float, 

2284 value: EncodableT, 

2285 replace: bool = False, 

2286 absttl: bool = False, 

2287 idletime: Optional[int] = None, 

2288 frequency: Optional[int] = None, 

2289 ) -> ResponseT: 

2290 """ 

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

2292 using DUMP. 

2293 

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

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

2296 

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

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

2299 greater). 

2300 

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

2302 key must be idle, prior to execution. 

2303 

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

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

2306 

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

2308 """ 

2309 params = [name, ttl, value] 

2310 if replace: 

2311 params.append("REPLACE") 

2312 if absttl: 

2313 params.append("ABSTTL") 

2314 if idletime is not None: 

2315 params.append("IDLETIME") 

2316 try: 

2317 params.append(int(idletime)) 

2318 except ValueError: 

2319 raise DataError("idletimemust be an integer") 

2320 

2321 if frequency is not None: 

2322 params.append("FREQ") 

2323 try: 

2324 params.append(int(frequency)) 

2325 except ValueError: 

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

2327 

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

2329 

2330 def set( 

2331 self, 

2332 name: KeyT, 

2333 value: EncodableT, 

2334 ex: Optional[ExpiryT] = None, 

2335 px: Optional[ExpiryT] = None, 

2336 nx: bool = False, 

2337 xx: bool = False, 

2338 keepttl: bool = False, 

2339 get: bool = False, 

2340 exat: Optional[AbsExpiryT] = None, 

2341 pxat: Optional[AbsExpiryT] = None, 

2342 ) -> ResponseT: 

2343 """ 

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

2345 

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

2347 

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

2349 

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

2351 if it does not exist. 

2352 

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

2354 if it already exists. 

2355 

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

2357 (Available since Redis 6.0) 

2358 

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

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

2361 (Available since Redis 6.2) 

2362 

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

2364 specified in unix time. 

2365 

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

2367 specified in unix time. 

2368 

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

2370 """ 

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

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

2373 raise DataError( 

2374 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

2376 ) 

2377 

2378 if nx and xx: 

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

2380 

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

2382 options = {} 

2383 

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

2385 

2386 if keepttl: 

2387 pieces.append("KEEPTTL") 

2388 

2389 if nx: 

2390 pieces.append("NX") 

2391 if xx: 

2392 pieces.append("XX") 

2393 

2394 if get: 

2395 pieces.append("GET") 

2396 options["get"] = True 

2397 

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

2399 

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

2401 self.set(name, value) 

2402 

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

2404 """ 

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

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

2407 

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

2409 """ 

2410 value = value and 1 or 0 

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

2412 

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

2414 """ 

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

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

2417 timedelta object. 

2418 

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

2420 """ 

2421 if isinstance(time, datetime.timedelta): 

2422 time = int(time.total_seconds()) 

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

2424 

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

2426 """ 

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

2428 

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

2430 """ 

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

2432 

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

2434 """ 

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

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

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

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

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

2440 of what's being injected. 

2441 

2442 Returns the length of the new string. 

2443 

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

2445 """ 

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

2447 

2448 def stralgo( 

2449 self, 

2450 algo: Literal["LCS"], 

2451 value1: KeyT, 

2452 value2: KeyT, 

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

2454 len: bool = False, 

2455 idx: bool = False, 

2456 minmatchlen: Optional[int] = None, 

2457 withmatchlen: bool = False, 

2458 **kwargs, 

2459 ) -> ResponseT: 

2460 """ 

2461 Implements complex algorithms that operate on strings. 

2462 Right now the only algorithm implemented is the LCS algorithm 

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

2464 implemented in the future. 

2465 

2466 ``algo`` Right now must be LCS 

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

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

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

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

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

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

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

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

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

2476 

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

2478 """ 

2479 # check validity 

2480 supported_algo = ["LCS"] 

2481 if algo not in supported_algo: 

2482 supported_algos_str = ", ".join(supported_algo) 

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

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

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

2486 if len and idx: 

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

2488 

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

2490 if len: 

2491 pieces.append(b"LEN") 

2492 if idx: 

2493 pieces.append(b"IDX") 

2494 try: 

2495 int(minmatchlen) 

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

2497 except TypeError: 

2498 pass 

2499 if withmatchlen: 

2500 pieces.append(b"WITHMATCHLEN") 

2501 

2502 return self.execute_command( 

2503 "STRALGO", 

2504 *pieces, 

2505 len=len, 

2506 idx=idx, 

2507 minmatchlen=minmatchlen, 

2508 withmatchlen=withmatchlen, 

2509 **kwargs, 

2510 ) 

2511 

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

2513 """ 

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

2515 

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

2517 """ 

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

2519 

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

2521 """ 

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

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

2524 """ 

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

2526 

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

2528 """ 

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

2530 if it does not exist. 

2531 

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

2533 """ 

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

2535 

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

2537 """ 

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

2539 

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

2541 """ 

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

2543 

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

2545 """ 

2546 Returns the type of key ``name`` 

2547 

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

2549 """ 

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

2551 

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

2553 """ 

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

2555 

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

2557 """ 

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

2559 

2560 def unwatch(self) -> None: 

2561 """ 

2562 Unwatches all previously watched keys for a transaction 

2563 

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

2565 """ 

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

2567 

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

2569 """ 

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

2571 

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

2573 """ 

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

2575 

2576 def lcs( 

2577 self, 

2578 key1: str, 

2579 key2: str, 

2580 len: Optional[bool] = False, 

2581 idx: Optional[bool] = False, 

2582 minmatchlen: Optional[int] = 0, 

2583 withmatchlen: Optional[bool] = False, 

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

2585 """ 

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

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

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

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

2590 the given ``minmatchlen``. 

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

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

2593 """ 

2594 pieces = [key1, key2] 

2595 if len: 

2596 pieces.append("LEN") 

2597 if idx: 

2598 pieces.append("IDX") 

2599 if minmatchlen != 0: 

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

2601 if withmatchlen: 

2602 pieces.append("WITHMATCHLEN") 

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

2604 

2605 

2606class AsyncBasicKeyCommands(BasicKeyCommands): 

2607 def __delitem__(self, name: KeyT): 

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

2609 

2610 def __contains__(self, name: KeyT): 

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

2612 

2613 def __getitem__(self, name: KeyT): 

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

2615 

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

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

2618 

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

2620 return super().watch(*names) 

2621 

2622 async def unwatch(self) -> None: 

2623 return super().unwatch() 

2624 

2625 

2626class ListCommands(CommandsProtocol): 

2627 """ 

2628 Redis commands for List data type. 

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

2630 """ 

2631 

2632 def blpop( 

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

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

2635 """ 

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

2637 named in the ``keys`` list. 

2638 

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

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

2641 of the lists. 

2642 

2643 If timeout is 0, then block indefinitely. 

2644 

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

2646 """ 

2647 if timeout is None: 

2648 timeout = 0 

2649 keys = list_or_args(keys, None) 

2650 keys.append(timeout) 

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

2652 

2653 def brpop( 

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

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

2656 """ 

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

2658 named in the ``keys`` list. 

2659 

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

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

2662 of the lists. 

2663 

2664 If timeout is 0, then block indefinitely. 

2665 

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

2667 """ 

2668 if timeout is None: 

2669 timeout = 0 

2670 keys = list_or_args(keys, None) 

2671 keys.append(timeout) 

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

2673 

2674 def brpoplpush( 

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

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

2677 """ 

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

2679 and then return it. 

2680 

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

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

2683 forever. 

2684 

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

2686 """ 

2687 if timeout is None: 

2688 timeout = 0 

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

2690 

2691 def blmpop( 

2692 self, 

2693 timeout: float, 

2694 numkeys: int, 

2695 *args: str, 

2696 direction: str, 

2697 count: Optional[int] = 1, 

2698 ) -> Optional[list]: 

2699 """ 

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

2701 of provided key names. 

2702 

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

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

2705 

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

2707 """ 

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

2709 

2710 return self.execute_command("BLMPOP", *cmd_args) 

2711 

2712 def lmpop( 

2713 self, 

2714 num_keys: int, 

2715 *args: str, 

2716 direction: str, 

2717 count: Optional[int] = 1, 

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

2719 """ 

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

2721 of args provided key names. 

2722 

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

2724 """ 

2725 cmd_args = [num_keys] + list(args) + [direction] 

2726 if count != 1: 

2727 cmd_args.extend(["COUNT", count]) 

2728 

2729 return self.execute_command("LMPOP", *cmd_args) 

2730 

2731 def lindex( 

2732 self, name: str, index: int 

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

2734 """ 

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

2736 

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

2738 end of the list 

2739 

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

2741 """ 

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

2743 

2744 def linsert( 

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

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

2747 """ 

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

2749 [``where``] ``refvalue`` 

2750 

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

2752 is not in the list. 

2753 

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

2755 """ 

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

2757 

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

2759 """ 

2760 Return the length of the list ``name`` 

2761 

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

2763 """ 

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

2765 

2766 def lpop( 

2767 self, 

2768 name: str, 

2769 count: Optional[int] = None, 

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

2771 """ 

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

2773 

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

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

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

2777 

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

2779 """ 

2780 if count is not None: 

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

2782 else: 

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

2784 

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

2786 """ 

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

2788 

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

2790 """ 

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

2792 

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

2794 """ 

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

2796 

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

2798 """ 

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

2800 

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

2802 """ 

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

2804 position ``start`` and ``end`` 

2805 

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

2807 Python slicing notation 

2808 

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

2810 """ 

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

2812 

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

2814 """ 

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

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

2817 

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

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

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

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

2822 

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

2824 """ 

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

2826 

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

2828 """ 

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

2830 

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

2832 """ 

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

2834 

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

2836 """ 

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

2838 between ``start`` and ``end`` 

2839 

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

2841 Python slicing notation 

2842 

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

2844 """ 

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

2846 

2847 def rpop( 

2848 self, 

2849 name: str, 

2850 count: Optional[int] = None, 

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

2852 """ 

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

2854 

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

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

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

2858 

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

2860 """ 

2861 if count is not None: 

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

2863 else: 

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

2865 

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

2867 """ 

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

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

2870 

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

2872 """ 

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

2874 

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

2876 """ 

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

2878 

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

2880 """ 

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

2882 

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

2884 """ 

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

2886 

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

2888 """ 

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

2890 

2891 def lpos( 

2892 self, 

2893 name: str, 

2894 value: str, 

2895 rank: Optional[int] = None, 

2896 count: Optional[int] = None, 

2897 maxlen: Optional[int] = None, 

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

2899 """ 

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

2901 

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

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

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

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

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

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

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

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

2910 

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

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

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

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

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

2916 

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

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

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

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

2921 

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

2923 """ 

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

2925 if rank is not None: 

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

2927 

2928 if count is not None: 

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

2930 

2931 if maxlen is not None: 

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

2933 

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

2935 

2936 def sort( 

2937 self, 

2938 name: str, 

2939 start: Optional[int] = None, 

2940 num: Optional[int] = None, 

2941 by: Optional[str] = None, 

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

2943 desc: bool = False, 

2944 alpha: bool = False, 

2945 store: Optional[str] = None, 

2946 groups: Optional[bool] = False, 

2947 ) -> Union[List, int]: 

2948 """ 

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

2950 

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

2952 

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

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

2955 

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

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

2958 the item value is located 

2959 

2960 ``desc`` allows for reversing the sort 

2961 

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

2963 

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

2965 the key ``store`` 

2966 

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

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

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

2970 

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

2972 """ 

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

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

2975 

2976 pieces: list[EncodableT] = [name] 

2977 if by is not None: 

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

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

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

2981 if get is not None: 

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

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

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

2985 # iterable. 

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

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

2988 else: 

2989 for g in get: 

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

2991 if desc: 

2992 pieces.append(b"DESC") 

2993 if alpha: 

2994 pieces.append(b"ALPHA") 

2995 if store is not None: 

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

2997 if groups: 

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

2999 raise DataError( 

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

3001 "must be specified and contain at least " 

3002 "two keys" 

3003 ) 

3004 

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

3006 options["keys"] = [name] 

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

3008 

3009 def sort_ro( 

3010 self, 

3011 key: str, 

3012 start: Optional[int] = None, 

3013 num: Optional[int] = None, 

3014 by: Optional[str] = None, 

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

3016 desc: bool = False, 

3017 alpha: bool = False, 

3018 ) -> list: 

3019 """ 

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

3021 (read-only variant of the SORT command) 

3022 

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

3024 

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

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

3027 

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

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

3030 the item value is located 

3031 

3032 ``desc`` allows for reversing the sort 

3033 

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

3035 

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

3037 """ 

3038 return self.sort( 

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

3040 ) 

3041 

3042 

3043AsyncListCommands = ListCommands 

3044 

3045 

3046class ScanCommands(CommandsProtocol): 

3047 """ 

3048 Redis SCAN commands. 

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

3050 """ 

3051 

3052 def scan( 

3053 self, 

3054 cursor: int = 0, 

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

3056 count: Optional[int] = None, 

3057 _type: Optional[str] = None, 

3058 **kwargs, 

3059 ) -> ResponseT: 

3060 """ 

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

3062 indicating the scan position. 

3063 

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

3065 

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

3067 return per batch. 

3068 

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

3070 Stock Redis instances allow for the following types: 

3071 HASH, LIST, SET, STREAM, STRING, ZSET 

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

3073 

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

3075 """ 

3076 pieces: list[EncodableT] = [cursor] 

3077 if match is not None: 

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

3079 if count is not None: 

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

3081 if _type is not None: 

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

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

3084 

3085 def scan_iter( 

3086 self, 

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

3088 count: Optional[int] = None, 

3089 _type: Optional[str] = None, 

3090 **kwargs, 

3091 ) -> Iterator: 

3092 """ 

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

3094 need to remember the cursor position. 

3095 

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

3097 

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

3099 return per batch. 

3100 

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

3102 Stock Redis instances allow for the following types: 

3103 HASH, LIST, SET, STREAM, STRING, ZSET 

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

3105 """ 

3106 cursor = "0" 

3107 while cursor != 0: 

3108 cursor, data = self.scan( 

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

3110 ) 

3111 yield from data 

3112 

3113 def sscan( 

3114 self, 

3115 name: KeyT, 

3116 cursor: int = 0, 

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

3118 count: Optional[int] = None, 

3119 ) -> ResponseT: 

3120 """ 

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

3122 indicating the scan position. 

3123 

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

3125 

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

3127 

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

3129 """ 

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

3131 if match is not None: 

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

3133 if count is not None: 

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

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

3136 

3137 def sscan_iter( 

3138 self, 

3139 name: KeyT, 

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

3141 count: Optional[int] = None, 

3142 ) -> Iterator: 

3143 """ 

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

3145 need to remember the cursor position. 

3146 

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

3148 

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

3150 """ 

3151 cursor = "0" 

3152 while cursor != 0: 

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

3154 yield from data 

3155 

3156 def hscan( 

3157 self, 

3158 name: KeyT, 

3159 cursor: int = 0, 

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

3161 count: Optional[int] = None, 

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

3163 ) -> ResponseT: 

3164 """ 

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

3166 indicating the scan position. 

3167 

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

3169 

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

3171 

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

3173 

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

3175 """ 

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

3177 if match is not None: 

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

3179 if count is not None: 

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

3181 if no_values is not None: 

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

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

3184 

3185 def hscan_iter( 

3186 self, 

3187 name: str, 

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

3189 count: Optional[int] = None, 

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

3191 ) -> Iterator: 

3192 """ 

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

3194 need to remember the cursor position. 

3195 

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

3197 

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

3199 

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

3201 """ 

3202 cursor = "0" 

3203 while cursor != 0: 

3204 cursor, data = self.hscan( 

3205 name, cursor=cursor, match=match, count=count, no_values=no_values 

3206 ) 

3207 if no_values: 

3208 yield from data 

3209 else: 

3210 yield from data.items() 

3211 

3212 def zscan( 

3213 self, 

3214 name: KeyT, 

3215 cursor: int = 0, 

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

3217 count: Optional[int] = None, 

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

3219 ) -> ResponseT: 

3220 """ 

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

3222 cursor indicating the scan position. 

3223 

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

3225 

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

3227 

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

3229 

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

3231 """ 

3232 pieces = [name, cursor] 

3233 if match is not None: 

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

3235 if count is not None: 

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

3237 options = {"score_cast_func": score_cast_func} 

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

3239 

3240 def zscan_iter( 

3241 self, 

3242 name: KeyT, 

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

3244 count: Optional[int] = None, 

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

3246 ) -> Iterator: 

3247 """ 

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

3249 need to remember the cursor position. 

3250 

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

3252 

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

3254 

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

3256 """ 

3257 cursor = "0" 

3258 while cursor != 0: 

3259 cursor, data = self.zscan( 

3260 name, 

3261 cursor=cursor, 

3262 match=match, 

3263 count=count, 

3264 score_cast_func=score_cast_func, 

3265 ) 

3266 yield from data 

3267 

3268 

3269class AsyncScanCommands(ScanCommands): 

3270 async def scan_iter( 

3271 self, 

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

3273 count: Optional[int] = None, 

3274 _type: Optional[str] = None, 

3275 **kwargs, 

3276 ) -> AsyncIterator: 

3277 """ 

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

3279 need to remember the cursor position. 

3280 

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

3282 

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

3284 return per batch. 

3285 

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

3287 Stock Redis instances allow for the following types: 

3288 HASH, LIST, SET, STREAM, STRING, ZSET 

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

3290 """ 

3291 cursor = "0" 

3292 while cursor != 0: 

3293 cursor, data = await self.scan( 

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

3295 ) 

3296 for d in data: 

3297 yield d 

3298 

3299 async def sscan_iter( 

3300 self, 

3301 name: KeyT, 

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

3303 count: Optional[int] = None, 

3304 ) -> AsyncIterator: 

3305 """ 

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

3307 need to remember the cursor position. 

3308 

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

3310 

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

3312 """ 

3313 cursor = "0" 

3314 while cursor != 0: 

3315 cursor, data = await self.sscan( 

3316 name, cursor=cursor, match=match, count=count 

3317 ) 

3318 for d in data: 

3319 yield d 

3320 

3321 async def hscan_iter( 

3322 self, 

3323 name: str, 

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

3325 count: Optional[int] = None, 

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

3327 ) -> AsyncIterator: 

3328 """ 

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

3330 need to remember the cursor position. 

3331 

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

3333 

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

3335 

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

3337 """ 

3338 cursor = "0" 

3339 while cursor != 0: 

3340 cursor, data = await self.hscan( 

3341 name, cursor=cursor, match=match, count=count, no_values=no_values 

3342 ) 

3343 if no_values: 

3344 for it in data: 

3345 yield it 

3346 else: 

3347 for it in data.items(): 

3348 yield it 

3349 

3350 async def zscan_iter( 

3351 self, 

3352 name: KeyT, 

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

3354 count: Optional[int] = None, 

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

3356 ) -> AsyncIterator: 

3357 """ 

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

3359 need to remember the cursor position. 

3360 

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

3362 

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

3364 

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

3366 """ 

3367 cursor = "0" 

3368 while cursor != 0: 

3369 cursor, data = await self.zscan( 

3370 name, 

3371 cursor=cursor, 

3372 match=match, 

3373 count=count, 

3374 score_cast_func=score_cast_func, 

3375 ) 

3376 for d in data: 

3377 yield d 

3378 

3379 

3380class SetCommands(CommandsProtocol): 

3381 """ 

3382 Redis commands for Set data type. 

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

3384 """ 

3385 

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

3387 """ 

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

3389 

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

3391 """ 

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

3393 

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

3395 """ 

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

3397 

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

3399 """ 

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

3401 

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

3403 """ 

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

3405 

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

3407 """ 

3408 args = list_or_args(keys, args) 

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

3410 

3411 def sdiffstore( 

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

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

3414 """ 

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

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

3417 

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

3419 """ 

3420 args = list_or_args(keys, args) 

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

3422 

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

3424 """ 

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

3426 

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

3428 """ 

3429 args = list_or_args(keys, args) 

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

3431 

3432 def sintercard( 

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

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

3435 """ 

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

3437 

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

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

3440 exit and yield limit as the cardinality 

3441 

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

3443 """ 

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

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

3446 

3447 def sinterstore( 

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

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

3450 """ 

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

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

3453 

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

3455 """ 

3456 args = list_or_args(keys, args) 

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

3458 

3459 def sismember( 

3460 self, name: KeyT, value: str 

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

3462 """ 

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

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

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

3466 

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

3468 """ 

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

3470 

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

3472 """ 

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

3474 

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

3476 """ 

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

3478 

3479 def smismember( 

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

3481 ) -> Union[ 

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

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

3484 ]: 

3485 """ 

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

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

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

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

3490 

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

3492 """ 

3493 args = list_or_args(values, args) 

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

3495 

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

3497 """ 

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

3499 

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

3501 """ 

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

3503 

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

3505 """ 

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

3507 

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

3509 """ 

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

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

3512 

3513 def srandmember( 

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

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

3516 """ 

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

3518 

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

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

3521 Redis 2.6+. 

3522 

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

3524 """ 

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

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

3527 

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

3529 """ 

3530 Remove ``values`` from set ``name`` 

3531 

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

3533 """ 

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

3535 

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

3537 """ 

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

3539 

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

3541 """ 

3542 args = list_or_args(keys, args) 

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

3544 

3545 def sunionstore( 

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

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

3548 """ 

3549 Store the union of sets specified by ``keys`` into a new 

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

3551 

3552 For more information, see https://redis.io/commands/sunionstore 

3553 """ 

3554 args = list_or_args(keys, args) 

3555 return self.execute_command("SUNIONSTORE", dest, *args) 

3556 

3557 

3558AsyncSetCommands = SetCommands 

3559 

3560 

3561class StreamCommands(CommandsProtocol): 

3562 """ 

3563 Redis commands for Stream data type. 

3564 see: https://redis.io/topics/streams-intro 

3565 """ 

3566 

3567 def xack(self, name: KeyT, groupname: GroupT, *ids: StreamIdT) -> ResponseT: 

3568 """ 

3569 Acknowledges the successful processing of one or more messages. 

3570 

3571 Args: 

3572 name: name of the stream. 

3573 groupname: name of the consumer group. 

3574 *ids: message ids to acknowledge. 

3575 

3576 For more information, see https://redis.io/commands/xack 

3577 """ 

3578 return self.execute_command("XACK", name, groupname, *ids) 

3579 

3580 def xackdel( 

3581 self, 

3582 name: KeyT, 

3583 groupname: GroupT, 

3584 *ids: StreamIdT, 

3585 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF", 

3586 ) -> ResponseT: 

3587 """ 

3588 Combines the functionality of XACK and XDEL. Acknowledges the specified 

3589 message IDs in the given consumer group and simultaneously attempts to 

3590 delete the corresponding entries from the stream. 

3591 """ 

3592 if not ids: 

3593 raise DataError("XACKDEL requires at least one message ID") 

3594 

3595 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

3596 raise DataError("XACKDEL ref_policy must be one of: KEEPREF, DELREF, ACKED") 

3597 

3598 pieces = [name, groupname, ref_policy, "IDS", len(ids)] 

3599 pieces.extend(ids) 

3600 return self.execute_command("XACKDEL", *pieces) 

3601 

3602 def xadd( 

3603 self, 

3604 name: KeyT, 

3605 fields: Dict[FieldT, EncodableT], 

3606 id: StreamIdT = "*", 

3607 maxlen: Optional[int] = None, 

3608 approximate: bool = True, 

3609 nomkstream: bool = False, 

3610 minid: Union[StreamIdT, None] = None, 

3611 limit: Optional[int] = None, 

3612 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None, 

3613 ) -> ResponseT: 

3614 """ 

3615 Add to a stream. 

3616 name: name of the stream 

3617 fields: dict of field/value pairs to insert into the stream 

3618 id: Location to insert this record. By default it is appended. 

3619 maxlen: truncate old stream members beyond this size. 

3620 Can't be specified with minid. 

3621 approximate: actual stream length may be slightly more than maxlen 

3622 nomkstream: When set to true, do not make a stream 

3623 minid: the minimum id in the stream to query. 

3624 Can't be specified with maxlen. 

3625 limit: specifies the maximum number of entries to retrieve 

3626 ref_policy: optional reference policy for consumer groups when trimming: 

3627 - KEEPREF (default): When trimming, preserves references in consumer groups' PEL 

3628 - DELREF: When trimming, removes all references from consumer groups' PEL 

3629 - ACKED: When trimming, only removes entries acknowledged by all consumer groups 

3630 

3631 For more information, see https://redis.io/commands/xadd 

3632 """ 

3633 pieces: list[EncodableT] = [] 

3634 if maxlen is not None and minid is not None: 

3635 raise DataError("Only one of ```maxlen``` or ```minid``` may be specified") 

3636 

3637 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

3638 raise DataError("XADD ref_policy must be one of: KEEPREF, DELREF, ACKED") 

3639 

3640 if maxlen is not None: 

3641 if not isinstance(maxlen, int) or maxlen < 0: 

3642 raise DataError("XADD maxlen must be non-negative integer") 

3643 pieces.append(b"MAXLEN") 

3644 if approximate: 

3645 pieces.append(b"~") 

3646 pieces.append(str(maxlen)) 

3647 if minid is not None: 

3648 pieces.append(b"MINID") 

3649 if approximate: 

3650 pieces.append(b"~") 

3651 pieces.append(minid) 

3652 if limit is not None: 

3653 pieces.extend([b"LIMIT", limit]) 

3654 if nomkstream: 

3655 pieces.append(b"NOMKSTREAM") 

3656 if ref_policy is not None: 

3657 pieces.append(ref_policy) 

3658 pieces.append(id) 

3659 if not isinstance(fields, dict) or len(fields) == 0: 

3660 raise DataError("XADD fields must be a non-empty dict") 

3661 for pair in fields.items(): 

3662 pieces.extend(pair) 

3663 return self.execute_command("XADD", name, *pieces) 

3664 

3665 def xautoclaim( 

3666 self, 

3667 name: KeyT, 

3668 groupname: GroupT, 

3669 consumername: ConsumerT, 

3670 min_idle_time: int, 

3671 start_id: StreamIdT = "0-0", 

3672 count: Optional[int] = None, 

3673 justid: bool = False, 

3674 ) -> ResponseT: 

3675 """ 

3676 Transfers ownership of pending stream entries that match the specified 

3677 criteria. Conceptually, equivalent to calling XPENDING and then XCLAIM, 

3678 but provides a more straightforward way to deal with message delivery 

3679 failures via SCAN-like semantics. 

3680 name: name of the stream. 

3681 groupname: name of the consumer group. 

3682 consumername: name of a consumer that claims the message. 

3683 min_idle_time: filter messages that were idle less than this amount of 

3684 milliseconds. 

3685 start_id: filter messages with equal or greater ID. 

3686 count: optional integer, upper limit of the number of entries that the 

3687 command attempts to claim. Set to 100 by default. 

3688 justid: optional boolean, false by default. Return just an array of IDs 

3689 of messages successfully claimed, without returning the actual message 

3690 

3691 For more information, see https://redis.io/commands/xautoclaim 

3692 """ 

3693 try: 

3694 if int(min_idle_time) < 0: 

3695 raise DataError( 

3696 "XAUTOCLAIM min_idle_time must be a nonnegative integer" 

3697 ) 

3698 except TypeError: 

3699 pass 

3700 

3701 kwargs = {} 

3702 pieces = [name, groupname, consumername, min_idle_time, start_id] 

3703 

3704 try: 

3705 if int(count) < 0: 

3706 raise DataError("XPENDING count must be a integer >= 0") 

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

3708 except TypeError: 

3709 pass 

3710 if justid: 

3711 pieces.append(b"JUSTID") 

3712 kwargs["parse_justid"] = True 

3713 

3714 return self.execute_command("XAUTOCLAIM", *pieces, **kwargs) 

3715 

3716 def xclaim( 

3717 self, 

3718 name: KeyT, 

3719 groupname: GroupT, 

3720 consumername: ConsumerT, 

3721 min_idle_time: int, 

3722 message_ids: Union[List[StreamIdT], Tuple[StreamIdT]], 

3723 idle: Optional[int] = None, 

3724 time: Optional[int] = None, 

3725 retrycount: Optional[int] = None, 

3726 force: bool = False, 

3727 justid: bool = False, 

3728 ) -> ResponseT: 

3729 """ 

3730 Changes the ownership of a pending message. 

3731 

3732 name: name of the stream. 

3733 

3734 groupname: name of the consumer group. 

3735 

3736 consumername: name of a consumer that claims the message. 

3737 

3738 min_idle_time: filter messages that were idle less than this amount of 

3739 milliseconds 

3740 

3741 message_ids: non-empty list or tuple of message IDs to claim 

3742 

3743 idle: optional. Set the idle time (last time it was delivered) of the 

3744 message in ms 

3745 

3746 time: optional integer. This is the same as idle but instead of a 

3747 relative amount of milliseconds, it sets the idle time to a specific 

3748 Unix time (in milliseconds). 

3749 

3750 retrycount: optional integer. set the retry counter to the specified 

3751 value. This counter is incremented every time a message is delivered 

3752 again. 

3753 

3754 force: optional boolean, false by default. Creates the pending message 

3755 entry in the PEL even if certain specified IDs are not already in the 

3756 PEL assigned to a different client. 

3757 

3758 justid: optional boolean, false by default. Return just an array of IDs 

3759 of messages successfully claimed, without returning the actual message 

3760 

3761 For more information, see https://redis.io/commands/xclaim 

3762 """ 

3763 if not isinstance(min_idle_time, int) or min_idle_time < 0: 

3764 raise DataError("XCLAIM min_idle_time must be a non negative integer") 

3765 if not isinstance(message_ids, (list, tuple)) or not message_ids: 

3766 raise DataError( 

3767 "XCLAIM message_ids must be a non empty list or " 

3768 "tuple of message IDs to claim" 

3769 ) 

3770 

3771 kwargs = {} 

3772 pieces: list[EncodableT] = [name, groupname, consumername, str(min_idle_time)] 

3773 pieces.extend(list(message_ids)) 

3774 

3775 if idle is not None: 

3776 if not isinstance(idle, int): 

3777 raise DataError("XCLAIM idle must be an integer") 

3778 pieces.extend((b"IDLE", str(idle))) 

3779 if time is not None: 

3780 if not isinstance(time, int): 

3781 raise DataError("XCLAIM time must be an integer") 

3782 pieces.extend((b"TIME", str(time))) 

3783 if retrycount is not None: 

3784 if not isinstance(retrycount, int): 

3785 raise DataError("XCLAIM retrycount must be an integer") 

3786 pieces.extend((b"RETRYCOUNT", str(retrycount))) 

3787 

3788 if force: 

3789 if not isinstance(force, bool): 

3790 raise DataError("XCLAIM force must be a boolean") 

3791 pieces.append(b"FORCE") 

3792 if justid: 

3793 if not isinstance(justid, bool): 

3794 raise DataError("XCLAIM justid must be a boolean") 

3795 pieces.append(b"JUSTID") 

3796 kwargs["parse_justid"] = True 

3797 return self.execute_command("XCLAIM", *pieces, **kwargs) 

3798 

3799 def xdel(self, name: KeyT, *ids: StreamIdT) -> ResponseT: 

3800 """ 

3801 Deletes one or more messages from a stream. 

3802 

3803 Args: 

3804 name: name of the stream. 

3805 *ids: message ids to delete. 

3806 

3807 For more information, see https://redis.io/commands/xdel 

3808 """ 

3809 return self.execute_command("XDEL", name, *ids) 

3810 

3811 def xdelex( 

3812 self, 

3813 name: KeyT, 

3814 *ids: StreamIdT, 

3815 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF", 

3816 ) -> ResponseT: 

3817 """ 

3818 Extended version of XDEL that provides more control over how message entries 

3819 are deleted concerning consumer groups. 

3820 """ 

3821 if not ids: 

3822 raise DataError("XDELEX requires at least one message ID") 

3823 

3824 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

3825 raise DataError("XDELEX ref_policy must be one of: KEEPREF, DELREF, ACKED") 

3826 

3827 pieces = [name, ref_policy, "IDS", len(ids)] 

3828 pieces.extend(ids) 

3829 return self.execute_command("XDELEX", *pieces) 

3830 

3831 def xgroup_create( 

3832 self, 

3833 name: KeyT, 

3834 groupname: GroupT, 

3835 id: StreamIdT = "$", 

3836 mkstream: bool = False, 

3837 entries_read: Optional[int] = None, 

3838 ) -> ResponseT: 

3839 """ 

3840 Create a new consumer group associated with a stream. 

3841 name: name of the stream. 

3842 groupname: name of the consumer group. 

3843 id: ID of the last item in the stream to consider already delivered. 

3844 

3845 For more information, see https://redis.io/commands/xgroup-create 

3846 """ 

3847 pieces: list[EncodableT] = ["XGROUP CREATE", name, groupname, id] 

3848 if mkstream: 

3849 pieces.append(b"MKSTREAM") 

3850 if entries_read is not None: 

3851 pieces.extend(["ENTRIESREAD", entries_read]) 

3852 

3853 return self.execute_command(*pieces) 

3854 

3855 def xgroup_delconsumer( 

3856 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

3857 ) -> ResponseT: 

3858 """ 

3859 Remove a specific consumer from a consumer group. 

3860 Returns the number of pending messages that the consumer had before it 

3861 was deleted. 

3862 name: name of the stream. 

3863 groupname: name of the consumer group. 

3864 consumername: name of consumer to delete 

3865 

3866 For more information, see https://redis.io/commands/xgroup-delconsumer 

3867 """ 

3868 return self.execute_command("XGROUP DELCONSUMER", name, groupname, consumername) 

3869 

3870 def xgroup_destroy(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3871 """ 

3872 Destroy a consumer group. 

3873 name: name of the stream. 

3874 groupname: name of the consumer group. 

3875 

3876 For more information, see https://redis.io/commands/xgroup-destroy 

3877 """ 

3878 return self.execute_command("XGROUP DESTROY", name, groupname) 

3879 

3880 def xgroup_createconsumer( 

3881 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

3882 ) -> ResponseT: 

3883 """ 

3884 Consumers in a consumer group are auto-created every time a new 

3885 consumer name is mentioned by some command. 

3886 They can be explicitly created by using this command. 

3887 name: name of the stream. 

3888 groupname: name of the consumer group. 

3889 consumername: name of consumer to create. 

3890 

3891 See: https://redis.io/commands/xgroup-createconsumer 

3892 """ 

3893 return self.execute_command( 

3894 "XGROUP CREATECONSUMER", name, groupname, consumername 

3895 ) 

3896 

3897 def xgroup_setid( 

3898 self, 

3899 name: KeyT, 

3900 groupname: GroupT, 

3901 id: StreamIdT, 

3902 entries_read: Optional[int] = None, 

3903 ) -> ResponseT: 

3904 """ 

3905 Set the consumer group last delivered ID to something else. 

3906 name: name of the stream. 

3907 groupname: name of the consumer group. 

3908 id: ID of the last item in the stream to consider already delivered. 

3909 

3910 For more information, see https://redis.io/commands/xgroup-setid 

3911 """ 

3912 pieces = [name, groupname, id] 

3913 if entries_read is not None: 

3914 pieces.extend(["ENTRIESREAD", entries_read]) 

3915 return self.execute_command("XGROUP SETID", *pieces) 

3916 

3917 def xinfo_consumers(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3918 """ 

3919 Returns general information about the consumers in the group. 

3920 name: name of the stream. 

3921 groupname: name of the consumer group. 

3922 

3923 For more information, see https://redis.io/commands/xinfo-consumers 

3924 """ 

3925 return self.execute_command("XINFO CONSUMERS", name, groupname) 

3926 

3927 def xinfo_groups(self, name: KeyT) -> ResponseT: 

3928 """ 

3929 Returns general information about the consumer groups of the stream. 

3930 name: name of the stream. 

3931 

3932 For more information, see https://redis.io/commands/xinfo-groups 

3933 """ 

3934 return self.execute_command("XINFO GROUPS", name) 

3935 

3936 def xinfo_stream(self, name: KeyT, full: bool = False) -> ResponseT: 

3937 """ 

3938 Returns general information about the stream. 

3939 name: name of the stream. 

3940 full: optional boolean, false by default. Return full summary 

3941 

3942 For more information, see https://redis.io/commands/xinfo-stream 

3943 """ 

3944 pieces = [name] 

3945 options = {} 

3946 if full: 

3947 pieces.append(b"FULL") 

3948 options = {"full": full} 

3949 return self.execute_command("XINFO STREAM", *pieces, **options) 

3950 

3951 def xlen(self, name: KeyT) -> ResponseT: 

3952 """ 

3953 Returns the number of elements in a given stream. 

3954 

3955 For more information, see https://redis.io/commands/xlen 

3956 """ 

3957 return self.execute_command("XLEN", name, keys=[name]) 

3958 

3959 def xpending(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3960 """ 

3961 Returns information about pending messages of a group. 

3962 name: name of the stream. 

3963 groupname: name of the consumer group. 

3964 

3965 For more information, see https://redis.io/commands/xpending 

3966 """ 

3967 return self.execute_command("XPENDING", name, groupname, keys=[name]) 

3968 

3969 def xpending_range( 

3970 self, 

3971 name: KeyT, 

3972 groupname: GroupT, 

3973 min: StreamIdT, 

3974 max: StreamIdT, 

3975 count: int, 

3976 consumername: Union[ConsumerT, None] = None, 

3977 idle: Optional[int] = None, 

3978 ) -> ResponseT: 

3979 """ 

3980 Returns information about pending messages, in a range. 

3981 

3982 name: name of the stream. 

3983 groupname: name of the consumer group. 

3984 idle: available from version 6.2. filter entries by their 

3985 idle-time, given in milliseconds (optional). 

3986 min: minimum stream ID. 

3987 max: maximum stream ID. 

3988 count: number of messages to return 

3989 consumername: name of a consumer to filter by (optional). 

3990 """ 

3991 if {min, max, count} == {None}: 

3992 if idle is not None or consumername is not None: 

3993 raise DataError( 

3994 "if XPENDING is provided with idle time" 

3995 " or consumername, it must be provided" 

3996 " with min, max and count parameters" 

3997 ) 

3998 return self.xpending(name, groupname) 

3999 

4000 pieces = [name, groupname] 

4001 if min is None or max is None or count is None: 

4002 raise DataError( 

4003 "XPENDING must be provided with min, max " 

4004 "and count parameters, or none of them." 

4005 ) 

4006 # idle 

4007 try: 

4008 if int(idle) < 0: 

4009 raise DataError("XPENDING idle must be a integer >= 0") 

4010 pieces.extend(["IDLE", idle]) 

4011 except TypeError: 

4012 pass 

4013 # count 

4014 try: 

4015 if int(count) < 0: 

4016 raise DataError("XPENDING count must be a integer >= 0") 

4017 pieces.extend([min, max, count]) 

4018 except TypeError: 

4019 pass 

4020 # consumername 

4021 if consumername: 

4022 pieces.append(consumername) 

4023 

4024 return self.execute_command("XPENDING", *pieces, parse_detail=True) 

4025 

4026 def xrange( 

4027 self, 

4028 name: KeyT, 

4029 min: StreamIdT = "-", 

4030 max: StreamIdT = "+", 

4031 count: Optional[int] = None, 

4032 ) -> ResponseT: 

4033 """ 

4034 Read stream values within an interval. 

4035 

4036 name: name of the stream. 

4037 

4038 start: first stream ID. defaults to '-', 

4039 meaning the earliest available. 

4040 

4041 finish: last stream ID. defaults to '+', 

4042 meaning the latest available. 

4043 

4044 count: if set, only return this many items, beginning with the 

4045 earliest available. 

4046 

4047 For more information, see https://redis.io/commands/xrange 

4048 """ 

4049 pieces = [min, max] 

4050 if count is not None: 

4051 if not isinstance(count, int) or count < 1: 

4052 raise DataError("XRANGE count must be a positive integer") 

4053 pieces.append(b"COUNT") 

4054 pieces.append(str(count)) 

4055 

4056 return self.execute_command("XRANGE", name, *pieces, keys=[name]) 

4057 

4058 def xread( 

4059 self, 

4060 streams: Dict[KeyT, StreamIdT], 

4061 count: Optional[int] = None, 

4062 block: Optional[int] = None, 

4063 ) -> ResponseT: 

4064 """ 

4065 Block and monitor multiple streams for new data. 

4066 

4067 streams: a dict of stream names to stream IDs, where 

4068 IDs indicate the last ID already seen. 

4069 

4070 count: if set, only return this many items, beginning with the 

4071 earliest available. 

4072 

4073 block: number of milliseconds to wait, if nothing already present. 

4074 

4075 For more information, see https://redis.io/commands/xread 

4076 """ 

4077 pieces = [] 

4078 if block is not None: 

4079 if not isinstance(block, int) or block < 0: 

4080 raise DataError("XREAD block must be a non-negative integer") 

4081 pieces.append(b"BLOCK") 

4082 pieces.append(str(block)) 

4083 if count is not None: 

4084 if not isinstance(count, int) or count < 1: 

4085 raise DataError("XREAD count must be a positive integer") 

4086 pieces.append(b"COUNT") 

4087 pieces.append(str(count)) 

4088 if not isinstance(streams, dict) or len(streams) == 0: 

4089 raise DataError("XREAD streams must be a non empty dict") 

4090 pieces.append(b"STREAMS") 

4091 keys, values = zip(*streams.items()) 

4092 pieces.extend(keys) 

4093 pieces.extend(values) 

4094 return self.execute_command("XREAD", *pieces, keys=keys) 

4095 

4096 def xreadgroup( 

4097 self, 

4098 groupname: str, 

4099 consumername: str, 

4100 streams: Dict[KeyT, StreamIdT], 

4101 count: Optional[int] = None, 

4102 block: Optional[int] = None, 

4103 noack: bool = False, 

4104 claim_min_idle_time: Optional[int] = None, 

4105 ) -> ResponseT: 

4106 """ 

4107 Read from a stream via a consumer group. 

4108 

4109 groupname: name of the consumer group. 

4110 

4111 consumername: name of the requesting consumer. 

4112 

4113 streams: a dict of stream names to stream IDs, where 

4114 IDs indicate the last ID already seen. 

4115 

4116 count: if set, only return this many items, beginning with the 

4117 earliest available. 

4118 

4119 block: number of milliseconds to wait, if nothing already present. 

4120 noack: do not add messages to the PEL 

4121 

4122 claim_min_idle_time: accepts an integer type and represents a 

4123 time interval in milliseconds 

4124 

4125 For more information, see https://redis.io/commands/xreadgroup 

4126 """ 

4127 options = {} 

4128 pieces: list[EncodableT] = [b"GROUP", groupname, consumername] 

4129 if count is not None: 

4130 if not isinstance(count, int) or count < 1: 

4131 raise DataError("XREADGROUP count must be a positive integer") 

4132 pieces.append(b"COUNT") 

4133 pieces.append(str(count)) 

4134 if block is not None: 

4135 if not isinstance(block, int) or block < 0: 

4136 raise DataError("XREADGROUP block must be a non-negative integer") 

4137 pieces.append(b"BLOCK") 

4138 pieces.append(str(block)) 

4139 if noack: 

4140 pieces.append(b"NOACK") 

4141 if claim_min_idle_time is not None: 

4142 if not isinstance(claim_min_idle_time, int) or claim_min_idle_time < 0: 

4143 raise DataError( 

4144 "XREADGROUP claim_min_idle_time must be a non-negative integer" 

4145 ) 

4146 pieces.append(b"CLAIM") 

4147 pieces.append(claim_min_idle_time) 

4148 options["claim_min_idle_time"] = claim_min_idle_time 

4149 if not isinstance(streams, dict) or len(streams) == 0: 

4150 raise DataError("XREADGROUP streams must be a non empty dict") 

4151 pieces.append(b"STREAMS") 

4152 pieces.extend(streams.keys()) 

4153 pieces.extend(streams.values()) 

4154 return self.execute_command("XREADGROUP", *pieces, **options) 

4155 

4156 def xrevrange( 

4157 self, 

4158 name: KeyT, 

4159 max: StreamIdT = "+", 

4160 min: StreamIdT = "-", 

4161 count: Optional[int] = None, 

4162 ) -> ResponseT: 

4163 """ 

4164 Read stream values within an interval, in reverse order. 

4165 

4166 name: name of the stream 

4167 

4168 start: first stream ID. defaults to '+', 

4169 meaning the latest available. 

4170 

4171 finish: last stream ID. defaults to '-', 

4172 meaning the earliest available. 

4173 

4174 count: if set, only return this many items, beginning with the 

4175 latest available. 

4176 

4177 For more information, see https://redis.io/commands/xrevrange 

4178 """ 

4179 pieces: list[EncodableT] = [max, min] 

4180 if count is not None: 

4181 if not isinstance(count, int) or count < 1: 

4182 raise DataError("XREVRANGE count must be a positive integer") 

4183 pieces.append(b"COUNT") 

4184 pieces.append(str(count)) 

4185 

4186 return self.execute_command("XREVRANGE", name, *pieces, keys=[name]) 

4187 

4188 def xtrim( 

4189 self, 

4190 name: KeyT, 

4191 maxlen: Optional[int] = None, 

4192 approximate: bool = True, 

4193 minid: Union[StreamIdT, None] = None, 

4194 limit: Optional[int] = None, 

4195 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None, 

4196 ) -> ResponseT: 

4197 """ 

4198 Trims old messages from a stream. 

4199 name: name of the stream. 

4200 maxlen: truncate old stream messages beyond this size 

4201 Can't be specified with minid. 

4202 approximate: actual stream length may be slightly more than maxlen 

4203 minid: the minimum id in the stream to query 

4204 Can't be specified with maxlen. 

4205 limit: specifies the maximum number of entries to retrieve 

4206 ref_policy: optional reference policy for consumer groups: 

4207 - KEEPREF (default): Trims entries but preserves references in consumer groups' PEL 

4208 - DELREF: Trims entries and removes all references from consumer groups' PEL 

4209 - ACKED: Only trims entries that were read and acknowledged by all consumer groups 

4210 

4211 For more information, see https://redis.io/commands/xtrim 

4212 """ 

4213 pieces: list[EncodableT] = [] 

4214 if maxlen is not None and minid is not None: 

4215 raise DataError("Only one of ``maxlen`` or ``minid`` may be specified") 

4216 

4217 if maxlen is None and minid is None: 

4218 raise DataError("One of ``maxlen`` or ``minid`` must be specified") 

4219 

4220 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

4221 raise DataError("XTRIM ref_policy must be one of: KEEPREF, DELREF, ACKED") 

4222 

4223 if maxlen is not None: 

4224 pieces.append(b"MAXLEN") 

4225 if minid is not None: 

4226 pieces.append(b"MINID") 

4227 if approximate: 

4228 pieces.append(b"~") 

4229 if maxlen is not None: 

4230 pieces.append(maxlen) 

4231 if minid is not None: 

4232 pieces.append(minid) 

4233 if limit is not None: 

4234 pieces.append(b"LIMIT") 

4235 pieces.append(limit) 

4236 if ref_policy is not None: 

4237 pieces.append(ref_policy) 

4238 

4239 return self.execute_command("XTRIM", name, *pieces) 

4240 

4241 

4242AsyncStreamCommands = StreamCommands 

4243 

4244 

4245class SortedSetCommands(CommandsProtocol): 

4246 """ 

4247 Redis commands for Sorted Sets data type. 

4248 see: https://redis.io/topics/data-types-intro#redis-sorted-sets 

4249 """ 

4250 

4251 def zadd( 

4252 self, 

4253 name: KeyT, 

4254 mapping: Mapping[AnyKeyT, EncodableT], 

4255 nx: bool = False, 

4256 xx: bool = False, 

4257 ch: bool = False, 

4258 incr: bool = False, 

4259 gt: bool = False, 

4260 lt: bool = False, 

4261 ) -> ResponseT: 

4262 """ 

4263 Set any number of element-name, score pairs to the key ``name``. Pairs 

4264 are specified as a dict of element-names keys to score values. 

4265 

4266 ``nx`` forces ZADD to only create new elements and not to update 

4267 scores for elements that already exist. 

4268 

4269 ``xx`` forces ZADD to only update scores of elements that already 

4270 exist. New elements will not be added. 

4271 

4272 ``ch`` modifies the return value to be the numbers of elements changed. 

4273 Changed elements include new elements that were added and elements 

4274 whose scores changed. 

4275 

4276 ``incr`` modifies ZADD to behave like ZINCRBY. In this mode only a 

4277 single element/score pair can be specified and the score is the amount 

4278 the existing score will be incremented by. When using this mode the 

4279 return value of ZADD will be the new score of the element. 

4280 

4281 ``lt`` only updates existing elements if the new score is less than 

4282 the current score. This flag doesn't prevent adding new elements. 

4283 

4284 ``gt`` only updates existing elements if the new score is greater than 

4285 the current score. This flag doesn't prevent adding new elements. 

4286 

4287 The return value of ZADD varies based on the mode specified. With no 

4288 options, ZADD returns the number of new elements added to the sorted 

4289 set. 

4290 

4291 ``nx``, ``lt``, and ``gt`` are mutually exclusive options. 

4292 

4293 See: https://redis.io/commands/ZADD 

4294 """ 

4295 if not mapping: 

4296 raise DataError("ZADD requires at least one element/score pair") 

4297 if nx and xx: 

4298 raise DataError("ZADD allows either 'nx' or 'xx', not both") 

4299 if gt and lt: 

4300 raise DataError("ZADD allows either 'gt' or 'lt', not both") 

4301 if incr and len(mapping) != 1: 

4302 raise DataError( 

4303 "ZADD option 'incr' only works when passing a single element/score pair" 

4304 ) 

4305 if nx and (gt or lt): 

4306 raise DataError("Only one of 'nx', 'lt', or 'gr' may be defined.") 

4307 

4308 pieces: list[EncodableT] = [] 

4309 options = {} 

4310 if nx: 

4311 pieces.append(b"NX") 

4312 if xx: 

4313 pieces.append(b"XX") 

4314 if ch: 

4315 pieces.append(b"CH") 

4316 if incr: 

4317 pieces.append(b"INCR") 

4318 options["as_score"] = True 

4319 if gt: 

4320 pieces.append(b"GT") 

4321 if lt: 

4322 pieces.append(b"LT") 

4323 for pair in mapping.items(): 

4324 pieces.append(pair[1]) 

4325 pieces.append(pair[0]) 

4326 return self.execute_command("ZADD", name, *pieces, **options) 

4327 

4328 def zcard(self, name: KeyT) -> ResponseT: 

4329 """ 

4330 Return the number of elements in the sorted set ``name`` 

4331 

4332 For more information, see https://redis.io/commands/zcard 

4333 """ 

4334 return self.execute_command("ZCARD", name, keys=[name]) 

4335 

4336 def zcount(self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT) -> ResponseT: 

4337 """ 

4338 Returns the number of elements in the sorted set at key ``name`` with 

4339 a score between ``min`` and ``max``. 

4340 

4341 For more information, see https://redis.io/commands/zcount 

4342 """ 

4343 return self.execute_command("ZCOUNT", name, min, max, keys=[name]) 

4344 

4345 def zdiff(self, keys: KeysT, withscores: bool = False) -> ResponseT: 

4346 """ 

4347 Returns the difference between the first and all successive input 

4348 sorted sets provided in ``keys``. 

4349 

4350 For more information, see https://redis.io/commands/zdiff 

4351 """ 

4352 pieces = [len(keys), *keys] 

4353 if withscores: 

4354 pieces.append("WITHSCORES") 

4355 return self.execute_command("ZDIFF", *pieces, keys=keys) 

4356 

4357 def zdiffstore(self, dest: KeyT, keys: KeysT) -> ResponseT: 

4358 """ 

4359 Computes the difference between the first and all successive input 

4360 sorted sets provided in ``keys`` and stores the result in ``dest``. 

4361 

4362 For more information, see https://redis.io/commands/zdiffstore 

4363 """ 

4364 pieces = [len(keys), *keys] 

4365 return self.execute_command("ZDIFFSTORE", dest, *pieces) 

4366 

4367 def zincrby(self, name: KeyT, amount: float, value: EncodableT) -> ResponseT: 

4368 """ 

4369 Increment the score of ``value`` in sorted set ``name`` by ``amount`` 

4370 

4371 For more information, see https://redis.io/commands/zincrby 

4372 """ 

4373 return self.execute_command("ZINCRBY", name, amount, value) 

4374 

4375 def zinter( 

4376 self, keys: KeysT, aggregate: Optional[str] = None, withscores: bool = False 

4377 ) -> ResponseT: 

4378 """ 

4379 Return the intersect of multiple sorted sets specified by ``keys``. 

4380 With the ``aggregate`` option, it is possible to specify how the 

4381 results of the union are aggregated. This option defaults to SUM, 

4382 where the score of an element is summed across the inputs where it 

4383 exists. When this option is set to either MIN or MAX, the resulting 

4384 set will contain the minimum or maximum score of an element across 

4385 the inputs where it exists. 

4386 

4387 For more information, see https://redis.io/commands/zinter 

4388 """ 

4389 return self._zaggregate("ZINTER", None, keys, aggregate, withscores=withscores) 

4390 

4391 def zinterstore( 

4392 self, 

4393 dest: KeyT, 

4394 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4395 aggregate: Optional[str] = None, 

4396 ) -> ResponseT: 

4397 """ 

4398 Intersect multiple sorted sets specified by ``keys`` into a new 

4399 sorted set, ``dest``. Scores in the destination will be aggregated 

4400 based on the ``aggregate``. This option defaults to SUM, where the 

4401 score of an element is summed across the inputs where it exists. 

4402 When this option is set to either MIN or MAX, the resulting set will 

4403 contain the minimum or maximum score of an element across the inputs 

4404 where it exists. 

4405 

4406 For more information, see https://redis.io/commands/zinterstore 

4407 """ 

4408 return self._zaggregate("ZINTERSTORE", dest, keys, aggregate) 

4409 

4410 def zintercard( 

4411 self, numkeys: int, keys: List[str], limit: int = 0 

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

4413 """ 

4414 Return the cardinality of the intersect of multiple sorted sets 

4415 specified by ``keys``. 

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

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

4418 exit and yield limit as the cardinality 

4419 

4420 For more information, see https://redis.io/commands/zintercard 

4421 """ 

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

4423 return self.execute_command("ZINTERCARD", *args, keys=keys) 

4424 

4425 def zlexcount(self, name, min, max): 

4426 """ 

4427 Return the number of items in the sorted set ``name`` between the 

4428 lexicographical range ``min`` and ``max``. 

4429 

4430 For more information, see https://redis.io/commands/zlexcount 

4431 """ 

4432 return self.execute_command("ZLEXCOUNT", name, min, max, keys=[name]) 

4433 

4434 def zpopmax(self, name: KeyT, count: Optional[int] = None) -> ResponseT: 

4435 """ 

4436 Remove and return up to ``count`` members with the highest scores 

4437 from the sorted set ``name``. 

4438 

4439 For more information, see https://redis.io/commands/zpopmax 

4440 """ 

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

4442 options = {"withscores": True} 

4443 return self.execute_command("ZPOPMAX", name, *args, **options) 

4444 

4445 def zpopmin(self, name: KeyT, count: Optional[int] = None) -> ResponseT: 

4446 """ 

4447 Remove and return up to ``count`` members with the lowest scores 

4448 from the sorted set ``name``. 

4449 

4450 For more information, see https://redis.io/commands/zpopmin 

4451 """ 

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

4453 options = {"withscores": True} 

4454 return self.execute_command("ZPOPMIN", name, *args, **options) 

4455 

4456 def zrandmember( 

4457 self, key: KeyT, count: Optional[int] = None, withscores: bool = False 

4458 ) -> ResponseT: 

4459 """ 

4460 Return a random element from the sorted set value stored at key. 

4461 

4462 ``count`` if the argument is positive, return an array of distinct 

4463 fields. If called with a negative count, the behavior changes and 

4464 the command is allowed to return the same field multiple times. 

4465 In this case, the number of returned fields is the absolute value 

4466 of the specified count. 

4467 

4468 ``withscores`` The optional WITHSCORES modifier changes the reply so it 

4469 includes the respective scores of the randomly selected elements from 

4470 the sorted set. 

4471 

4472 For more information, see https://redis.io/commands/zrandmember 

4473 """ 

4474 params = [] 

4475 if count is not None: 

4476 params.append(count) 

4477 if withscores: 

4478 params.append("WITHSCORES") 

4479 

4480 return self.execute_command("ZRANDMEMBER", key, *params) 

4481 

4482 def bzpopmax(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4483 """ 

4484 ZPOPMAX a value off of the first non-empty sorted set 

4485 named in the ``keys`` list. 

4486 

4487 If none of the sorted sets in ``keys`` has a value to ZPOPMAX, 

4488 then block for ``timeout`` seconds, or until a member gets added 

4489 to one of the sorted sets. 

4490 

4491 If timeout is 0, then block indefinitely. 

4492 

4493 For more information, see https://redis.io/commands/bzpopmax 

4494 """ 

4495 if timeout is None: 

4496 timeout = 0 

4497 keys = list_or_args(keys, None) 

4498 keys.append(timeout) 

4499 return self.execute_command("BZPOPMAX", *keys) 

4500 

4501 def bzpopmin(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4502 """ 

4503 ZPOPMIN a value off of the first non-empty sorted set 

4504 named in the ``keys`` list. 

4505 

4506 If none of the sorted sets in ``keys`` has a value to ZPOPMIN, 

4507 then block for ``timeout`` seconds, or until a member gets added 

4508 to one of the sorted sets. 

4509 

4510 If timeout is 0, then block indefinitely. 

4511 

4512 For more information, see https://redis.io/commands/bzpopmin 

4513 """ 

4514 if timeout is None: 

4515 timeout = 0 

4516 keys: list[EncodableT] = list_or_args(keys, None) 

4517 keys.append(timeout) 

4518 return self.execute_command("BZPOPMIN", *keys) 

4519 

4520 def zmpop( 

4521 self, 

4522 num_keys: int, 

4523 keys: List[str], 

4524 min: Optional[bool] = False, 

4525 max: Optional[bool] = False, 

4526 count: Optional[int] = 1, 

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

4528 """ 

4529 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4530 named in the ``keys`` list. 

4531 For more information, see https://redis.io/commands/zmpop 

4532 """ 

4533 args = [num_keys] + keys 

4534 if (min and max) or (not min and not max): 

4535 raise DataError 

4536 elif min: 

4537 args.append("MIN") 

4538 else: 

4539 args.append("MAX") 

4540 if count != 1: 

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

4542 

4543 return self.execute_command("ZMPOP", *args) 

4544 

4545 def bzmpop( 

4546 self, 

4547 timeout: float, 

4548 numkeys: int, 

4549 keys: List[str], 

4550 min: Optional[bool] = False, 

4551 max: Optional[bool] = False, 

4552 count: Optional[int] = 1, 

4553 ) -> Optional[list]: 

4554 """ 

4555 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4556 named in the ``keys`` list. 

4557 

4558 If none of the sorted sets in ``keys`` has a value to pop, 

4559 then block for ``timeout`` seconds, or until a member gets added 

4560 to one of the sorted sets. 

4561 

4562 If timeout is 0, then block indefinitely. 

4563 

4564 For more information, see https://redis.io/commands/bzmpop 

4565 """ 

4566 args = [timeout, numkeys, *keys] 

4567 if (min and max) or (not min and not max): 

4568 raise DataError("Either min or max, but not both must be set") 

4569 elif min: 

4570 args.append("MIN") 

4571 else: 

4572 args.append("MAX") 

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

4574 

4575 return self.execute_command("BZMPOP", *args) 

4576 

4577 def _zrange( 

4578 self, 

4579 command, 

4580 dest: Union[KeyT, None], 

4581 name: KeyT, 

4582 start: int, 

4583 end: int, 

4584 desc: bool = False, 

4585 byscore: bool = False, 

4586 bylex: bool = False, 

4587 withscores: bool = False, 

4588 score_cast_func: Union[type, Callable, None] = float, 

4589 offset: Optional[int] = None, 

4590 num: Optional[int] = None, 

4591 ) -> ResponseT: 

4592 if byscore and bylex: 

4593 raise DataError("``byscore`` and ``bylex`` can not be specified together.") 

4594 if (offset is not None and num is None) or (num is not None and offset is None): 

4595 raise DataError("``offset`` and ``num`` must both be specified.") 

4596 if bylex and withscores: 

4597 raise DataError( 

4598 "``withscores`` not supported in combination with ``bylex``." 

4599 ) 

4600 pieces = [command] 

4601 if dest: 

4602 pieces.append(dest) 

4603 pieces.extend([name, start, end]) 

4604 if byscore: 

4605 pieces.append("BYSCORE") 

4606 if bylex: 

4607 pieces.append("BYLEX") 

4608 if desc: 

4609 pieces.append("REV") 

4610 if offset is not None and num is not None: 

4611 pieces.extend(["LIMIT", offset, num]) 

4612 if withscores: 

4613 pieces.append("WITHSCORES") 

4614 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4615 options["keys"] = [name] 

4616 return self.execute_command(*pieces, **options) 

4617 

4618 def zrange( 

4619 self, 

4620 name: KeyT, 

4621 start: int, 

4622 end: int, 

4623 desc: bool = False, 

4624 withscores: bool = False, 

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

4626 byscore: bool = False, 

4627 bylex: bool = False, 

4628 offset: Optional[int] = None, 

4629 num: Optional[int] = None, 

4630 ) -> ResponseT: 

4631 """ 

4632 Return a range of values from sorted set ``name`` between 

4633 ``start`` and ``end`` sorted in ascending order. 

4634 

4635 ``start`` and ``end`` can be negative, indicating the end of the range. 

4636 

4637 ``desc`` a boolean indicating whether to sort the results in reversed 

4638 order. 

4639 

4640 ``withscores`` indicates to return the scores along with the values. 

4641 The return type is a list of (value, score) pairs. 

4642 

4643 ``score_cast_func`` a callable used to cast the score return value. 

4644 

4645 ``byscore`` when set to True, returns the range of elements from the 

4646 sorted set having scores equal or between ``start`` and ``end``. 

4647 

4648 ``bylex`` when set to True, returns the range of elements from the 

4649 sorted set between the ``start`` and ``end`` lexicographical closed 

4650 range intervals. 

4651 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

4652 whether the range interval is exclusive or inclusive, respectively. 

4653 

4654 ``offset`` and ``num`` are specified, then return a slice of the range. 

4655 Can't be provided when using ``bylex``. 

4656 

4657 For more information, see https://redis.io/commands/zrange 

4658 """ 

4659 # Need to support ``desc`` also when using old redis version 

4660 # because it was supported in 3.5.3 (of redis-py) 

4661 if not byscore and not bylex and (offset is None and num is None) and desc: 

4662 return self.zrevrange(name, start, end, withscores, score_cast_func) 

4663 

4664 return self._zrange( 

4665 "ZRANGE", 

4666 None, 

4667 name, 

4668 start, 

4669 end, 

4670 desc, 

4671 byscore, 

4672 bylex, 

4673 withscores, 

4674 score_cast_func, 

4675 offset, 

4676 num, 

4677 ) 

4678 

4679 def zrevrange( 

4680 self, 

4681 name: KeyT, 

4682 start: int, 

4683 end: int, 

4684 withscores: bool = False, 

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

4686 ) -> ResponseT: 

4687 """ 

4688 Return a range of values from sorted set ``name`` between 

4689 ``start`` and ``end`` sorted in descending order. 

4690 

4691 ``start`` and ``end`` can be negative, indicating the end of the range. 

4692 

4693 ``withscores`` indicates to return the scores along with the values 

4694 The return type is a list of (value, score) pairs 

4695 

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

4697 

4698 For more information, see https://redis.io/commands/zrevrange 

4699 """ 

4700 pieces = ["ZREVRANGE", name, start, end] 

4701 if withscores: 

4702 pieces.append(b"WITHSCORES") 

4703 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4704 options["keys"] = name 

4705 return self.execute_command(*pieces, **options) 

4706 

4707 def zrangestore( 

4708 self, 

4709 dest: KeyT, 

4710 name: KeyT, 

4711 start: int, 

4712 end: int, 

4713 byscore: bool = False, 

4714 bylex: bool = False, 

4715 desc: bool = False, 

4716 offset: Optional[int] = None, 

4717 num: Optional[int] = None, 

4718 ) -> ResponseT: 

4719 """ 

4720 Stores in ``dest`` the result of a range of values from sorted set 

4721 ``name`` between ``start`` and ``end`` sorted in ascending order. 

4722 

4723 ``start`` and ``end`` can be negative, indicating the end of the range. 

4724 

4725 ``byscore`` when set to True, returns the range of elements from the 

4726 sorted set having scores equal or between ``start`` and ``end``. 

4727 

4728 ``bylex`` when set to True, returns the range of elements from the 

4729 sorted set between the ``start`` and ``end`` lexicographical closed 

4730 range intervals. 

4731 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

4732 whether the range interval is exclusive or inclusive, respectively. 

4733 

4734 ``desc`` a boolean indicating whether to sort the results in reversed 

4735 order. 

4736 

4737 ``offset`` and ``num`` are specified, then return a slice of the range. 

4738 Can't be provided when using ``bylex``. 

4739 

4740 For more information, see https://redis.io/commands/zrangestore 

4741 """ 

4742 return self._zrange( 

4743 "ZRANGESTORE", 

4744 dest, 

4745 name, 

4746 start, 

4747 end, 

4748 desc, 

4749 byscore, 

4750 bylex, 

4751 False, 

4752 None, 

4753 offset, 

4754 num, 

4755 ) 

4756 

4757 def zrangebylex( 

4758 self, 

4759 name: KeyT, 

4760 min: EncodableT, 

4761 max: EncodableT, 

4762 start: Optional[int] = None, 

4763 num: Optional[int] = None, 

4764 ) -> ResponseT: 

4765 """ 

4766 Return the lexicographical range of values from sorted set ``name`` 

4767 between ``min`` and ``max``. 

4768 

4769 If ``start`` and ``num`` are specified, then return a slice of the 

4770 range. 

4771 

4772 For more information, see https://redis.io/commands/zrangebylex 

4773 """ 

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

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

4776 pieces = ["ZRANGEBYLEX", name, min, max] 

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

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

4779 return self.execute_command(*pieces, keys=[name]) 

4780 

4781 def zrevrangebylex( 

4782 self, 

4783 name: KeyT, 

4784 max: EncodableT, 

4785 min: EncodableT, 

4786 start: Optional[int] = None, 

4787 num: Optional[int] = None, 

4788 ) -> ResponseT: 

4789 """ 

4790 Return the reversed lexicographical range of values from sorted set 

4791 ``name`` between ``max`` and ``min``. 

4792 

4793 If ``start`` and ``num`` are specified, then return a slice of the 

4794 range. 

4795 

4796 For more information, see https://redis.io/commands/zrevrangebylex 

4797 """ 

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

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

4800 pieces = ["ZREVRANGEBYLEX", name, max, min] 

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

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

4803 return self.execute_command(*pieces, keys=[name]) 

4804 

4805 def zrangebyscore( 

4806 self, 

4807 name: KeyT, 

4808 min: ZScoreBoundT, 

4809 max: ZScoreBoundT, 

4810 start: Optional[int] = None, 

4811 num: Optional[int] = None, 

4812 withscores: bool = False, 

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

4814 ) -> ResponseT: 

4815 """ 

4816 Return a range of values from the sorted set ``name`` with scores 

4817 between ``min`` and ``max``. 

4818 

4819 If ``start`` and ``num`` are specified, then return a slice 

4820 of the range. 

4821 

4822 ``withscores`` indicates to return the scores along with the values. 

4823 The return type is a list of (value, score) pairs 

4824 

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

4826 

4827 For more information, see https://redis.io/commands/zrangebyscore 

4828 """ 

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

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

4831 pieces = ["ZRANGEBYSCORE", name, min, max] 

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

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

4834 if withscores: 

4835 pieces.append("WITHSCORES") 

4836 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4837 options["keys"] = [name] 

4838 return self.execute_command(*pieces, **options) 

4839 

4840 def zrevrangebyscore( 

4841 self, 

4842 name: KeyT, 

4843 max: ZScoreBoundT, 

4844 min: ZScoreBoundT, 

4845 start: Optional[int] = None, 

4846 num: Optional[int] = None, 

4847 withscores: bool = False, 

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

4849 ): 

4850 """ 

4851 Return a range of values from the sorted set ``name`` with scores 

4852 between ``min`` and ``max`` in descending order. 

4853 

4854 If ``start`` and ``num`` are specified, then return a slice 

4855 of the range. 

4856 

4857 ``withscores`` indicates to return the scores along with the values. 

4858 The return type is a list of (value, score) pairs 

4859 

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

4861 

4862 For more information, see https://redis.io/commands/zrevrangebyscore 

4863 """ 

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

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

4866 pieces = ["ZREVRANGEBYSCORE", name, max, min] 

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

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

4869 if withscores: 

4870 pieces.append("WITHSCORES") 

4871 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4872 options["keys"] = [name] 

4873 return self.execute_command(*pieces, **options) 

4874 

4875 def zrank( 

4876 self, 

4877 name: KeyT, 

4878 value: EncodableT, 

4879 withscore: bool = False, 

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

4881 ) -> ResponseT: 

4882 """ 

4883 Returns a 0-based value indicating the rank of ``value`` in sorted set 

4884 ``name``. 

4885 The optional WITHSCORE argument supplements the command's 

4886 reply with the score of the element returned. 

4887 

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

4889 

4890 For more information, see https://redis.io/commands/zrank 

4891 """ 

4892 pieces = ["ZRANK", name, value] 

4893 if withscore: 

4894 pieces.append("WITHSCORE") 

4895 

4896 options = {"withscore": withscore, "score_cast_func": score_cast_func} 

4897 

4898 return self.execute_command(*pieces, **options) 

4899 

4900 def zrem(self, name: KeyT, *values: FieldT) -> ResponseT: 

4901 """ 

4902 Remove member ``values`` from sorted set ``name`` 

4903 

4904 For more information, see https://redis.io/commands/zrem 

4905 """ 

4906 return self.execute_command("ZREM", name, *values) 

4907 

4908 def zremrangebylex(self, name: KeyT, min: EncodableT, max: EncodableT) -> ResponseT: 

4909 """ 

4910 Remove all elements in the sorted set ``name`` between the 

4911 lexicographical range specified by ``min`` and ``max``. 

4912 

4913 Returns the number of elements removed. 

4914 

4915 For more information, see https://redis.io/commands/zremrangebylex 

4916 """ 

4917 return self.execute_command("ZREMRANGEBYLEX", name, min, max) 

4918 

4919 def zremrangebyrank(self, name: KeyT, min: int, max: int) -> ResponseT: 

4920 """ 

4921 Remove all elements in the sorted set ``name`` with ranks between 

4922 ``min`` and ``max``. Values are 0-based, ordered from smallest score 

4923 to largest. Values can be negative indicating the highest scores. 

4924 Returns the number of elements removed 

4925 

4926 For more information, see https://redis.io/commands/zremrangebyrank 

4927 """ 

4928 return self.execute_command("ZREMRANGEBYRANK", name, min, max) 

4929 

4930 def zremrangebyscore( 

4931 self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT 

4932 ) -> ResponseT: 

4933 """ 

4934 Remove all elements in the sorted set ``name`` with scores 

4935 between ``min`` and ``max``. Returns the number of elements removed. 

4936 

4937 For more information, see https://redis.io/commands/zremrangebyscore 

4938 """ 

4939 return self.execute_command("ZREMRANGEBYSCORE", name, min, max) 

4940 

4941 def zrevrank( 

4942 self, 

4943 name: KeyT, 

4944 value: EncodableT, 

4945 withscore: bool = False, 

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

4947 ) -> ResponseT: 

4948 """ 

4949 Returns a 0-based value indicating the descending rank of 

4950 ``value`` in sorted set ``name``. 

4951 The optional ``withscore`` argument supplements the command's 

4952 reply with the score of the element returned. 

4953 

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

4955 

4956 For more information, see https://redis.io/commands/zrevrank 

4957 """ 

4958 pieces = ["ZREVRANK", name, value] 

4959 if withscore: 

4960 pieces.append("WITHSCORE") 

4961 

4962 options = {"withscore": withscore, "score_cast_func": score_cast_func} 

4963 

4964 return self.execute_command(*pieces, **options) 

4965 

4966 def zscore(self, name: KeyT, value: EncodableT) -> ResponseT: 

4967 """ 

4968 Return the score of element ``value`` in sorted set ``name`` 

4969 

4970 For more information, see https://redis.io/commands/zscore 

4971 """ 

4972 return self.execute_command("ZSCORE", name, value, keys=[name]) 

4973 

4974 def zunion( 

4975 self, 

4976 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4977 aggregate: Optional[str] = None, 

4978 withscores: bool = False, 

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

4980 ) -> ResponseT: 

4981 """ 

4982 Return the union of multiple sorted sets specified by ``keys``. 

4983 ``keys`` can be provided as dictionary of keys and their weights. 

4984 Scores will be aggregated based on the ``aggregate``, or SUM if 

4985 none is provided. 

4986 

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

4988 

4989 For more information, see https://redis.io/commands/zunion 

4990 """ 

4991 return self._zaggregate( 

4992 "ZUNION", 

4993 None, 

4994 keys, 

4995 aggregate, 

4996 withscores=withscores, 

4997 score_cast_func=score_cast_func, 

4998 ) 

4999 

5000 def zunionstore( 

5001 self, 

5002 dest: KeyT, 

5003 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

5004 aggregate: Optional[str] = None, 

5005 ) -> ResponseT: 

5006 """ 

5007 Union multiple sorted sets specified by ``keys`` into 

5008 a new sorted set, ``dest``. Scores in the destination will be 

5009 aggregated based on the ``aggregate``, or SUM if none is provided. 

5010 

5011 For more information, see https://redis.io/commands/zunionstore 

5012 """ 

5013 return self._zaggregate("ZUNIONSTORE", dest, keys, aggregate) 

5014 

5015 def zmscore(self, key: KeyT, members: List[str]) -> ResponseT: 

5016 """ 

5017 Returns the scores associated with the specified members 

5018 in the sorted set stored at key. 

5019 ``members`` should be a list of the member name. 

5020 Return type is a list of score. 

5021 If the member does not exist, a None will be returned 

5022 in corresponding position. 

5023 

5024 For more information, see https://redis.io/commands/zmscore 

5025 """ 

5026 if not members: 

5027 raise DataError("ZMSCORE members must be a non-empty list") 

5028 pieces = [key] + members 

5029 return self.execute_command("ZMSCORE", *pieces, keys=[key]) 

5030 

5031 def _zaggregate( 

5032 self, 

5033 command: str, 

5034 dest: Union[KeyT, None], 

5035 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

5036 aggregate: Optional[str] = None, 

5037 **options, 

5038 ) -> ResponseT: 

5039 pieces: list[EncodableT] = [command] 

5040 if dest is not None: 

5041 pieces.append(dest) 

5042 pieces.append(len(keys)) 

5043 if isinstance(keys, dict): 

5044 keys, weights = keys.keys(), keys.values() 

5045 else: 

5046 weights = None 

5047 pieces.extend(keys) 

5048 if weights: 

5049 pieces.append(b"WEIGHTS") 

5050 pieces.extend(weights) 

5051 if aggregate: 

5052 if aggregate.upper() in ["SUM", "MIN", "MAX"]: 

5053 pieces.append(b"AGGREGATE") 

5054 pieces.append(aggregate) 

5055 else: 

5056 raise DataError("aggregate can be sum, min or max.") 

5057 if options.get("withscores", False): 

5058 pieces.append(b"WITHSCORES") 

5059 options["keys"] = keys 

5060 return self.execute_command(*pieces, **options) 

5061 

5062 

5063AsyncSortedSetCommands = SortedSetCommands 

5064 

5065 

5066class HyperlogCommands(CommandsProtocol): 

5067 """ 

5068 Redis commands of HyperLogLogs data type. 

5069 see: https://redis.io/topics/data-types-intro#hyperloglogs 

5070 """ 

5071 

5072 def pfadd(self, name: KeyT, *values: FieldT) -> ResponseT: 

5073 """ 

5074 Adds the specified elements to the specified HyperLogLog. 

5075 

5076 For more information, see https://redis.io/commands/pfadd 

5077 """ 

5078 return self.execute_command("PFADD", name, *values) 

5079 

5080 def pfcount(self, *sources: KeyT) -> ResponseT: 

5081 """ 

5082 Return the approximated cardinality of 

5083 the set observed by the HyperLogLog at key(s). 

5084 

5085 For more information, see https://redis.io/commands/pfcount 

5086 """ 

5087 return self.execute_command("PFCOUNT", *sources) 

5088 

5089 def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT: 

5090 """ 

5091 Merge N different HyperLogLogs into a single one. 

5092 

5093 For more information, see https://redis.io/commands/pfmerge 

5094 """ 

5095 return self.execute_command("PFMERGE", dest, *sources) 

5096 

5097 

5098AsyncHyperlogCommands = HyperlogCommands 

5099 

5100 

5101class HashDataPersistOptions(Enum): 

5102 # set the value for each provided key to each 

5103 # provided value only if all do not already exist. 

5104 FNX = "FNX" 

5105 

5106 # set the value for each provided key to each 

5107 # provided value only if all already exist. 

5108 FXX = "FXX" 

5109 

5110 

5111class HashCommands(CommandsProtocol): 

5112 """ 

5113 Redis commands for Hash data type. 

5114 see: https://redis.io/topics/data-types-intro#redis-hashes 

5115 """ 

5116 

5117 def hdel(self, name: str, *keys: str) -> Union[Awaitable[int], int]: 

5118 """ 

5119 Delete ``keys`` from hash ``name`` 

5120 

5121 For more information, see https://redis.io/commands/hdel 

5122 """ 

5123 return self.execute_command("HDEL", name, *keys) 

5124 

5125 def hexists(self, name: str, key: str) -> Union[Awaitable[bool], bool]: 

5126 """ 

5127 Returns a boolean indicating if ``key`` exists within hash ``name`` 

5128 

5129 For more information, see https://redis.io/commands/hexists 

5130 """ 

5131 return self.execute_command("HEXISTS", name, key, keys=[name]) 

5132 

5133 def hget( 

5134 self, name: str, key: str 

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

5136 """ 

5137 Return the value of ``key`` within the hash ``name`` 

5138 

5139 For more information, see https://redis.io/commands/hget 

5140 """ 

5141 return self.execute_command("HGET", name, key, keys=[name]) 

5142 

5143 def hgetall(self, name: str) -> Union[Awaitable[dict], dict]: 

5144 """ 

5145 Return a Python dict of the hash's name/value pairs 

5146 

5147 For more information, see https://redis.io/commands/hgetall 

5148 """ 

5149 return self.execute_command("HGETALL", name, keys=[name]) 

5150 

5151 def hgetdel( 

5152 self, name: str, *keys: str 

5153 ) -> Union[ 

5154 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]] 

5155 ]: 

5156 """ 

5157 Return the value of ``key`` within the hash ``name`` and 

5158 delete the field in the hash. 

5159 This command is similar to HGET, except for the fact that it also deletes 

5160 the key on success from the hash with the provided ```name```. 

5161 

5162 Available since Redis 8.0 

5163 For more information, see https://redis.io/commands/hgetdel 

5164 """ 

5165 if len(keys) == 0: 

5166 raise DataError("'hgetdel' should have at least one key provided") 

5167 

5168 return self.execute_command("HGETDEL", name, "FIELDS", len(keys), *keys) 

5169 

5170 def hgetex( 

5171 self, 

5172 name: KeyT, 

5173 *keys: str, 

5174 ex: Optional[ExpiryT] = None, 

5175 px: Optional[ExpiryT] = None, 

5176 exat: Optional[AbsExpiryT] = None, 

5177 pxat: Optional[AbsExpiryT] = None, 

5178 persist: bool = False, 

5179 ) -> Union[ 

5180 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]] 

5181 ]: 

5182 """ 

5183 Return the values of ``key`` and ``keys`` within the hash ``name`` 

5184 and optionally set their expiration. 

5185 

5186 ``ex`` sets an expire flag on ``kyes`` for ``ex`` seconds. 

5187 

5188 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds. 

5189 

5190 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds, 

5191 specified in unix time. 

5192 

5193 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds, 

5194 specified in unix time. 

5195 

5196 ``persist`` remove the time to live associated with the ``keys``. 

5197 

5198 Available since Redis 8.0 

5199 For more information, see https://redis.io/commands/hgetex 

5200 """ 

5201 if not keys: 

5202 raise DataError("'hgetex' should have at least one key provided") 

5203 

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

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

5206 raise DataError( 

5207 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

5209 ) 

5210 

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

5212 

5213 if persist: 

5214 exp_options.append("PERSIST") 

5215 

5216 return self.execute_command( 

5217 "HGETEX", 

5218 name, 

5219 *exp_options, 

5220 "FIELDS", 

5221 len(keys), 

5222 *keys, 

5223 ) 

5224 

5225 def hincrby( 

5226 self, name: str, key: str, amount: int = 1 

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

5228 """ 

5229 Increment the value of ``key`` in hash ``name`` by ``amount`` 

5230 

5231 For more information, see https://redis.io/commands/hincrby 

5232 """ 

5233 return self.execute_command("HINCRBY", name, key, amount) 

5234 

5235 def hincrbyfloat( 

5236 self, name: str, key: str, amount: float = 1.0 

5237 ) -> Union[Awaitable[float], float]: 

5238 """ 

5239 Increment the value of ``key`` in hash ``name`` by floating ``amount`` 

5240 

5241 For more information, see https://redis.io/commands/hincrbyfloat 

5242 """ 

5243 return self.execute_command("HINCRBYFLOAT", name, key, amount) 

5244 

5245 def hkeys(self, name: str) -> Union[Awaitable[List], List]: 

5246 """ 

5247 Return the list of keys within hash ``name`` 

5248 

5249 For more information, see https://redis.io/commands/hkeys 

5250 """ 

5251 return self.execute_command("HKEYS", name, keys=[name]) 

5252 

5253 def hlen(self, name: str) -> Union[Awaitable[int], int]: 

5254 """ 

5255 Return the number of elements in hash ``name`` 

5256 

5257 For more information, see https://redis.io/commands/hlen 

5258 """ 

5259 return self.execute_command("HLEN", name, keys=[name]) 

5260 

5261 def hset( 

5262 self, 

5263 name: str, 

5264 key: Optional[str] = None, 

5265 value: Optional[str] = None, 

5266 mapping: Optional[dict] = None, 

5267 items: Optional[list] = None, 

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

5269 """ 

5270 Set ``key`` to ``value`` within hash ``name``, 

5271 ``mapping`` accepts a dict of key/value pairs that will be 

5272 added to hash ``name``. 

5273 ``items`` accepts a list of key/value pairs that will be 

5274 added to hash ``name``. 

5275 Returns the number of fields that were added. 

5276 

5277 For more information, see https://redis.io/commands/hset 

5278 """ 

5279 

5280 if key is None and not mapping and not items: 

5281 raise DataError("'hset' with no key value pairs") 

5282 

5283 pieces = [] 

5284 if items: 

5285 pieces.extend(items) 

5286 if key is not None: 

5287 pieces.extend((key, value)) 

5288 if mapping: 

5289 for pair in mapping.items(): 

5290 pieces.extend(pair) 

5291 

5292 return self.execute_command("HSET", name, *pieces) 

5293 

5294 def hsetex( 

5295 self, 

5296 name: str, 

5297 key: Optional[str] = None, 

5298 value: Optional[str] = None, 

5299 mapping: Optional[dict] = None, 

5300 items: Optional[list] = None, 

5301 ex: Optional[ExpiryT] = None, 

5302 px: Optional[ExpiryT] = None, 

5303 exat: Optional[AbsExpiryT] = None, 

5304 pxat: Optional[AbsExpiryT] = None, 

5305 data_persist_option: Optional[HashDataPersistOptions] = None, 

5306 keepttl: bool = False, 

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

5308 """ 

5309 Set ``key`` to ``value`` within hash ``name`` 

5310 

5311 ``mapping`` accepts a dict of key/value pairs that will be 

5312 added to hash ``name``. 

5313 

5314 ``items`` accepts a list of key/value pairs that will be 

5315 added to hash ``name``. 

5316 

5317 ``ex`` sets an expire flag on ``keys`` for ``ex`` seconds. 

5318 

5319 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds. 

5320 

5321 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds, 

5322 specified in unix time. 

5323 

5324 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds, 

5325 specified in unix time. 

5326 

5327 ``data_persist_option`` can be set to ``FNX`` or ``FXX`` to control the 

5328 behavior of the command. 

5329 ``FNX`` will set the value for each provided key to each 

5330 provided value only if all do not already exist. 

5331 ``FXX`` will set the value for each provided key to each 

5332 provided value only if all already exist. 

5333 

5334 ``keepttl`` if True, retain the time to live associated with the keys. 

5335 

5336 Returns the number of fields that were added. 

5337 

5338 Available since Redis 8.0 

5339 For more information, see https://redis.io/commands/hsetex 

5340 """ 

5341 if key is None and not mapping and not items: 

5342 raise DataError("'hsetex' with no key value pairs") 

5343 

5344 if items and len(items) % 2 != 0: 

5345 raise DataError( 

5346 "'hsetex' with odd number of items. " 

5347 "'items' must contain a list of key/value pairs." 

5348 ) 

5349 

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

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

5352 raise DataError( 

5353 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

5355 ) 

5356 

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

5358 if data_persist_option: 

5359 exp_options.append(data_persist_option.value) 

5360 

5361 if keepttl: 

5362 exp_options.append("KEEPTTL") 

5363 

5364 pieces = [] 

5365 if items: 

5366 pieces.extend(items) 

5367 if key is not None: 

5368 pieces.extend((key, value)) 

5369 if mapping: 

5370 for pair in mapping.items(): 

5371 pieces.extend(pair) 

5372 

5373 return self.execute_command( 

5374 "HSETEX", name, *exp_options, "FIELDS", int(len(pieces) / 2), *pieces 

5375 ) 

5376 

5377 def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool]: 

5378 """ 

5379 Set ``key`` to ``value`` within hash ``name`` if ``key`` does not 

5380 exist. Returns 1 if HSETNX created a field, otherwise 0. 

5381 

5382 For more information, see https://redis.io/commands/hsetnx 

5383 """ 

5384 return self.execute_command("HSETNX", name, key, value) 

5385 

5386 @deprecated_function( 

5387 version="4.0.0", 

5388 reason="Use 'hset' instead.", 

5389 name="hmset", 

5390 ) 

5391 def hmset(self, name: str, mapping: dict) -> Union[Awaitable[str], str]: 

5392 """ 

5393 Set key to value within hash ``name`` for each corresponding 

5394 key and value from the ``mapping`` dict. 

5395 

5396 For more information, see https://redis.io/commands/hmset 

5397 """ 

5398 if not mapping: 

5399 raise DataError("'hmset' with 'mapping' of length 0") 

5400 items = [] 

5401 for pair in mapping.items(): 

5402 items.extend(pair) 

5403 return self.execute_command("HMSET", name, *items) 

5404 

5405 def hmget(self, name: str, keys: List, *args: List) -> Union[Awaitable[List], List]: 

5406 """ 

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

5408 

5409 For more information, see https://redis.io/commands/hmget 

5410 """ 

5411 args = list_or_args(keys, args) 

5412 return self.execute_command("HMGET", name, *args, keys=[name]) 

5413 

5414 def hvals(self, name: str) -> Union[Awaitable[List], List]: 

5415 """ 

5416 Return the list of values within hash ``name`` 

5417 

5418 For more information, see https://redis.io/commands/hvals 

5419 """ 

5420 return self.execute_command("HVALS", name, keys=[name]) 

5421 

5422 def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]: 

5423 """ 

5424 Return the number of bytes stored in the value of ``key`` 

5425 within hash ``name`` 

5426 

5427 For more information, see https://redis.io/commands/hstrlen 

5428 """ 

5429 return self.execute_command("HSTRLEN", name, key, keys=[name]) 

5430 

5431 def hexpire( 

5432 self, 

5433 name: KeyT, 

5434 seconds: ExpiryT, 

5435 *fields: str, 

5436 nx: bool = False, 

5437 xx: bool = False, 

5438 gt: bool = False, 

5439 lt: bool = False, 

5440 ) -> ResponseT: 

5441 """ 

5442 Sets or updates the expiration time for fields within a hash key, using relative 

5443 time in seconds. 

5444 

5445 If a field already has an expiration time, the behavior of the update can be 

5446 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5447 

5448 The return value provides detailed information about the outcome for each field. 

5449 

5450 For more information, see https://redis.io/commands/hexpire 

5451 

5452 Args: 

5453 name: The name of the hash key. 

5454 seconds: Expiration time in seconds, relative. Can be an integer, or a 

5455 Python `timedelta` object. 

5456 fields: List of fields within the hash to apply the expiration time to. 

5457 nx: Set expiry only when the field has no expiry. 

5458 xx: Set expiry only when the field has an existing expiry. 

5459 gt: Set expiry only when the new expiry is greater than the current one. 

5460 lt: Set expiry only when the new expiry is less than the current one. 

5461 

5462 Returns: 

5463 Returns a list which contains for each field in the request: 

5464 - `-2` if the field does not exist, or if the key does not exist. 

5465 - `0` if the specified NX | XX | GT | LT condition was not met. 

5466 - `1` if the expiration time was set or updated. 

5467 - `2` if the field was deleted because the specified expiration time is 

5468 in the past. 

5469 """ 

5470 conditions = [nx, xx, gt, lt] 

5471 if sum(conditions) > 1: 

5472 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5473 

5474 if isinstance(seconds, datetime.timedelta): 

5475 seconds = int(seconds.total_seconds()) 

5476 

5477 options = [] 

5478 if nx: 

5479 options.append("NX") 

5480 if xx: 

5481 options.append("XX") 

5482 if gt: 

5483 options.append("GT") 

5484 if lt: 

5485 options.append("LT") 

5486 

5487 return self.execute_command( 

5488 "HEXPIRE", name, seconds, *options, "FIELDS", len(fields), *fields 

5489 ) 

5490 

5491 def hpexpire( 

5492 self, 

5493 name: KeyT, 

5494 milliseconds: ExpiryT, 

5495 *fields: str, 

5496 nx: bool = False, 

5497 xx: bool = False, 

5498 gt: bool = False, 

5499 lt: bool = False, 

5500 ) -> ResponseT: 

5501 """ 

5502 Sets or updates the expiration time for fields within a hash key, using relative 

5503 time in milliseconds. 

5504 

5505 If a field already has an expiration time, the behavior of the update can be 

5506 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5507 

5508 The return value provides detailed information about the outcome for each field. 

5509 

5510 For more information, see https://redis.io/commands/hpexpire 

5511 

5512 Args: 

5513 name: The name of the hash key. 

5514 milliseconds: Expiration time in milliseconds, relative. Can be an integer, 

5515 or a Python `timedelta` object. 

5516 fields: List of fields within the hash to apply the expiration time to. 

5517 nx: Set expiry only when the field has no expiry. 

5518 xx: Set expiry only when the field has an existing expiry. 

5519 gt: Set expiry only when the new expiry is greater than the current one. 

5520 lt: Set expiry only when the new expiry is less than the current one. 

5521 

5522 Returns: 

5523 Returns a list which contains for each field in the request: 

5524 - `-2` if the field does not exist, or if the key does not exist. 

5525 - `0` if the specified NX | XX | GT | LT condition was not met. 

5526 - `1` if the expiration time was set or updated. 

5527 - `2` if the field was deleted because the specified expiration time is 

5528 in the past. 

5529 """ 

5530 conditions = [nx, xx, gt, lt] 

5531 if sum(conditions) > 1: 

5532 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5533 

5534 if isinstance(milliseconds, datetime.timedelta): 

5535 milliseconds = int(milliseconds.total_seconds() * 1000) 

5536 

5537 options = [] 

5538 if nx: 

5539 options.append("NX") 

5540 if xx: 

5541 options.append("XX") 

5542 if gt: 

5543 options.append("GT") 

5544 if lt: 

5545 options.append("LT") 

5546 

5547 return self.execute_command( 

5548 "HPEXPIRE", name, milliseconds, *options, "FIELDS", len(fields), *fields 

5549 ) 

5550 

5551 def hexpireat( 

5552 self, 

5553 name: KeyT, 

5554 unix_time_seconds: AbsExpiryT, 

5555 *fields: str, 

5556 nx: bool = False, 

5557 xx: bool = False, 

5558 gt: bool = False, 

5559 lt: bool = False, 

5560 ) -> ResponseT: 

5561 """ 

5562 Sets or updates the expiration time for fields within a hash key, using an 

5563 absolute Unix timestamp in seconds. 

5564 

5565 If a field already has an expiration time, the behavior of the update can be 

5566 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5567 

5568 The return value provides detailed information about the outcome for each field. 

5569 

5570 For more information, see https://redis.io/commands/hexpireat 

5571 

5572 Args: 

5573 name: The name of the hash key. 

5574 unix_time_seconds: Expiration time as Unix timestamp in seconds. Can be an 

5575 integer or a Python `datetime` object. 

5576 fields: List of fields within the hash to apply the expiration time to. 

5577 nx: Set expiry only when the field has no expiry. 

5578 xx: Set expiry only when the field has an existing expiration time. 

5579 gt: Set expiry only when the new expiry is greater than the current one. 

5580 lt: Set expiry only when the new expiry is less than the current one. 

5581 

5582 Returns: 

5583 Returns a list which contains for each field in the request: 

5584 - `-2` if the field does not exist, or if the key does not exist. 

5585 - `0` if the specified NX | XX | GT | LT condition was not met. 

5586 - `1` if the expiration time was set or updated. 

5587 - `2` if the field was deleted because the specified expiration time is 

5588 in the past. 

5589 """ 

5590 conditions = [nx, xx, gt, lt] 

5591 if sum(conditions) > 1: 

5592 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5593 

5594 if isinstance(unix_time_seconds, datetime.datetime): 

5595 unix_time_seconds = int(unix_time_seconds.timestamp()) 

5596 

5597 options = [] 

5598 if nx: 

5599 options.append("NX") 

5600 if xx: 

5601 options.append("XX") 

5602 if gt: 

5603 options.append("GT") 

5604 if lt: 

5605 options.append("LT") 

5606 

5607 return self.execute_command( 

5608 "HEXPIREAT", 

5609 name, 

5610 unix_time_seconds, 

5611 *options, 

5612 "FIELDS", 

5613 len(fields), 

5614 *fields, 

5615 ) 

5616 

5617 def hpexpireat( 

5618 self, 

5619 name: KeyT, 

5620 unix_time_milliseconds: AbsExpiryT, 

5621 *fields: str, 

5622 nx: bool = False, 

5623 xx: bool = False, 

5624 gt: bool = False, 

5625 lt: bool = False, 

5626 ) -> ResponseT: 

5627 """ 

5628 Sets or updates the expiration time for fields within a hash key, using an 

5629 absolute Unix timestamp in milliseconds. 

5630 

5631 If a field already has an expiration time, the behavior of the update can be 

5632 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5633 

5634 The return value provides detailed information about the outcome for each field. 

5635 

5636 For more information, see https://redis.io/commands/hpexpireat 

5637 

5638 Args: 

5639 name: The name of the hash key. 

5640 unix_time_milliseconds: Expiration time as Unix timestamp in milliseconds. 

5641 Can be an integer or a Python `datetime` object. 

5642 fields: List of fields within the hash to apply the expiry. 

5643 nx: Set expiry only when the field has no expiry. 

5644 xx: Set expiry only when the field has an existing expiry. 

5645 gt: Set expiry only when the new expiry is greater than the current one. 

5646 lt: Set expiry only when the new expiry is less than the current one. 

5647 

5648 Returns: 

5649 Returns a list which contains for each field in the request: 

5650 - `-2` if the field does not exist, or if the key does not exist. 

5651 - `0` if the specified NX | XX | GT | LT condition was not met. 

5652 - `1` if the expiration time was set or updated. 

5653 - `2` if the field was deleted because the specified expiration time is 

5654 in the past. 

5655 """ 

5656 conditions = [nx, xx, gt, lt] 

5657 if sum(conditions) > 1: 

5658 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5659 

5660 if isinstance(unix_time_milliseconds, datetime.datetime): 

5661 unix_time_milliseconds = int(unix_time_milliseconds.timestamp() * 1000) 

5662 

5663 options = [] 

5664 if nx: 

5665 options.append("NX") 

5666 if xx: 

5667 options.append("XX") 

5668 if gt: 

5669 options.append("GT") 

5670 if lt: 

5671 options.append("LT") 

5672 

5673 return self.execute_command( 

5674 "HPEXPIREAT", 

5675 name, 

5676 unix_time_milliseconds, 

5677 *options, 

5678 "FIELDS", 

5679 len(fields), 

5680 *fields, 

5681 ) 

5682 

5683 def hpersist(self, name: KeyT, *fields: str) -> ResponseT: 

5684 """ 

5685 Removes the expiration time for each specified field in a hash. 

5686 

5687 For more information, see https://redis.io/commands/hpersist 

5688 

5689 Args: 

5690 name: The name of the hash key. 

5691 fields: A list of fields within the hash from which to remove the 

5692 expiration time. 

5693 

5694 Returns: 

5695 Returns a list which contains for each field in the request: 

5696 - `-2` if the field does not exist, or if the key does not exist. 

5697 - `-1` if the field exists but has no associated expiration time. 

5698 - `1` if the expiration time was successfully removed from the field. 

5699 """ 

5700 return self.execute_command("HPERSIST", name, "FIELDS", len(fields), *fields) 

5701 

5702 def hexpiretime(self, key: KeyT, *fields: str) -> ResponseT: 

5703 """ 

5704 Returns the expiration times of hash fields as Unix timestamps in seconds. 

5705 

5706 For more information, see https://redis.io/commands/hexpiretime 

5707 

5708 Args: 

5709 key: The hash key. 

5710 fields: A list of fields within the hash for which to get the expiration 

5711 time. 

5712 

5713 Returns: 

5714 Returns a list which contains for each field in the request: 

5715 - `-2` if the field does not exist, or if the key does not exist. 

5716 - `-1` if the field exists but has no associated expire time. 

5717 - A positive integer representing the expiration Unix timestamp in 

5718 seconds, if the field has an associated expiration time. 

5719 """ 

5720 return self.execute_command( 

5721 "HEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key] 

5722 ) 

5723 

5724 def hpexpiretime(self, key: KeyT, *fields: str) -> ResponseT: 

5725 """ 

5726 Returns the expiration times of hash fields as Unix timestamps in milliseconds. 

5727 

5728 For more information, see https://redis.io/commands/hpexpiretime 

5729 

5730 Args: 

5731 key: The hash key. 

5732 fields: A list of fields within the hash for which to get the expiration 

5733 time. 

5734 

5735 Returns: 

5736 Returns a list which contains for each field in the request: 

5737 - `-2` if the field does not exist, or if the key does not exist. 

5738 - `-1` if the field exists but has no associated expire time. 

5739 - A positive integer representing the expiration Unix timestamp in 

5740 milliseconds, if the field has an associated expiration time. 

5741 """ 

5742 return self.execute_command( 

5743 "HPEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key] 

5744 ) 

5745 

5746 def httl(self, key: KeyT, *fields: str) -> ResponseT: 

5747 """ 

5748 Returns the TTL (Time To Live) in seconds for each specified field within a hash 

5749 key. 

5750 

5751 For more information, see https://redis.io/commands/httl 

5752 

5753 Args: 

5754 key: The hash key. 

5755 fields: A list of fields within the hash for which to get the TTL. 

5756 

5757 Returns: 

5758 Returns a list which contains for each field in the request: 

5759 - `-2` if the field does not exist, or if the key does not exist. 

5760 - `-1` if the field exists but has no associated expire time. 

5761 - A positive integer representing the TTL in seconds if the field has 

5762 an associated expiration time. 

5763 """ 

5764 return self.execute_command( 

5765 "HTTL", key, "FIELDS", len(fields), *fields, keys=[key] 

5766 ) 

5767 

5768 def hpttl(self, key: KeyT, *fields: str) -> ResponseT: 

5769 """ 

5770 Returns the TTL (Time To Live) in milliseconds for each specified field within a 

5771 hash key. 

5772 

5773 For more information, see https://redis.io/commands/hpttl 

5774 

5775 Args: 

5776 key: The hash key. 

5777 fields: A list of fields within the hash for which to get the TTL. 

5778 

5779 Returns: 

5780 Returns a list which contains for each field in the request: 

5781 - `-2` if the field does not exist, or if the key does not exist. 

5782 - `-1` if the field exists but has no associated expire time. 

5783 - A positive integer representing the TTL in milliseconds if the field 

5784 has an associated expiration time. 

5785 """ 

5786 return self.execute_command( 

5787 "HPTTL", key, "FIELDS", len(fields), *fields, keys=[key] 

5788 ) 

5789 

5790 

5791AsyncHashCommands = HashCommands 

5792 

5793 

5794class Script: 

5795 """ 

5796 An executable Lua script object returned by ``register_script`` 

5797 """ 

5798 

5799 def __init__(self, registered_client: "redis.client.Redis", script: ScriptTextT): 

5800 self.registered_client = registered_client 

5801 self.script = script 

5802 # Precalculate and store the SHA1 hex digest of the script. 

5803 

5804 if isinstance(script, str): 

5805 # We need the encoding from the client in order to generate an 

5806 # accurate byte representation of the script 

5807 encoder = self.get_encoder() 

5808 script = encoder.encode(script) 

5809 self.sha = hashlib.sha1(script).hexdigest() 

5810 

5811 def __call__( 

5812 self, 

5813 keys: Union[Sequence[KeyT], None] = None, 

5814 args: Union[Iterable[EncodableT], None] = None, 

5815 client: Union["redis.client.Redis", None] = None, 

5816 ): 

5817 """Execute the script, passing any required ``args``""" 

5818 keys = keys or [] 

5819 args = args or [] 

5820 if client is None: 

5821 client = self.registered_client 

5822 args = tuple(keys) + tuple(args) 

5823 # make sure the Redis server knows about the script 

5824 from redis.client import Pipeline 

5825 

5826 if isinstance(client, Pipeline): 

5827 # Make sure the pipeline can register the script before executing. 

5828 client.scripts.add(self) 

5829 try: 

5830 return client.evalsha(self.sha, len(keys), *args) 

5831 except NoScriptError: 

5832 # Maybe the client is pointed to a different server than the client 

5833 # that created this instance? 

5834 # Overwrite the sha just in case there was a discrepancy. 

5835 self.sha = client.script_load(self.script) 

5836 return client.evalsha(self.sha, len(keys), *args) 

5837 

5838 def get_encoder(self): 

5839 """Get the encoder to encode string scripts into bytes.""" 

5840 try: 

5841 return self.registered_client.get_encoder() 

5842 except AttributeError: 

5843 # DEPRECATED 

5844 # In version <=4.1.2, this was the code we used to get the encoder. 

5845 # However, after 4.1.2 we added support for scripting in clustered 

5846 # redis. ClusteredRedis doesn't have a `.connection_pool` attribute 

5847 # so we changed the Script class to use 

5848 # `self.registered_client.get_encoder` (see above). 

5849 # However, that is technically a breaking change, as consumers who 

5850 # use Scripts directly might inject a `registered_client` that 

5851 # doesn't have a `.get_encoder` field. This try/except prevents us 

5852 # from breaking backward-compatibility. Ideally, it would be 

5853 # removed in the next major release. 

5854 return self.registered_client.connection_pool.get_encoder() 

5855 

5856 

5857class AsyncScript: 

5858 """ 

5859 An executable Lua script object returned by ``register_script`` 

5860 """ 

5861 

5862 def __init__( 

5863 self, 

5864 registered_client: "redis.asyncio.client.Redis", 

5865 script: ScriptTextT, 

5866 ): 

5867 self.registered_client = registered_client 

5868 self.script = script 

5869 # Precalculate and store the SHA1 hex digest of the script. 

5870 

5871 if isinstance(script, str): 

5872 # We need the encoding from the client in order to generate an 

5873 # accurate byte representation of the script 

5874 try: 

5875 encoder = registered_client.connection_pool.get_encoder() 

5876 except AttributeError: 

5877 # Cluster 

5878 encoder = registered_client.get_encoder() 

5879 script = encoder.encode(script) 

5880 self.sha = hashlib.sha1(script).hexdigest() 

5881 

5882 async def __call__( 

5883 self, 

5884 keys: Union[Sequence[KeyT], None] = None, 

5885 args: Union[Iterable[EncodableT], None] = None, 

5886 client: Union["redis.asyncio.client.Redis", None] = None, 

5887 ): 

5888 """Execute the script, passing any required ``args``""" 

5889 keys = keys or [] 

5890 args = args or [] 

5891 if client is None: 

5892 client = self.registered_client 

5893 args = tuple(keys) + tuple(args) 

5894 # make sure the Redis server knows about the script 

5895 from redis.asyncio.client import Pipeline 

5896 

5897 if isinstance(client, Pipeline): 

5898 # Make sure the pipeline can register the script before executing. 

5899 client.scripts.add(self) 

5900 try: 

5901 return await client.evalsha(self.sha, len(keys), *args) 

5902 except NoScriptError: 

5903 # Maybe the client is pointed to a different server than the client 

5904 # that created this instance? 

5905 # Overwrite the sha just in case there was a discrepancy. 

5906 self.sha = await client.script_load(self.script) 

5907 return await client.evalsha(self.sha, len(keys), *args) 

5908 

5909 

5910class PubSubCommands(CommandsProtocol): 

5911 """ 

5912 Redis PubSub commands. 

5913 see https://redis.io/topics/pubsub 

5914 """ 

5915 

5916 def publish(self, channel: ChannelT, message: EncodableT, **kwargs) -> ResponseT: 

5917 """ 

5918 Publish ``message`` on ``channel``. 

5919 Returns the number of subscribers the message was delivered to. 

5920 

5921 For more information, see https://redis.io/commands/publish 

5922 """ 

5923 return self.execute_command("PUBLISH", channel, message, **kwargs) 

5924 

5925 def spublish(self, shard_channel: ChannelT, message: EncodableT) -> ResponseT: 

5926 """ 

5927 Posts a message to the given shard channel. 

5928 Returns the number of clients that received the message 

5929 

5930 For more information, see https://redis.io/commands/spublish 

5931 """ 

5932 return self.execute_command("SPUBLISH", shard_channel, message) 

5933 

5934 def pubsub_channels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

5935 """ 

5936 Return a list of channels that have at least one subscriber 

5937 

5938 For more information, see https://redis.io/commands/pubsub-channels 

5939 """ 

5940 return self.execute_command("PUBSUB CHANNELS", pattern, **kwargs) 

5941 

5942 def pubsub_shardchannels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

5943 """ 

5944 Return a list of shard_channels that have at least one subscriber 

5945 

5946 For more information, see https://redis.io/commands/pubsub-shardchannels 

5947 """ 

5948 return self.execute_command("PUBSUB SHARDCHANNELS", pattern, **kwargs) 

5949 

5950 def pubsub_numpat(self, **kwargs) -> ResponseT: 

5951 """ 

5952 Returns the number of subscriptions to patterns 

5953 

5954 For more information, see https://redis.io/commands/pubsub-numpat 

5955 """ 

5956 return self.execute_command("PUBSUB NUMPAT", **kwargs) 

5957 

5958 def pubsub_numsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

5959 """ 

5960 Return a list of (channel, number of subscribers) tuples 

5961 for each channel given in ``*args`` 

5962 

5963 For more information, see https://redis.io/commands/pubsub-numsub 

5964 """ 

5965 return self.execute_command("PUBSUB NUMSUB", *args, **kwargs) 

5966 

5967 def pubsub_shardnumsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

5968 """ 

5969 Return a list of (shard_channel, number of subscribers) tuples 

5970 for each channel given in ``*args`` 

5971 

5972 For more information, see https://redis.io/commands/pubsub-shardnumsub 

5973 """ 

5974 return self.execute_command("PUBSUB SHARDNUMSUB", *args, **kwargs) 

5975 

5976 

5977AsyncPubSubCommands = PubSubCommands 

5978 

5979 

5980class ScriptCommands(CommandsProtocol): 

5981 """ 

5982 Redis Lua script commands. see: 

5983 https://redis.io/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/ 

5984 """ 

5985 

5986 def _eval( 

5987 self, 

5988 command: str, 

5989 script: str, 

5990 numkeys: int, 

5991 *keys_and_args: Union[KeyT, EncodableT], 

5992 ) -> Union[Awaitable[str], str]: 

5993 return self.execute_command(command, script, numkeys, *keys_and_args) 

5994 

5995 def eval( 

5996 self, script: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT] 

5997 ) -> Union[Awaitable[str], str]: 

5998 """ 

5999 Execute the Lua ``script``, specifying the ``numkeys`` the script 

6000 will touch and the key names and argument values in ``keys_and_args``. 

6001 Returns the result of the script. 

6002 

6003 In practice, use the object returned by ``register_script``. This 

6004 function exists purely for Redis API completion. 

6005 

6006 For more information, see https://redis.io/commands/eval 

6007 """ 

6008 return self._eval("EVAL", script, numkeys, *keys_and_args) 

6009 

6010 def eval_ro( 

6011 self, script: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT] 

6012 ) -> Union[Awaitable[str], str]: 

6013 """ 

6014 The read-only variant of the EVAL command 

6015 

6016 Execute the read-only Lua ``script`` specifying the ``numkeys`` the script 

6017 will touch and the key names and argument values in ``keys_and_args``. 

6018 Returns the result of the script. 

6019 

6020 For more information, see https://redis.io/commands/eval_ro 

6021 """ 

6022 return self._eval("EVAL_RO", script, numkeys, *keys_and_args) 

6023 

6024 def _evalsha( 

6025 self, 

6026 command: str, 

6027 sha: str, 

6028 numkeys: int, 

6029 *keys_and_args: Union[KeyT, EncodableT], 

6030 ) -> Union[Awaitable[str], str]: 

6031 return self.execute_command(command, sha, numkeys, *keys_and_args) 

6032 

6033 def evalsha( 

6034 self, sha: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT] 

6035 ) -> Union[Awaitable[str], str]: 

6036 """ 

6037 Use the ``sha`` to execute a Lua script already registered via EVAL 

6038 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

6039 key names and argument values in ``keys_and_args``. Returns the result 

6040 of the script. 

6041 

6042 In practice, use the object returned by ``register_script``. This 

6043 function exists purely for Redis API completion. 

6044 

6045 For more information, see https://redis.io/commands/evalsha 

6046 """ 

6047 return self._evalsha("EVALSHA", sha, numkeys, *keys_and_args) 

6048 

6049 def evalsha_ro( 

6050 self, sha: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT] 

6051 ) -> Union[Awaitable[str], str]: 

6052 """ 

6053 The read-only variant of the EVALSHA command 

6054 

6055 Use the ``sha`` to execute a read-only Lua script already registered via EVAL 

6056 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

6057 key names and argument values in ``keys_and_args``. Returns the result 

6058 of the script. 

6059 

6060 For more information, see https://redis.io/commands/evalsha_ro 

6061 """ 

6062 return self._evalsha("EVALSHA_RO", sha, numkeys, *keys_and_args) 

6063 

6064 def script_exists(self, *args: str) -> ResponseT: 

6065 """ 

6066 Check if a script exists in the script cache by specifying the SHAs of 

6067 each script as ``args``. Returns a list of boolean values indicating if 

6068 if each already script exists in the cache_data. 

6069 

6070 For more information, see https://redis.io/commands/script-exists 

6071 """ 

6072 return self.execute_command("SCRIPT EXISTS", *args) 

6073 

6074 def script_debug(self, *args) -> None: 

6075 raise NotImplementedError( 

6076 "SCRIPT DEBUG is intentionally not implemented in the client." 

6077 ) 

6078 

6079 def script_flush( 

6080 self, sync_type: Union[Literal["SYNC"], Literal["ASYNC"]] = None 

6081 ) -> ResponseT: 

6082 """Flush all scripts from the script cache_data. 

6083 

6084 ``sync_type`` is by default SYNC (synchronous) but it can also be 

6085 ASYNC. 

6086 

6087 For more information, see https://redis.io/commands/script-flush 

6088 """ 

6089 

6090 # Redis pre 6 had no sync_type. 

6091 if sync_type not in ["SYNC", "ASYNC", None]: 

6092 raise DataError( 

6093 "SCRIPT FLUSH defaults to SYNC in redis > 6.2, or " 

6094 "accepts SYNC/ASYNC. For older versions, " 

6095 "of redis leave as None." 

6096 ) 

6097 if sync_type is None: 

6098 pieces = [] 

6099 else: 

6100 pieces = [sync_type] 

6101 return self.execute_command("SCRIPT FLUSH", *pieces) 

6102 

6103 def script_kill(self) -> ResponseT: 

6104 """ 

6105 Kill the currently executing Lua script 

6106 

6107 For more information, see https://redis.io/commands/script-kill 

6108 """ 

6109 return self.execute_command("SCRIPT KILL") 

6110 

6111 def script_load(self, script: ScriptTextT) -> ResponseT: 

6112 """ 

6113 Load a Lua ``script`` into the script cache_data. Returns the SHA. 

6114 

6115 For more information, see https://redis.io/commands/script-load 

6116 """ 

6117 return self.execute_command("SCRIPT LOAD", script) 

6118 

6119 def register_script(self: "redis.client.Redis", script: ScriptTextT) -> Script: 

6120 """ 

6121 Register a Lua ``script`` specifying the ``keys`` it will touch. 

6122 Returns a Script object that is callable and hides the complexity of 

6123 deal with scripts, keys, and shas. This is the preferred way to work 

6124 with Lua scripts. 

6125 """ 

6126 return Script(self, script) 

6127 

6128 

6129class AsyncScriptCommands(ScriptCommands): 

6130 async def script_debug(self, *args) -> None: 

6131 return super().script_debug() 

6132 

6133 def register_script( 

6134 self: "redis.asyncio.client.Redis", 

6135 script: ScriptTextT, 

6136 ) -> AsyncScript: 

6137 """ 

6138 Register a Lua ``script`` specifying the ``keys`` it will touch. 

6139 Returns a Script object that is callable and hides the complexity of 

6140 deal with scripts, keys, and shas. This is the preferred way to work 

6141 with Lua scripts. 

6142 """ 

6143 return AsyncScript(self, script) 

6144 

6145 

6146class GeoCommands(CommandsProtocol): 

6147 """ 

6148 Redis Geospatial commands. 

6149 see: https://redis.com/redis-best-practices/indexing-patterns/geospatial/ 

6150 """ 

6151 

6152 def geoadd( 

6153 self, 

6154 name: KeyT, 

6155 values: Sequence[EncodableT], 

6156 nx: bool = False, 

6157 xx: bool = False, 

6158 ch: bool = False, 

6159 ) -> ResponseT: 

6160 """ 

6161 Add the specified geospatial items to the specified key identified 

6162 by the ``name`` argument. The Geospatial items are given as ordered 

6163 members of the ``values`` argument, each item or place is formed by 

6164 the triad longitude, latitude and name. 

6165 

6166 Note: You can use ZREM to remove elements. 

6167 

6168 ``nx`` forces ZADD to only create new elements and not to update 

6169 scores for elements that already exist. 

6170 

6171 ``xx`` forces ZADD to only update scores of elements that already 

6172 exist. New elements will not be added. 

6173 

6174 ``ch`` modifies the return value to be the numbers of elements changed. 

6175 Changed elements include new elements that were added and elements 

6176 whose scores changed. 

6177 

6178 For more information, see https://redis.io/commands/geoadd 

6179 """ 

6180 if nx and xx: 

6181 raise DataError("GEOADD allows either 'nx' or 'xx', not both") 

6182 if len(values) % 3 != 0: 

6183 raise DataError("GEOADD requires places with lon, lat and name values") 

6184 pieces = [name] 

6185 if nx: 

6186 pieces.append("NX") 

6187 if xx: 

6188 pieces.append("XX") 

6189 if ch: 

6190 pieces.append("CH") 

6191 pieces.extend(values) 

6192 return self.execute_command("GEOADD", *pieces) 

6193 

6194 def geodist( 

6195 self, name: KeyT, place1: FieldT, place2: FieldT, unit: Optional[str] = None 

6196 ) -> ResponseT: 

6197 """ 

6198 Return the distance between ``place1`` and ``place2`` members of the 

6199 ``name`` key. 

6200 The units must be one of the following : m, km mi, ft. By default 

6201 meters are used. 

6202 

6203 For more information, see https://redis.io/commands/geodist 

6204 """ 

6205 pieces: list[EncodableT] = [name, place1, place2] 

6206 if unit and unit not in ("m", "km", "mi", "ft"): 

6207 raise DataError("GEODIST invalid unit") 

6208 elif unit: 

6209 pieces.append(unit) 

6210 return self.execute_command("GEODIST", *pieces, keys=[name]) 

6211 

6212 def geohash(self, name: KeyT, *values: FieldT) -> ResponseT: 

6213 """ 

6214 Return the geo hash string for each item of ``values`` members of 

6215 the specified key identified by the ``name`` argument. 

6216 

6217 For more information, see https://redis.io/commands/geohash 

6218 """ 

6219 return self.execute_command("GEOHASH", name, *values, keys=[name]) 

6220 

6221 def geopos(self, name: KeyT, *values: FieldT) -> ResponseT: 

6222 """ 

6223 Return the positions of each item of ``values`` as members of 

6224 the specified key identified by the ``name`` argument. Each position 

6225 is represented by the pairs lon and lat. 

6226 

6227 For more information, see https://redis.io/commands/geopos 

6228 """ 

6229 return self.execute_command("GEOPOS", name, *values, keys=[name]) 

6230 

6231 def georadius( 

6232 self, 

6233 name: KeyT, 

6234 longitude: float, 

6235 latitude: float, 

6236 radius: float, 

6237 unit: Optional[str] = None, 

6238 withdist: bool = False, 

6239 withcoord: bool = False, 

6240 withhash: bool = False, 

6241 count: Optional[int] = None, 

6242 sort: Optional[str] = None, 

6243 store: Optional[KeyT] = None, 

6244 store_dist: Optional[KeyT] = None, 

6245 any: bool = False, 

6246 ) -> ResponseT: 

6247 """ 

6248 Return the members of the specified key identified by the 

6249 ``name`` argument which are within the borders of the area specified 

6250 with the ``latitude`` and ``longitude`` location and the maximum 

6251 distance from the center specified by the ``radius`` value. 

6252 

6253 The units must be one of the following : m, km mi, ft. By default 

6254 

6255 ``withdist`` indicates to return the distances of each place. 

6256 

6257 ``withcoord`` indicates to return the latitude and longitude of 

6258 each place. 

6259 

6260 ``withhash`` indicates to return the geohash string of each place. 

6261 

6262 ``count`` indicates to return the number of elements up to N. 

6263 

6264 ``sort`` indicates to return the places in a sorted way, ASC for 

6265 nearest to fairest and DESC for fairest to nearest. 

6266 

6267 ``store`` indicates to save the places names in a sorted set named 

6268 with a specific key, each element of the destination sorted set is 

6269 populated with the score got from the original geo sorted set. 

6270 

6271 ``store_dist`` indicates to save the places names in a sorted set 

6272 named with a specific key, instead of ``store`` the sorted set 

6273 destination score is set with the distance. 

6274 

6275 For more information, see https://redis.io/commands/georadius 

6276 """ 

6277 return self._georadiusgeneric( 

6278 "GEORADIUS", 

6279 name, 

6280 longitude, 

6281 latitude, 

6282 radius, 

6283 unit=unit, 

6284 withdist=withdist, 

6285 withcoord=withcoord, 

6286 withhash=withhash, 

6287 count=count, 

6288 sort=sort, 

6289 store=store, 

6290 store_dist=store_dist, 

6291 any=any, 

6292 ) 

6293 

6294 def georadiusbymember( 

6295 self, 

6296 name: KeyT, 

6297 member: FieldT, 

6298 radius: float, 

6299 unit: Optional[str] = None, 

6300 withdist: bool = False, 

6301 withcoord: bool = False, 

6302 withhash: bool = False, 

6303 count: Optional[int] = None, 

6304 sort: Optional[str] = None, 

6305 store: Union[KeyT, None] = None, 

6306 store_dist: Union[KeyT, None] = None, 

6307 any: bool = False, 

6308 ) -> ResponseT: 

6309 """ 

6310 This command is exactly like ``georadius`` with the sole difference 

6311 that instead of taking, as the center of the area to query, a longitude 

6312 and latitude value, it takes the name of a member already existing 

6313 inside the geospatial index represented by the sorted set. 

6314 

6315 For more information, see https://redis.io/commands/georadiusbymember 

6316 """ 

6317 return self._georadiusgeneric( 

6318 "GEORADIUSBYMEMBER", 

6319 name, 

6320 member, 

6321 radius, 

6322 unit=unit, 

6323 withdist=withdist, 

6324 withcoord=withcoord, 

6325 withhash=withhash, 

6326 count=count, 

6327 sort=sort, 

6328 store=store, 

6329 store_dist=store_dist, 

6330 any=any, 

6331 ) 

6332 

6333 def _georadiusgeneric( 

6334 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

6335 ) -> ResponseT: 

6336 pieces = list(args) 

6337 if kwargs["unit"] and kwargs["unit"] not in ("m", "km", "mi", "ft"): 

6338 raise DataError("GEORADIUS invalid unit") 

6339 elif kwargs["unit"]: 

6340 pieces.append(kwargs["unit"]) 

6341 else: 

6342 pieces.append("m") 

6343 

6344 if kwargs["any"] and kwargs["count"] is None: 

6345 raise DataError("``any`` can't be provided without ``count``") 

6346 

6347 for arg_name, byte_repr in ( 

6348 ("withdist", "WITHDIST"), 

6349 ("withcoord", "WITHCOORD"), 

6350 ("withhash", "WITHHASH"), 

6351 ): 

6352 if kwargs[arg_name]: 

6353 pieces.append(byte_repr) 

6354 

6355 if kwargs["count"] is not None: 

6356 pieces.extend(["COUNT", kwargs["count"]]) 

6357 if kwargs["any"]: 

6358 pieces.append("ANY") 

6359 

6360 if kwargs["sort"]: 

6361 if kwargs["sort"] == "ASC": 

6362 pieces.append("ASC") 

6363 elif kwargs["sort"] == "DESC": 

6364 pieces.append("DESC") 

6365 else: 

6366 raise DataError("GEORADIUS invalid sort") 

6367 

6368 if kwargs["store"] and kwargs["store_dist"]: 

6369 raise DataError("GEORADIUS store and store_dist cant be set together") 

6370 

6371 if kwargs["store"]: 

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

6373 

6374 if kwargs["store_dist"]: 

6375 pieces.extend([b"STOREDIST", kwargs["store_dist"]]) 

6376 

6377 return self.execute_command(command, *pieces, **kwargs) 

6378 

6379 def geosearch( 

6380 self, 

6381 name: KeyT, 

6382 member: Union[FieldT, None] = None, 

6383 longitude: Union[float, None] = None, 

6384 latitude: Union[float, None] = None, 

6385 unit: str = "m", 

6386 radius: Union[float, None] = None, 

6387 width: Union[float, None] = None, 

6388 height: Union[float, None] = None, 

6389 sort: Optional[str] = None, 

6390 count: Optional[int] = None, 

6391 any: bool = False, 

6392 withcoord: bool = False, 

6393 withdist: bool = False, 

6394 withhash: bool = False, 

6395 ) -> ResponseT: 

6396 """ 

6397 Return the members of specified key identified by the 

6398 ``name`` argument, which are within the borders of the 

6399 area specified by a given shape. This command extends the 

6400 GEORADIUS command, so in addition to searching within circular 

6401 areas, it supports searching within rectangular areas. 

6402 

6403 This command should be used in place of the deprecated 

6404 GEORADIUS and GEORADIUSBYMEMBER commands. 

6405 

6406 ``member`` Use the position of the given existing 

6407 member in the sorted set. Can't be given with ``longitude`` 

6408 and ``latitude``. 

6409 

6410 ``longitude`` and ``latitude`` Use the position given by 

6411 this coordinates. Can't be given with ``member`` 

6412 ``radius`` Similar to GEORADIUS, search inside circular 

6413 area according the given radius. Can't be given with 

6414 ``height`` and ``width``. 

6415 ``height`` and ``width`` Search inside an axis-aligned 

6416 rectangle, determined by the given height and width. 

6417 Can't be given with ``radius`` 

6418 

6419 ``unit`` must be one of the following : m, km, mi, ft. 

6420 `m` for meters (the default value), `km` for kilometers, 

6421 `mi` for miles and `ft` for feet. 

6422 

6423 ``sort`` indicates to return the places in a sorted way, 

6424 ASC for nearest to furthest and DESC for furthest to nearest. 

6425 

6426 ``count`` limit the results to the first count matching items. 

6427 

6428 ``any`` is set to True, the command will return as soon as 

6429 enough matches are found. Can't be provided without ``count`` 

6430 

6431 ``withdist`` indicates to return the distances of each place. 

6432 ``withcoord`` indicates to return the latitude and longitude of 

6433 each place. 

6434 

6435 ``withhash`` indicates to return the geohash string of each place. 

6436 

6437 For more information, see https://redis.io/commands/geosearch 

6438 """ 

6439 

6440 return self._geosearchgeneric( 

6441 "GEOSEARCH", 

6442 name, 

6443 member=member, 

6444 longitude=longitude, 

6445 latitude=latitude, 

6446 unit=unit, 

6447 radius=radius, 

6448 width=width, 

6449 height=height, 

6450 sort=sort, 

6451 count=count, 

6452 any=any, 

6453 withcoord=withcoord, 

6454 withdist=withdist, 

6455 withhash=withhash, 

6456 store=None, 

6457 store_dist=None, 

6458 ) 

6459 

6460 def geosearchstore( 

6461 self, 

6462 dest: KeyT, 

6463 name: KeyT, 

6464 member: Optional[FieldT] = None, 

6465 longitude: Optional[float] = None, 

6466 latitude: Optional[float] = None, 

6467 unit: str = "m", 

6468 radius: Optional[float] = None, 

6469 width: Optional[float] = None, 

6470 height: Optional[float] = None, 

6471 sort: Optional[str] = None, 

6472 count: Optional[int] = None, 

6473 any: bool = False, 

6474 storedist: bool = False, 

6475 ) -> ResponseT: 

6476 """ 

6477 This command is like GEOSEARCH, but stores the result in 

6478 ``dest``. By default, it stores the results in the destination 

6479 sorted set with their geospatial information. 

6480 if ``store_dist`` set to True, the command will stores the 

6481 items in a sorted set populated with their distance from the 

6482 center of the circle or box, as a floating-point number. 

6483 

6484 For more information, see https://redis.io/commands/geosearchstore 

6485 """ 

6486 return self._geosearchgeneric( 

6487 "GEOSEARCHSTORE", 

6488 dest, 

6489 name, 

6490 member=member, 

6491 longitude=longitude, 

6492 latitude=latitude, 

6493 unit=unit, 

6494 radius=radius, 

6495 width=width, 

6496 height=height, 

6497 sort=sort, 

6498 count=count, 

6499 any=any, 

6500 withcoord=None, 

6501 withdist=None, 

6502 withhash=None, 

6503 store=None, 

6504 store_dist=storedist, 

6505 ) 

6506 

6507 def _geosearchgeneric( 

6508 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

6509 ) -> ResponseT: 

6510 pieces = list(args) 

6511 

6512 # FROMMEMBER or FROMLONLAT 

6513 if kwargs["member"] is None: 

6514 if kwargs["longitude"] is None or kwargs["latitude"] is None: 

6515 raise DataError("GEOSEARCH must have member or longitude and latitude") 

6516 if kwargs["member"]: 

6517 if kwargs["longitude"] or kwargs["latitude"]: 

6518 raise DataError( 

6519 "GEOSEARCH member and longitude or latitude cant be set together" 

6520 ) 

6521 pieces.extend([b"FROMMEMBER", kwargs["member"]]) 

6522 if kwargs["longitude"] is not None and kwargs["latitude"] is not None: 

6523 pieces.extend([b"FROMLONLAT", kwargs["longitude"], kwargs["latitude"]]) 

6524 

6525 # BYRADIUS or BYBOX 

6526 if kwargs["radius"] is None: 

6527 if kwargs["width"] is None or kwargs["height"] is None: 

6528 raise DataError("GEOSEARCH must have radius or width and height") 

6529 if kwargs["unit"] is None: 

6530 raise DataError("GEOSEARCH must have unit") 

6531 if kwargs["unit"].lower() not in ("m", "km", "mi", "ft"): 

6532 raise DataError("GEOSEARCH invalid unit") 

6533 if kwargs["radius"]: 

6534 if kwargs["width"] or kwargs["height"]: 

6535 raise DataError( 

6536 "GEOSEARCH radius and width or height cant be set together" 

6537 ) 

6538 pieces.extend([b"BYRADIUS", kwargs["radius"], kwargs["unit"]]) 

6539 if kwargs["width"] and kwargs["height"]: 

6540 pieces.extend([b"BYBOX", kwargs["width"], kwargs["height"], kwargs["unit"]]) 

6541 

6542 # sort 

6543 if kwargs["sort"]: 

6544 if kwargs["sort"].upper() == "ASC": 

6545 pieces.append(b"ASC") 

6546 elif kwargs["sort"].upper() == "DESC": 

6547 pieces.append(b"DESC") 

6548 else: 

6549 raise DataError("GEOSEARCH invalid sort") 

6550 

6551 # count any 

6552 if kwargs["count"]: 

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

6554 if kwargs["any"]: 

6555 pieces.append(b"ANY") 

6556 elif kwargs["any"]: 

6557 raise DataError("GEOSEARCH ``any`` can't be provided without count") 

6558 

6559 # other properties 

6560 for arg_name, byte_repr in ( 

6561 ("withdist", b"WITHDIST"), 

6562 ("withcoord", b"WITHCOORD"), 

6563 ("withhash", b"WITHHASH"), 

6564 ("store_dist", b"STOREDIST"), 

6565 ): 

6566 if kwargs[arg_name]: 

6567 pieces.append(byte_repr) 

6568 

6569 kwargs["keys"] = [args[0] if command == "GEOSEARCH" else args[1]] 

6570 

6571 return self.execute_command(command, *pieces, **kwargs) 

6572 

6573 

6574AsyncGeoCommands = GeoCommands 

6575 

6576 

6577class ModuleCommands(CommandsProtocol): 

6578 """ 

6579 Redis Module commands. 

6580 see: https://redis.io/topics/modules-intro 

6581 """ 

6582 

6583 def module_load(self, path, *args) -> ResponseT: 

6584 """ 

6585 Loads the module from ``path``. 

6586 Passes all ``*args`` to the module, during loading. 

6587 Raises ``ModuleError`` if a module is not found at ``path``. 

6588 

6589 For more information, see https://redis.io/commands/module-load 

6590 """ 

6591 return self.execute_command("MODULE LOAD", path, *args) 

6592 

6593 def module_loadex( 

6594 self, 

6595 path: str, 

6596 options: Optional[List[str]] = None, 

6597 args: Optional[List[str]] = None, 

6598 ) -> ResponseT: 

6599 """ 

6600 Loads a module from a dynamic library at runtime with configuration directives. 

6601 

6602 For more information, see https://redis.io/commands/module-loadex 

6603 """ 

6604 pieces = [] 

6605 if options is not None: 

6606 pieces.append("CONFIG") 

6607 pieces.extend(options) 

6608 if args is not None: 

6609 pieces.append("ARGS") 

6610 pieces.extend(args) 

6611 

6612 return self.execute_command("MODULE LOADEX", path, *pieces) 

6613 

6614 def module_unload(self, name) -> ResponseT: 

6615 """ 

6616 Unloads the module ``name``. 

6617 Raises ``ModuleError`` if ``name`` is not in loaded modules. 

6618 

6619 For more information, see https://redis.io/commands/module-unload 

6620 """ 

6621 return self.execute_command("MODULE UNLOAD", name) 

6622 

6623 def module_list(self) -> ResponseT: 

6624 """ 

6625 Returns a list of dictionaries containing the name and version of 

6626 all loaded modules. 

6627 

6628 For more information, see https://redis.io/commands/module-list 

6629 """ 

6630 return self.execute_command("MODULE LIST") 

6631 

6632 def command_info(self) -> None: 

6633 raise NotImplementedError( 

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

6635 ) 

6636 

6637 def command_count(self) -> ResponseT: 

6638 return self.execute_command("COMMAND COUNT") 

6639 

6640 def command_getkeys(self, *args) -> ResponseT: 

6641 return self.execute_command("COMMAND GETKEYS", *args) 

6642 

6643 def command(self) -> ResponseT: 

6644 return self.execute_command("COMMAND") 

6645 

6646 

6647class AsyncModuleCommands(ModuleCommands): 

6648 async def command_info(self) -> None: 

6649 return super().command_info() 

6650 

6651 

6652class ClusterCommands(CommandsProtocol): 

6653 """ 

6654 Class for Redis Cluster commands 

6655 """ 

6656 

6657 def cluster(self, cluster_arg, *args, **kwargs) -> ResponseT: 

6658 return self.execute_command(f"CLUSTER {cluster_arg.upper()}", *args, **kwargs) 

6659 

6660 def readwrite(self, **kwargs) -> ResponseT: 

6661 """ 

6662 Disables read queries for a connection to a Redis Cluster slave node. 

6663 

6664 For more information, see https://redis.io/commands/readwrite 

6665 """ 

6666 return self.execute_command("READWRITE", **kwargs) 

6667 

6668 def readonly(self, **kwargs) -> ResponseT: 

6669 """ 

6670 Enables read queries for a connection to a Redis Cluster replica node. 

6671 

6672 For more information, see https://redis.io/commands/readonly 

6673 """ 

6674 return self.execute_command("READONLY", **kwargs) 

6675 

6676 

6677AsyncClusterCommands = ClusterCommands 

6678 

6679 

6680class FunctionCommands: 

6681 """ 

6682 Redis Function commands 

6683 """ 

6684 

6685 def function_load( 

6686 self, code: str, replace: Optional[bool] = False 

6687 ) -> Union[Awaitable[str], str]: 

6688 """ 

6689 Load a library to Redis. 

6690 :param code: the source code (must start with 

6691 Shebang statement that provides a metadata about the library) 

6692 :param replace: changes the behavior to overwrite the existing library 

6693 with the new contents. 

6694 Return the library name that was loaded. 

6695 

6696 For more information, see https://redis.io/commands/function-load 

6697 """ 

6698 pieces = ["REPLACE"] if replace else [] 

6699 pieces.append(code) 

6700 return self.execute_command("FUNCTION LOAD", *pieces) 

6701 

6702 def function_delete(self, library: str) -> Union[Awaitable[str], str]: 

6703 """ 

6704 Delete the library called ``library`` and all its functions. 

6705 

6706 For more information, see https://redis.io/commands/function-delete 

6707 """ 

6708 return self.execute_command("FUNCTION DELETE", library) 

6709 

6710 def function_flush(self, mode: str = "SYNC") -> Union[Awaitable[str], str]: 

6711 """ 

6712 Deletes all the libraries. 

6713 

6714 For more information, see https://redis.io/commands/function-flush 

6715 """ 

6716 return self.execute_command("FUNCTION FLUSH", mode) 

6717 

6718 def function_list( 

6719 self, library: Optional[str] = "*", withcode: Optional[bool] = False 

6720 ) -> Union[Awaitable[List], List]: 

6721 """ 

6722 Return information about the functions and libraries. 

6723 

6724 Args: 

6725 

6726 library: specify a pattern for matching library names 

6727 withcode: cause the server to include the libraries source implementation 

6728 in the reply 

6729 """ 

6730 args = ["LIBRARYNAME", library] 

6731 if withcode: 

6732 args.append("WITHCODE") 

6733 return self.execute_command("FUNCTION LIST", *args) 

6734 

6735 def _fcall( 

6736 self, command: str, function, numkeys: int, *keys_and_args: Any 

6737 ) -> Union[Awaitable[str], str]: 

6738 return self.execute_command(command, function, numkeys, *keys_and_args) 

6739 

6740 def fcall( 

6741 self, function, numkeys: int, *keys_and_args: Any 

6742 ) -> Union[Awaitable[str], str]: 

6743 """ 

6744 Invoke a function. 

6745 

6746 For more information, see https://redis.io/commands/fcall 

6747 """ 

6748 return self._fcall("FCALL", function, numkeys, *keys_and_args) 

6749 

6750 def fcall_ro( 

6751 self, function, numkeys: int, *keys_and_args: Any 

6752 ) -> Union[Awaitable[str], str]: 

6753 """ 

6754 This is a read-only variant of the FCALL command that cannot 

6755 execute commands that modify data. 

6756 

6757 For more information, see https://redis.io/commands/fcall_ro 

6758 """ 

6759 return self._fcall("FCALL_RO", function, numkeys, *keys_and_args) 

6760 

6761 def function_dump(self) -> Union[Awaitable[str], str]: 

6762 """ 

6763 Return the serialized payload of loaded libraries. 

6764 

6765 For more information, see https://redis.io/commands/function-dump 

6766 """ 

6767 from redis.client import NEVER_DECODE 

6768 

6769 options = {} 

6770 options[NEVER_DECODE] = [] 

6771 

6772 return self.execute_command("FUNCTION DUMP", **options) 

6773 

6774 def function_restore( 

6775 self, payload: str, policy: Optional[str] = "APPEND" 

6776 ) -> Union[Awaitable[str], str]: 

6777 """ 

6778 Restore libraries from the serialized ``payload``. 

6779 You can use the optional policy argument to provide a policy 

6780 for handling existing libraries. 

6781 

6782 For more information, see https://redis.io/commands/function-restore 

6783 """ 

6784 return self.execute_command("FUNCTION RESTORE", payload, policy) 

6785 

6786 def function_kill(self) -> Union[Awaitable[str], str]: 

6787 """ 

6788 Kill a function that is currently executing. 

6789 

6790 For more information, see https://redis.io/commands/function-kill 

6791 """ 

6792 return self.execute_command("FUNCTION KILL") 

6793 

6794 def function_stats(self) -> Union[Awaitable[List], List]: 

6795 """ 

6796 Return information about the function that's currently running 

6797 and information about the available execution engines. 

6798 

6799 For more information, see https://redis.io/commands/function-stats 

6800 """ 

6801 return self.execute_command("FUNCTION STATS") 

6802 

6803 

6804AsyncFunctionCommands = FunctionCommands 

6805 

6806 

6807class DataAccessCommands( 

6808 BasicKeyCommands, 

6809 HyperlogCommands, 

6810 HashCommands, 

6811 GeoCommands, 

6812 ListCommands, 

6813 ScanCommands, 

6814 SetCommands, 

6815 StreamCommands, 

6816 SortedSetCommands, 

6817): 

6818 """ 

6819 A class containing all of the implemented data access redis commands. 

6820 This class is to be used as a mixin for synchronous Redis clients. 

6821 """ 

6822 

6823 

6824class AsyncDataAccessCommands( 

6825 AsyncBasicKeyCommands, 

6826 AsyncHyperlogCommands, 

6827 AsyncHashCommands, 

6828 AsyncGeoCommands, 

6829 AsyncListCommands, 

6830 AsyncScanCommands, 

6831 AsyncSetCommands, 

6832 AsyncStreamCommands, 

6833 AsyncSortedSetCommands, 

6834): 

6835 """ 

6836 A class containing all of the implemented data access redis commands. 

6837 This class is to be used as a mixin for asynchronous Redis clients. 

6838 """ 

6839 

6840 

6841class CoreCommands( 

6842 ACLCommands, 

6843 ClusterCommands, 

6844 DataAccessCommands, 

6845 ManagementCommands, 

6846 ModuleCommands, 

6847 PubSubCommands, 

6848 ScriptCommands, 

6849 FunctionCommands, 

6850): 

6851 """ 

6852 A class containing all of the implemented redis commands. This class is 

6853 to be used as a mixin for synchronous Redis clients. 

6854 """ 

6855 

6856 

6857class AsyncCoreCommands( 

6858 AsyncACLCommands, 

6859 AsyncClusterCommands, 

6860 AsyncDataAccessCommands, 

6861 AsyncManagementCommands, 

6862 AsyncModuleCommands, 

6863 AsyncPubSubCommands, 

6864 AsyncScriptCommands, 

6865 AsyncFunctionCommands, 

6866): 

6867 """ 

6868 A class containing all of the implemented redis commands. This class is 

6869 to be used as a mixin for asynchronous Redis clients. 

6870 """