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

1964 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 experimental_args, 

51 experimental_method, 

52 extract_expire_flags, 

53) 

54 

55from .helpers import at_most_one_value_set, list_or_args 

56 

57if TYPE_CHECKING: 

58 import redis.asyncio.client 

59 import redis.client 

60 

61 

62class ACLCommands(CommandsProtocol): 

63 """ 

64 Redis Access Control List (ACL) commands. 

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

66 """ 

67 

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

69 """ 

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

71 

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

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

74 that category. 

75 

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

77 """ 

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

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

80 

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

82 """ 

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

84 

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

86 """ 

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

88 

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

90 """ 

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

92 

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

94 """ 

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

96 

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

98 """Generate a random password value. 

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

100 the next multiple of 4. 

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

102 """ 

103 pieces = [] 

104 if bits is not None: 

105 try: 

106 b = int(bits) 

107 if b < 0 or b > 4096: 

108 raise ValueError 

109 pieces.append(b) 

110 except ValueError: 

111 raise DataError( 

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

113 ) 

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

115 

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

117 """ 

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

119 

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

121 

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

123 """ 

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

125 

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

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

128 the different subcommands. 

129 

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

131 """ 

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

133 

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

135 """ 

136 Return a list of all ACLs on the server 

137 

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

139 """ 

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

141 

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

143 """ 

144 Get ACL logs as a list. 

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

146 :rtype: List. 

147 

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

149 """ 

150 args = [] 

151 if count is not None: 

152 if not isinstance(count, int): 

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

154 args.append(count) 

155 

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

157 

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

159 """ 

160 Reset ACL logs. 

161 :rtype: Boolean. 

162 

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

164 """ 

165 args = [b"RESET"] 

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

167 

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

169 """ 

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

171 

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

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

174 

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

176 """ 

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

178 

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

180 """ 

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

182 

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

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

185 

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

187 """ 

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

189 

190 def acl_setuser( 

191 self, 

192 username: str, 

193 enabled: bool = False, 

194 nopass: bool = False, 

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

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

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

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

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

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

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

202 reset: bool = False, 

203 reset_keys: bool = False, 

204 reset_channels: bool = False, 

205 reset_passwords: bool = False, 

206 **kwargs, 

207 ) -> ResponseT: 

208 """ 

209 Create or update an ACL user. 

210 

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

212 the existing ACL is completely overwritten and replaced with the 

213 specified values. 

214 

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

216 

217 Args: 

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

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

220 Defaults to `False`. 

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

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

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

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

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

226 when adding or removing a single password. 

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

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

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

230 prefixed string can be used when adding or removing a 

231 single password. 

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

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

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

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

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

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

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

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

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

241 are prefixed with ``cache:``. 

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

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

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

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

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

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

248 applied on top. 

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

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

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

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

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

254 reset prior to applying any new channel permissions 

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

256 channel permissions will be kept and any new specified 

257 channel permissions will be applied on top. 

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

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

260 passwords specified in `passwords` or `hashed_passwords`. 

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

262 status will be kept and any new specified passwords or 

263 hashed passwords will be applied on top. 

264 """ 

265 encoder = self.get_encoder() 

266 pieces: List[EncodableT] = [username] 

267 

268 if reset: 

269 pieces.append(b"reset") 

270 

271 if reset_keys: 

272 pieces.append(b"resetkeys") 

273 

274 if reset_channels: 

275 pieces.append(b"resetchannels") 

276 

277 if reset_passwords: 

278 pieces.append(b"resetpass") 

279 

280 if enabled: 

281 pieces.append(b"on") 

282 else: 

283 pieces.append(b"off") 

284 

285 if (passwords or hashed_passwords) and nopass: 

286 raise DataError( 

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

288 ) 

289 

290 if passwords: 

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

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

293 passwords = list_or_args(passwords, []) 

294 for i, password in enumerate(passwords): 

295 password = encoder.encode(password) 

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

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

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

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

300 else: 

301 raise DataError( 

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

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

304 ) 

305 

306 if hashed_passwords: 

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

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

309 hashed_passwords = list_or_args(hashed_passwords, []) 

310 for i, hashed_password in enumerate(hashed_passwords): 

311 hashed_password = encoder.encode(hashed_password) 

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

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

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

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

316 else: 

317 raise DataError( 

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

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

320 ) 

321 

322 if nopass: 

323 pieces.append(b"nopass") 

324 

325 if categories: 

326 for category in categories: 

327 category = encoder.encode(category) 

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

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

330 pieces.append(category) 

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

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

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

334 pieces.append(category) 

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

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

337 else: 

338 raise DataError( 

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

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

341 ) 

342 if commands: 

343 for cmd in commands: 

344 cmd = encoder.encode(cmd) 

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

346 raise DataError( 

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

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

349 ) 

350 pieces.append(cmd) 

351 

352 if keys: 

353 for key in keys: 

354 key = encoder.encode(key) 

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

356 key = b"~%s" % key 

357 pieces.append(key) 

358 

359 if channels: 

360 for channel in channels: 

361 channel = encoder.encode(channel) 

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

363 

364 if selectors: 

365 for cmd, key in selectors: 

366 cmd = encoder.encode(cmd) 

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

368 raise DataError( 

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

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

371 ) 

372 

373 key = encoder.encode(key) 

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

375 key = b"~%s" % key 

376 

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

378 

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

380 

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

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

383 

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

385 """ 

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

387 

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

389 """Get the username for the current connection 

390 

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

392 """ 

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

394 

395 

396AsyncACLCommands = ACLCommands 

397 

398 

399class ManagementCommands(CommandsProtocol): 

400 """ 

401 Redis management commands 

402 """ 

403 

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

405 """ 

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

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

408 authenticate for the given user. 

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

410 """ 

411 pieces = [] 

412 if username is not None: 

413 pieces.append(username) 

414 pieces.append(password) 

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

416 

417 def bgrewriteaof(self, **kwargs): 

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

419 

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

421 """ 

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

423 

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

425 """ 

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

427 this method is asynchronous and returns immediately. 

428 

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

430 """ 

431 pieces = [] 

432 if schedule: 

433 pieces.append("SCHEDULE") 

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

435 

436 def role(self) -> ResponseT: 

437 """ 

438 Provide information on the role of a Redis instance in 

439 the context of replication, by returning if the instance 

440 is currently a master, slave, or sentinel. 

441 

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

443 """ 

444 return self.execute_command("ROLE") 

445 

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

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

448 

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

450 """ 

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

452 

453 def client_kill_filter( 

454 self, 

455 _id: Optional[str] = None, 

456 _type: Optional[str] = None, 

457 addr: Optional[str] = None, 

458 skipme: Optional[bool] = None, 

459 laddr: Optional[bool] = None, 

460 user: Optional[str] = None, 

461 maxage: Optional[int] = None, 

462 **kwargs, 

463 ) -> ResponseT: 

464 """ 

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

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

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

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

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

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

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

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

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

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

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

476 """ 

477 args = [] 

478 if _type is not None: 

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

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

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

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

483 if skipme is not None: 

484 if not isinstance(skipme, bool): 

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

486 if skipme: 

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

488 else: 

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

490 if _id is not None: 

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

492 if addr is not None: 

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

494 if laddr is not None: 

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

496 if user is not None: 

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

498 if maxage is not None: 

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

500 if not args: 

501 raise DataError( 

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

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

504 ) 

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

506 

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

508 """ 

509 Returns information and statistics about the current 

510 client connection. 

511 

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

513 """ 

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

515 

516 def client_list( 

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

518 ) -> ResponseT: 

519 """ 

520 Returns a list of currently connected clients. 

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

522 

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

524 replica, pubsub) 

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

526 

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

528 """ 

529 args = [] 

530 if _type is not None: 

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

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

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

534 args.append(b"TYPE") 

535 args.append(_type) 

536 if not isinstance(client_id, list): 

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

538 if client_id: 

539 args.append(b"ID") 

540 args += client_id 

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

542 

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

544 """ 

545 Returns the current connection name 

546 

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

548 """ 

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

550 

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

552 """ 

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

554 redirecting tracking notifications. 

555 

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

557 """ 

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

559 

560 def client_reply( 

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

562 ) -> ResponseT: 

563 """ 

564 Enable and disable redis server replies. 

565 

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

567 ON - The default most with server replies to commands 

568 OFF - Disable server responses to commands 

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

570 

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

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

573 TimeoutError. 

574 The test_client_reply unit test illustrates this, and 

575 conftest.py has a client with a timeout. 

576 

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

578 """ 

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

580 if reply not in replies: 

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

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

583 

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

585 """ 

586 Returns the current connection id 

587 

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

589 """ 

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

591 

592 def client_tracking_on( 

593 self, 

594 clientid: Optional[int] = None, 

595 prefix: Sequence[KeyT] = [], 

596 bcast: bool = False, 

597 optin: bool = False, 

598 optout: bool = False, 

599 noloop: bool = False, 

600 ) -> ResponseT: 

601 """ 

602 Turn on the tracking mode. 

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

604 

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

606 """ 

607 return self.client_tracking( 

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

609 ) 

610 

611 def client_tracking_off( 

612 self, 

613 clientid: Optional[int] = None, 

614 prefix: Sequence[KeyT] = [], 

615 bcast: bool = False, 

616 optin: bool = False, 

617 optout: bool = False, 

618 noloop: bool = False, 

619 ) -> ResponseT: 

620 """ 

621 Turn off the tracking mode. 

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

623 

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

625 """ 

626 return self.client_tracking( 

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

628 ) 

629 

630 def client_tracking( 

631 self, 

632 on: bool = True, 

633 clientid: Optional[int] = None, 

634 prefix: Sequence[KeyT] = [], 

635 bcast: bool = False, 

636 optin: bool = False, 

637 optout: bool = False, 

638 noloop: bool = False, 

639 **kwargs, 

640 ) -> ResponseT: 

641 """ 

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

643 for server assisted client side caching. 

644 

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

646 

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

648 the specified ID. 

649 

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

651 invalidation messages are reported for all the prefixes 

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

653 

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

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

656 after a CLIENT CACHING yes command. 

657 

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

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

660 CLIENT CACHING no command. 

661 

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

663 connection itself. 

664 

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

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

667 

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

669 """ 

670 

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

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

673 

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

675 if clientid is not None: 

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

677 for p in prefix: 

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

679 if bcast: 

680 pieces.append("BCAST") 

681 if optin: 

682 pieces.append("OPTIN") 

683 if optout: 

684 pieces.append("OPTOUT") 

685 if noloop: 

686 pieces.append("NOLOOP") 

687 

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

689 

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

691 """ 

692 Returns the information about the current client connection's 

693 use of the server assisted client side cache. 

694 

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

696 """ 

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

698 

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

700 """ 

701 Sets the current connection name 

702 

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

704 

705 .. note:: 

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

707 

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

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

710 """ 

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

712 

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

714 """ 

715 Sets the current connection library name or version 

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

717 """ 

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

719 

720 def client_unblock( 

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

722 ) -> ResponseT: 

723 """ 

724 Unblocks a connection by its client id. 

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

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

727 regular timeout mechanism. 

728 

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

730 """ 

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

732 if error: 

733 args.append(b"ERROR") 

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

735 

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

737 """ 

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

739 

740 

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

742 

743 Args: 

744 timeout: milliseconds to pause clients 

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

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

747 a write command. 

748 

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

750 

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

752 * PUBLISH: Will block client. 

753 * PFCOUNT: Will block client. 

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

755 appear blocked. 

756 """ 

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

758 if not isinstance(timeout, int): 

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

760 if not all: 

761 args.append("WRITE") 

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

763 

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

765 """ 

766 Unpause all redis clients 

767 

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

769 """ 

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

771 

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

773 """ 

774 Sets the client eviction mode for the current connection. 

775 

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

777 """ 

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

779 

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

781 """ 

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

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

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

785 # unless it sends the TOUCH command. 

786 

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

788 """ 

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

790 

791 def command(self, **kwargs): 

792 """ 

793 Returns dict reply of details about all Redis commands. 

794 

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

796 """ 

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

798 

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

800 raise NotImplementedError( 

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

802 ) 

803 

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

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

806 

807 def command_list( 

808 self, 

809 module: Optional[str] = None, 

810 category: Optional[str] = None, 

811 pattern: Optional[str] = None, 

812 ) -> ResponseT: 

813 """ 

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

815 You can use one of the following filters: 

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

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

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

819 

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

821 """ 

822 pieces = [] 

823 if module is not None: 

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

825 if category is not None: 

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

827 if pattern is not None: 

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

829 

830 if pieces: 

831 pieces.insert(0, "FILTERBY") 

832 

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

834 

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

836 """ 

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

838 

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

840 """ 

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

842 

843 def command_docs(self, *args): 

844 """ 

845 This function throws a NotImplementedError since it is intentionally 

846 not supported. 

847 """ 

848 raise NotImplementedError( 

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

850 ) 

851 

852 def config_get( 

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

854 ) -> ResponseT: 

855 """ 

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

857 

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

859 """ 

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

861 

862 def config_set( 

863 self, 

864 name: KeyT, 

865 value: EncodableT, 

866 *args: Union[KeyT, EncodableT], 

867 **kwargs, 

868 ) -> ResponseT: 

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

870 

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

872 """ 

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

874 

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

876 """ 

877 Reset runtime statistics 

878 

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

880 """ 

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

882 

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

884 """ 

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

886 

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

888 """ 

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

890 

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

892 """ 

893 Returns the number of keys in the current database 

894 

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

896 """ 

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

898 

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

900 """ 

901 Returns version specific meta information about a given key 

902 

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

904 """ 

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

906 

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

908 raise NotImplementedError( 

909 """ 

910 DEBUG SEGFAULT is intentionally not implemented in the client. 

911 

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

913 """ 

914 ) 

915 

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

917 """ 

918 Echo the string back from the server 

919 

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

921 """ 

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

923 

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

925 """ 

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

927 

928 ``asynchronous`` indicates whether the operation is 

929 executed asynchronously by the server. 

930 

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

932 """ 

933 args = [] 

934 if asynchronous: 

935 args.append(b"ASYNC") 

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

937 

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

939 """ 

940 Delete all keys in the current database. 

941 

942 ``asynchronous`` indicates whether the operation is 

943 executed asynchronously by the server. 

944 

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

946 """ 

947 args = [] 

948 if asynchronous: 

949 args.append(b"ASYNC") 

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

951 

952 def sync(self) -> ResponseT: 

953 """ 

954 Initiates a replication stream from the master. 

955 

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

957 """ 

958 from redis.client import NEVER_DECODE 

959 

960 options = {} 

961 options[NEVER_DECODE] = [] 

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

963 

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

965 """ 

966 Initiates a replication stream from the master. 

967 Newer version for `sync`. 

968 

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

970 """ 

971 from redis.client import NEVER_DECODE 

972 

973 options = {} 

974 options[NEVER_DECODE] = [] 

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

976 

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

978 """ 

979 Swap two databases 

980 

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

982 """ 

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

984 

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

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

987 

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

989 """ 

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

991 

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

993 """ 

994 Returns a dictionary containing information about the Redis server 

995 

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

997 of information 

998 

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

1000 and will generate ResponseError 

1001 

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

1003 """ 

1004 if section is None: 

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

1006 else: 

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

1008 

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

1010 """ 

1011 Return a Python datetime object representing the last time the 

1012 Redis database was saved to disk 

1013 

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

1015 """ 

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

1017 

1018 def latency_doctor(self): 

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

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

1021 

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

1023 """ 

1024 raise NotImplementedError( 

1025 """ 

1026 LATENCY DOCTOR is intentionally not implemented in the client. 

1027 

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

1029 """ 

1030 ) 

1031 

1032 def latency_graph(self): 

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

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

1035 

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

1037 """ 

1038 raise NotImplementedError( 

1039 """ 

1040 LATENCY GRAPH is intentionally not implemented in the client. 

1041 

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

1043 """ 

1044 ) 

1045 

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

1047 """ 

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

1049 

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

1051 """ 

1052 if version_numbers: 

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

1054 else: 

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

1056 

1057 def reset(self) -> ResponseT: 

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

1059 

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

1061 """ 

1062 return self.execute_command("RESET") 

1063 

1064 def migrate( 

1065 self, 

1066 host: str, 

1067 port: int, 

1068 keys: KeysT, 

1069 destination_db: int, 

1070 timeout: int, 

1071 copy: bool = False, 

1072 replace: bool = False, 

1073 auth: Optional[str] = None, 

1074 **kwargs, 

1075 ) -> ResponseT: 

1076 """ 

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

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

1079 

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

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

1082 command is interrupted. 

1083 

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

1085 the source server. 

1086 

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

1088 on the destination server if they exist. 

1089 

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

1091 the password provided. 

1092 

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

1094 """ 

1095 keys = list_or_args(keys, []) 

1096 if not keys: 

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

1098 pieces = [] 

1099 if copy: 

1100 pieces.append(b"COPY") 

1101 if replace: 

1102 pieces.append(b"REPLACE") 

1103 if auth: 

1104 pieces.append(b"AUTH") 

1105 pieces.append(auth) 

1106 pieces.append(b"KEYS") 

1107 pieces.extend(keys) 

1108 return self.execute_command( 

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

1110 ) 

1111 

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

1113 """ 

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

1115 """ 

1116 return self.execute_command( 

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

1118 ) 

1119 

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

1121 raise NotImplementedError( 

1122 """ 

1123 MEMORY DOCTOR is intentionally not implemented in the client. 

1124 

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

1126 """ 

1127 ) 

1128 

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

1130 raise NotImplementedError( 

1131 """ 

1132 MEMORY HELP is intentionally not implemented in the client. 

1133 

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

1135 """ 

1136 ) 

1137 

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

1139 """ 

1140 Return a dictionary of memory stats 

1141 

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

1143 """ 

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

1145 

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

1147 """ 

1148 Return an internal statistics report from the memory allocator. 

1149 

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

1151 """ 

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

1153 

1154 def memory_usage( 

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

1156 ) -> ResponseT: 

1157 """ 

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

1159 administrative overheads. 

1160 

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

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

1163 all elements. 

1164 

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

1166 """ 

1167 args = [] 

1168 if isinstance(samples, int): 

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

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

1171 

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

1173 """ 

1174 Attempts to purge dirty pages for reclamation by allocator 

1175 

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

1177 """ 

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

1179 

1180 def latency_histogram(self, *args): 

1181 """ 

1182 This function throws a NotImplementedError since it is intentionally 

1183 not supported. 

1184 """ 

1185 raise NotImplementedError( 

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

1187 ) 

1188 

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

1190 """ 

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

1192 

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

1194 """ 

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

1196 

1197 def latency_latest(self) -> ResponseT: 

1198 """ 

1199 Reports the latest latency events logged. 

1200 

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

1202 """ 

1203 return self.execute_command("LATENCY LATEST") 

1204 

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

1206 """ 

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

1208 

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

1210 """ 

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

1212 

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

1214 """ 

1215 Ping the Redis server to test connectivity. 

1216 

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

1218 responds with "PONG". 

1219 

1220 This command is useful for: 

1221 - Testing whether a connection is still alive 

1222 - Verifying the server's ability to serve data 

1223 

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

1225 """ 

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

1227 

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

1229 """ 

1230 Ask the server to close the connection. 

1231 

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

1233 """ 

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

1235 

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

1237 """ 

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

1239 

1240 Examples of valid arguments include: 

1241 

1242 NO ONE (set no replication) 

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

1244 

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

1246 """ 

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

1248 

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

1250 """ 

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

1252 blocking until the save is complete 

1253 

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

1255 """ 

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

1257 

1258 def shutdown( 

1259 self, 

1260 save: bool = False, 

1261 nosave: bool = False, 

1262 now: bool = False, 

1263 force: bool = False, 

1264 abort: bool = False, 

1265 **kwargs, 

1266 ) -> None: 

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

1268 data will be flushed before shutdown. 

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

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

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

1272 are configured. 

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

1274 the shutdown sequence. 

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

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

1277 

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

1279 """ 

1280 if save and nosave: 

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

1282 args = ["SHUTDOWN"] 

1283 if save: 

1284 args.append("SAVE") 

1285 if nosave: 

1286 args.append("NOSAVE") 

1287 if now: 

1288 args.append("NOW") 

1289 if force: 

1290 args.append("FORCE") 

1291 if abort: 

1292 args.append("ABORT") 

1293 try: 

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

1295 except ConnectionError: 

1296 # a ConnectionError here is expected 

1297 return 

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

1299 

1300 def slaveof( 

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

1302 ) -> ResponseT: 

1303 """ 

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

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

1306 instance is promoted to a master instead. 

1307 

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

1309 """ 

1310 if host is None and port is None: 

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

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

1313 

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

1315 """ 

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

1317 most recent ``num`` items. 

1318 

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

1320 """ 

1321 from redis.client import NEVER_DECODE 

1322 

1323 args = ["SLOWLOG GET"] 

1324 if num is not None: 

1325 args.append(num) 

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

1327 if decode_responses is True: 

1328 kwargs[NEVER_DECODE] = [] 

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

1330 

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

1332 """ 

1333 Get the number of items in the slowlog 

1334 

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

1336 """ 

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

1338 

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

1340 """ 

1341 Remove all items in the slowlog 

1342 

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

1344 """ 

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

1346 

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

1348 """ 

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

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

1351 

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

1353 """ 

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

1355 

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

1357 """ 

1358 Redis synchronous replication 

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

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

1361 reached. 

1362 

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

1364 """ 

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

1366 

1367 def waitaof( 

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

1369 ) -> ResponseT: 

1370 """ 

1371 This command blocks the current client until all previous write 

1372 commands by that client are acknowledged as having been fsynced 

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

1374 of replicas. 

1375 

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

1377 """ 

1378 return self.execute_command( 

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

1380 ) 

1381 

1382 def hello(self): 

1383 """ 

1384 This function throws a NotImplementedError since it is intentionally 

1385 not supported. 

1386 """ 

1387 raise NotImplementedError( 

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

1389 ) 

1390 

1391 def failover(self): 

1392 """ 

1393 This function throws a NotImplementedError since it is intentionally 

1394 not supported. 

1395 """ 

1396 raise NotImplementedError( 

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

1398 ) 

1399 

1400 

1401class AsyncManagementCommands(ManagementCommands): 

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

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

1404 

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

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

1407 

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

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

1410 

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

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

1413 

1414 async def shutdown( 

1415 self, 

1416 save: bool = False, 

1417 nosave: bool = False, 

1418 now: bool = False, 

1419 force: bool = False, 

1420 abort: bool = False, 

1421 **kwargs, 

1422 ) -> None: 

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

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

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

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

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

1428 

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

1430 """ 

1431 if save and nosave: 

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

1433 args = ["SHUTDOWN"] 

1434 if save: 

1435 args.append("SAVE") 

1436 if nosave: 

1437 args.append("NOSAVE") 

1438 if now: 

1439 args.append("NOW") 

1440 if force: 

1441 args.append("FORCE") 

1442 if abort: 

1443 args.append("ABORT") 

1444 try: 

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

1446 except ConnectionError: 

1447 # a ConnectionError here is expected 

1448 return 

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

1450 

1451 

1452class BitFieldOperation: 

1453 """ 

1454 Command builder for BITFIELD commands. 

1455 """ 

1456 

1457 def __init__( 

1458 self, 

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

1460 key: str, 

1461 default_overflow: Optional[str] = None, 

1462 ): 

1463 self.client = client 

1464 self.key = key 

1465 self._default_overflow = default_overflow 

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

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

1468 self._last_overflow = "WRAP" 

1469 self.reset() 

1470 

1471 def reset(self): 

1472 """ 

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

1474 """ 

1475 self.operations = [] 

1476 self._last_overflow = "WRAP" 

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

1478 

1479 def overflow(self, overflow: str): 

1480 """ 

1481 Update the overflow algorithm of successive INCRBY operations 

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

1483 Redis docs for descriptions of these algorithmsself. 

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

1485 """ 

1486 overflow = overflow.upper() 

1487 if overflow != self._last_overflow: 

1488 self._last_overflow = overflow 

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

1490 return self 

1491 

1492 def incrby( 

1493 self, 

1494 fmt: str, 

1495 offset: BitfieldOffsetT, 

1496 increment: int, 

1497 overflow: Optional[str] = None, 

1498 ): 

1499 """ 

1500 Increment a bitfield by a given amount. 

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

1502 for an unsigned 8-bit integer. 

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

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

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

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

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

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

1509 descriptions of these algorithms. 

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

1511 """ 

1512 if overflow is not None: 

1513 self.overflow(overflow) 

1514 

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

1516 return self 

1517 

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

1519 """ 

1520 Get the value of a given bitfield. 

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

1522 an unsigned 8-bit integer. 

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

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

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

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

1527 """ 

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

1529 return self 

1530 

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

1532 """ 

1533 Set the value of a given bitfield. 

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

1535 an unsigned 8-bit integer. 

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

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

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

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

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

1541 """ 

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

1543 return self 

1544 

1545 @property 

1546 def command(self): 

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

1548 for ops in self.operations: 

1549 cmd.extend(ops) 

1550 return cmd 

1551 

1552 def execute(self) -> ResponseT: 

1553 """ 

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

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

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

1557 will be present within the pipeline's execute. 

1558 """ 

1559 command = self.command 

1560 self.reset() 

1561 return self.client.execute_command(*command) 

1562 

1563 

1564class DataPersistOptions(Enum): 

1565 # set the value for each provided key to each 

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

1567 NX = "NX" 

1568 

1569 # set the value for each provided key to each 

1570 # provided value only if all already exist. 

1571 XX = "XX" 

1572 

1573 

1574class BasicKeyCommands(CommandsProtocol): 

1575 """ 

1576 Redis basic key-based commands 

1577 """ 

1578 

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

1580 """ 

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

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

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

1584 

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

1586 """ 

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

1588 

1589 def bitcount( 

1590 self, 

1591 key: KeyT, 

1592 start: Optional[int] = None, 

1593 end: Optional[int] = None, 

1594 mode: Optional[str] = None, 

1595 ) -> ResponseT: 

1596 """ 

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

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

1599 

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

1601 """ 

1602 params = [key] 

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

1604 params.append(start) 

1605 params.append(end) 

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

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

1608 if mode is not None: 

1609 params.append(mode) 

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

1611 

1612 def bitfield( 

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

1614 key: KeyT, 

1615 default_overflow: Optional[str] = None, 

1616 ) -> BitFieldOperation: 

1617 """ 

1618 Return a BitFieldOperation instance to conveniently construct one or 

1619 more bitfield operations on ``key``. 

1620 

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

1622 """ 

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

1624 

1625 def bitfield_ro( 

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

1627 key: KeyT, 

1628 encoding: str, 

1629 offset: BitfieldOffsetT, 

1630 items: Optional[list] = None, 

1631 ) -> ResponseT: 

1632 """ 

1633 Return an array of the specified bitfield values 

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

1635 parameters and remaining values are result of corresponding 

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

1637 Read-only variant of the BITFIELD command. 

1638 

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

1640 """ 

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

1642 

1643 items = items or [] 

1644 for encoding, offset in items: 

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

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

1647 

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

1649 """ 

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

1651 store the result in ``dest``. 

1652 

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

1654 """ 

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

1656 

1657 def bitpos( 

1658 self, 

1659 key: KeyT, 

1660 bit: int, 

1661 start: Optional[int] = None, 

1662 end: Optional[int] = None, 

1663 mode: Optional[str] = None, 

1664 ) -> ResponseT: 

1665 """ 

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

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

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

1669 means to look at the first three bytes. 

1670 

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

1672 """ 

1673 if bit not in (0, 1): 

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

1675 params = [key, bit] 

1676 

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

1678 

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

1680 params.append(end) 

1681 elif start is None and end is not None: 

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

1683 

1684 if mode is not None: 

1685 params.append(mode) 

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

1687 

1688 def copy( 

1689 self, 

1690 source: str, 

1691 destination: str, 

1692 destination_db: Optional[str] = None, 

1693 replace: bool = False, 

1694 ) -> ResponseT: 

1695 """ 

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

1697 

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

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

1700 

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

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

1703 the ``destination`` key already exists. 

1704 

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

1706 """ 

1707 params = [source, destination] 

1708 if destination_db is not None: 

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

1710 if replace: 

1711 params.append("REPLACE") 

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

1713 

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

1715 """ 

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

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

1718 

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

1720 """ 

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

1722 

1723 decr = decrby 

1724 

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

1726 """ 

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

1728 """ 

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

1730 

1731 def __delitem__(self, name: KeyT): 

1732 self.delete(name) 

1733 

1734 @experimental_method() 

1735 def delex( 

1736 self, 

1737 name: KeyT, 

1738 ifeq: Optional[Union[bytes, str]] = None, 

1739 ifne: Optional[Union[bytes, str]] = None, 

1740 ifdeq: Optional[str] = None, # hex digest 

1741 ifdne: Optional[str] = None, # hex digest 

1742 ) -> int: 

1743 """ 

1744 Conditionally removes the specified key. 

1745 

1746 Warning: 

1747 **Experimental** since 7.1. 

1748 This API may change or be removed without notice. 

1749 The API may change based on feedback. 

1750 

1751 Arguments: 

1752 name: KeyT - the key to delete 

1753 ifeq match-valu: Optional[Union[bytes, str]] - Delete the key only if its value is equal to match-value 

1754 ifne match-value: Optional[Union[bytes, str]] - Delete the key only if its value is not equal to match-value 

1755 ifdeq match-digest: Optional[str] - Delete the key only if the digest of its value is equal to match-digest 

1756 ifdne match-digest: Optional[str] - Delete the key only if the digest of its value is not equal to match-digest 

1757 

1758 Returns: 

1759 int: 1 if the key was deleted, 0 otherwise. 

1760 Raises: 

1761 redis.exceptions.ResponseError: if key exists but is not a string 

1762 and a condition is specified. 

1763 ValueError: if more than one condition is provided. 

1764 

1765 

1766 Requires Redis 8.4 or greater. 

1767 For more information, see https://redis.io/commands/delex 

1768 """ 

1769 conds = [x is not None for x in (ifeq, ifne, ifdeq, ifdne)] 

1770 if sum(conds) > 1: 

1771 raise ValueError("Only one of IFEQ/IFNE/IFDEQ/IFDNE may be specified") 

1772 

1773 pieces = ["DELEX", name] 

1774 if ifeq is not None: 

1775 pieces += ["IFEQ", ifeq] 

1776 elif ifne is not None: 

1777 pieces += ["IFNE", ifne] 

1778 elif ifdeq is not None: 

1779 pieces += ["IFDEQ", ifdeq] 

1780 elif ifdne is not None: 

1781 pieces += ["IFDNE", ifdne] 

1782 

1783 return self.execute_command(*pieces) 

1784 

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

1786 """ 

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

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

1789 

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

1791 """ 

1792 from redis.client import NEVER_DECODE 

1793 

1794 options = {} 

1795 options[NEVER_DECODE] = [] 

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

1797 

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

1799 """ 

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

1801 

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

1803 """ 

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

1805 

1806 __contains__ = exists 

1807 

1808 def expire( 

1809 self, 

1810 name: KeyT, 

1811 time: ExpiryT, 

1812 nx: bool = False, 

1813 xx: bool = False, 

1814 gt: bool = False, 

1815 lt: bool = False, 

1816 ) -> ResponseT: 

1817 """ 

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

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

1820 object. 

1821 

1822 Valid options are: 

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

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

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

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

1827 

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

1829 """ 

1830 if isinstance(time, datetime.timedelta): 

1831 time = int(time.total_seconds()) 

1832 

1833 exp_option = list() 

1834 if nx: 

1835 exp_option.append("NX") 

1836 if xx: 

1837 exp_option.append("XX") 

1838 if gt: 

1839 exp_option.append("GT") 

1840 if lt: 

1841 exp_option.append("LT") 

1842 

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

1844 

1845 def expireat( 

1846 self, 

1847 name: KeyT, 

1848 when: AbsExpiryT, 

1849 nx: bool = False, 

1850 xx: bool = False, 

1851 gt: bool = False, 

1852 lt: bool = False, 

1853 ) -> ResponseT: 

1854 """ 

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

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

1857 datetime object. 

1858 

1859 Valid options are: 

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

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

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

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

1864 

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

1866 """ 

1867 if isinstance(when, datetime.datetime): 

1868 when = int(when.timestamp()) 

1869 

1870 exp_option = list() 

1871 if nx: 

1872 exp_option.append("NX") 

1873 if xx: 

1874 exp_option.append("XX") 

1875 if gt: 

1876 exp_option.append("GT") 

1877 if lt: 

1878 exp_option.append("LT") 

1879 

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

1881 

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

1883 """ 

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

1885 at which the given key will expire. 

1886 

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

1888 """ 

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

1890 

1891 @experimental_method() 

1892 def digest(self, name: KeyT) -> Optional[str]: 

1893 """ 

1894 Return the digest of the value stored at the specified key. 

1895 

1896 Warning: 

1897 **Experimental** since 7.1. 

1898 This API may change or be removed without notice. 

1899 The API may change based on feedback. 

1900 

1901 Arguments: 

1902 - name: KeyT - the key to get the digest of 

1903 

1904 Returns: 

1905 - None if the key does not exist 

1906 - (bulk string) the XXH3 digest of the value as a hex string 

1907 Raises: 

1908 - ResponseError if key exists but is not a string 

1909 

1910 

1911 Requires Redis 8.4 or greater. 

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

1913 """ 

1914 # Bulk string response is already handled (bytes/str based on decode_responses) 

1915 return self.execute_command("DIGEST", name) 

1916 

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

1918 """ 

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

1920 

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

1922 """ 

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

1924 

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

1926 """ 

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

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

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

1930 is a string). 

1931 

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

1933 """ 

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

1935 

1936 def getex( 

1937 self, 

1938 name: KeyT, 

1939 ex: Optional[ExpiryT] = None, 

1940 px: Optional[ExpiryT] = None, 

1941 exat: Optional[AbsExpiryT] = None, 

1942 pxat: Optional[AbsExpiryT] = None, 

1943 persist: bool = False, 

1944 ) -> ResponseT: 

1945 """ 

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

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

1948 additional options. All time parameters can be given as 

1949 datetime.timedelta or integers. 

1950 

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

1952 

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

1954 

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

1956 specified in unix time. 

1957 

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

1959 specified in unix time. 

1960 

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

1962 

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

1964 """ 

1965 if not at_most_one_value_set((ex, px, exat, pxat, persist)): 

1966 raise DataError( 

1967 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

1969 ) 

1970 

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

1972 

1973 if persist: 

1974 exp_options.append("PERSIST") 

1975 

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

1977 

1978 def __getitem__(self, name: KeyT): 

1979 """ 

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

1981 doesn't exist. 

1982 """ 

1983 value = self.get(name) 

1984 if value is not None: 

1985 return value 

1986 raise KeyError(name) 

1987 

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

1989 """ 

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

1991 

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

1993 """ 

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

1995 

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

1997 """ 

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

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

2000 

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

2002 """ 

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

2004 

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

2006 """ 

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

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

2009 

2010 As per Redis 6.2, GETSET is considered deprecated. 

2011 Please use SET with GET parameter in new code. 

2012 

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

2014 """ 

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

2016 

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

2018 """ 

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

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

2021 

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

2023 """ 

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

2025 

2026 incr = incrby 

2027 

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

2029 """ 

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

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

2032 

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

2034 """ 

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

2036 

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

2038 """ 

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

2040 

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

2042 """ 

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

2044 

2045 def lmove( 

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

2047 ) -> ResponseT: 

2048 """ 

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

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

2051 Returns the element being popped and pushed. 

2052 

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

2054 """ 

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

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

2057 

2058 def blmove( 

2059 self, 

2060 first_list: str, 

2061 second_list: str, 

2062 timeout: int, 

2063 src: str = "LEFT", 

2064 dest: str = "RIGHT", 

2065 ) -> ResponseT: 

2066 """ 

2067 Blocking version of lmove. 

2068 

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

2070 """ 

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

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

2073 

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

2075 """ 

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

2077 

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

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

2080 will be raised. 

2081 

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

2083 """ 

2084 from redis.client import EMPTY_RESPONSE 

2085 

2086 args = list_or_args(keys, args) 

2087 options = {} 

2088 if not args: 

2089 options[EMPTY_RESPONSE] = [] 

2090 options["keys"] = args 

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

2092 

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

2094 """ 

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

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

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

2098 

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

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

2101 will be raised. 

2102 

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

2104 """ 

2105 items = [] 

2106 for pair in mapping.items(): 

2107 items.extend(pair) 

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

2109 

2110 def msetex( 

2111 self, 

2112 mapping: Mapping[AnyKeyT, EncodableT], 

2113 data_persist_option: Optional[DataPersistOptions] = None, 

2114 ex: Optional[ExpiryT] = None, 

2115 px: Optional[ExpiryT] = None, 

2116 exat: Optional[AbsExpiryT] = None, 

2117 pxat: Optional[AbsExpiryT] = None, 

2118 keepttl: bool = False, 

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

2120 """ 

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

2122 

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

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

2125 will be raised. 

2126 

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

2128 

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

2130 behavior of the command. 

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

2132 provided value only if all do not already exist. 

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

2134 provided value only if all already exist. 

2135 

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

2137 

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

2139 

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

2141 specified in unix time. 

2142 

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

2144 specified in unix time. 

2145 

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

2147 

2148 Returns the number of fields that were added. 

2149 

2150 Available since Redis 8.4 

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

2152 """ 

2153 if not at_most_one_value_set((ex, px, exat, pxat, keepttl)): 

2154 raise DataError( 

2155 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

2157 ) 

2158 

2159 exp_options: list[EncodableT] = [] 

2160 if data_persist_option: 

2161 exp_options.append(data_persist_option.value) 

2162 

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

2164 

2165 if keepttl: 

2166 exp_options.append("KEEPTTL") 

2167 

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

2169 

2170 for pair in mapping.items(): 

2171 pieces.extend(pair) 

2172 

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

2174 

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

2176 """ 

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

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

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

2180 Returns a boolean indicating if the operation was successful. 

2181 

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

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

2184 will be raised. 

2185 

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

2187 """ 

2188 items = [] 

2189 for pair in mapping.items(): 

2190 items.extend(pair) 

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

2192 

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

2194 """ 

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

2196 

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

2198 """ 

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

2200 

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

2202 """ 

2203 Removes an expiration on ``name`` 

2204 

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

2206 """ 

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

2208 

2209 def pexpire( 

2210 self, 

2211 name: KeyT, 

2212 time: ExpiryT, 

2213 nx: bool = False, 

2214 xx: bool = False, 

2215 gt: bool = False, 

2216 lt: bool = False, 

2217 ) -> ResponseT: 

2218 """ 

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

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

2221 integer or a Python timedelta object. 

2222 

2223 Valid options are: 

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

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

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

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

2228 

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

2230 """ 

2231 if isinstance(time, datetime.timedelta): 

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

2233 

2234 exp_option = list() 

2235 if nx: 

2236 exp_option.append("NX") 

2237 if xx: 

2238 exp_option.append("XX") 

2239 if gt: 

2240 exp_option.append("GT") 

2241 if lt: 

2242 exp_option.append("LT") 

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

2244 

2245 def pexpireat( 

2246 self, 

2247 name: KeyT, 

2248 when: AbsExpiryT, 

2249 nx: bool = False, 

2250 xx: bool = False, 

2251 gt: bool = False, 

2252 lt: bool = False, 

2253 ) -> ResponseT: 

2254 """ 

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

2256 can be represented as an integer representing unix time in 

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

2258 

2259 Valid options are: 

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

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

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

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

2264 

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

2266 """ 

2267 if isinstance(when, datetime.datetime): 

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

2269 exp_option = list() 

2270 if nx: 

2271 exp_option.append("NX") 

2272 if xx: 

2273 exp_option.append("XX") 

2274 if gt: 

2275 exp_option.append("GT") 

2276 if lt: 

2277 exp_option.append("LT") 

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

2279 

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

2281 """ 

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

2283 at which the given key will expire. 

2284 

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

2286 """ 

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

2288 

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

2290 """ 

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

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

2293 timedelta object 

2294 

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

2296 """ 

2297 if isinstance(time_ms, datetime.timedelta): 

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

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

2300 

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

2302 """ 

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

2304 

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

2306 """ 

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

2308 

2309 def hrandfield( 

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

2311 ) -> ResponseT: 

2312 """ 

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

2314 

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

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

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

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

2319 specified count. 

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

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

2322 

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

2324 """ 

2325 params = [] 

2326 if count is not None: 

2327 params.append(count) 

2328 if withvalues: 

2329 params.append("WITHVALUES") 

2330 

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

2332 

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

2334 """ 

2335 Returns the name of a random key 

2336 

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

2338 """ 

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

2340 

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

2342 """ 

2343 Rename key ``src`` to ``dst`` 

2344 

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

2346 """ 

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

2348 

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

2350 """ 

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

2352 

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

2354 """ 

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

2356 

2357 def restore( 

2358 self, 

2359 name: KeyT, 

2360 ttl: float, 

2361 value: EncodableT, 

2362 replace: bool = False, 

2363 absttl: bool = False, 

2364 idletime: Optional[int] = None, 

2365 frequency: Optional[int] = None, 

2366 ) -> ResponseT: 

2367 """ 

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

2369 using DUMP. 

2370 

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

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

2373 

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

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

2376 greater). 

2377 

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

2379 key must be idle, prior to execution. 

2380 

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

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

2383 

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

2385 """ 

2386 params = [name, ttl, value] 

2387 if replace: 

2388 params.append("REPLACE") 

2389 if absttl: 

2390 params.append("ABSTTL") 

2391 if idletime is not None: 

2392 params.append("IDLETIME") 

2393 try: 

2394 params.append(int(idletime)) 

2395 except ValueError: 

2396 raise DataError("idletimemust be an integer") 

2397 

2398 if frequency is not None: 

2399 params.append("FREQ") 

2400 try: 

2401 params.append(int(frequency)) 

2402 except ValueError: 

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

2404 

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

2406 

2407 @experimental_args(["ifeq", "ifne", "ifdeq", "ifdne"]) 

2408 def set( 

2409 self, 

2410 name: KeyT, 

2411 value: EncodableT, 

2412 ex: Optional[ExpiryT] = None, 

2413 px: Optional[ExpiryT] = None, 

2414 nx: bool = False, 

2415 xx: bool = False, 

2416 keepttl: bool = False, 

2417 get: bool = False, 

2418 exat: Optional[AbsExpiryT] = None, 

2419 pxat: Optional[AbsExpiryT] = None, 

2420 ifeq: Optional[Union[bytes, str]] = None, 

2421 ifne: Optional[Union[bytes, str]] = None, 

2422 ifdeq: Optional[str] = None, # hex digest of current value 

2423 ifdne: Optional[str] = None, # hex digest of current value 

2424 ) -> ResponseT: 

2425 """ 

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

2427 

2428 Warning: 

2429 **Experimental** since 7.1. 

2430 The usage of the arguments ``ifeq``, ``ifne``, ``ifdeq``, and ``ifdne`` 

2431 is experimental. The API or returned results when those parameters are used 

2432 may change based on feedback. 

2433 

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

2435 

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

2437 

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

2439 if it does not exist. 

2440 

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

2442 if it already exists. 

2443 

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

2445 (Available since Redis 6.0) 

2446 

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

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

2449 (Available since Redis 6.2) 

2450 

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

2452 specified in unix time. 

2453 

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

2455 specified in unix time. 

2456 

2457 ``ifeq`` set the value at key ``name`` to ``value`` only if the current 

2458 value exactly matches the argument. 

2459 If key doesn’t exist - it won’t be created. 

2460 (Requires Redis 8.4 or greater) 

2461 

2462 ``ifne`` set the value at key ``name`` to ``value`` only if the current 

2463 value does not exactly match the argument. 

2464 If key doesn’t exist - it will be created. 

2465 (Requires Redis 8.4 or greater) 

2466 

2467 ``ifdeq`` set the value at key ``name`` to ``value`` only if the current 

2468 value XXH3 hex digest exactly matches the argument. 

2469 If key doesn’t exist - it won’t be created. 

2470 (Requires Redis 8.4 or greater) 

2471 

2472 ``ifdne`` set the value at key ``name`` to ``value`` only if the current 

2473 value XXH3 hex digest does not exactly match the argument. 

2474 If key doesn’t exist - it will be created. 

2475 (Requires Redis 8.4 or greater) 

2476 

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

2478 """ 

2479 

2480 if not at_most_one_value_set((ex, px, exat, pxat, keepttl)): 

2481 raise DataError( 

2482 "``ex``, ``px``, ``exat``, ``pxat``, " 

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

2484 ) 

2485 

2486 # Enforce mutual exclusivity among all conditional switches. 

2487 if not at_most_one_value_set((nx, xx, ifeq, ifne, ifdeq, ifdne)): 

2488 raise DataError( 

2489 "``nx``, ``xx``, ``ifeq``, ``ifne``, ``ifdeq``, ``ifdne`` are mutually exclusive." 

2490 ) 

2491 

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

2493 options = {} 

2494 

2495 # Conditional modifier (exactly one at most) 

2496 if nx: 

2497 pieces.append("NX") 

2498 elif xx: 

2499 pieces.append("XX") 

2500 elif ifeq is not None: 

2501 pieces.extend(("IFEQ", ifeq)) 

2502 elif ifne is not None: 

2503 pieces.extend(("IFNE", ifne)) 

2504 elif ifdeq is not None: 

2505 pieces.extend(("IFDEQ", ifdeq)) 

2506 elif ifdne is not None: 

2507 pieces.extend(("IFDNE", ifdne)) 

2508 

2509 if get: 

2510 pieces.append("GET") 

2511 options["get"] = True 

2512 

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

2514 

2515 if keepttl: 

2516 pieces.append("KEEPTTL") 

2517 

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

2519 

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

2521 self.set(name, value) 

2522 

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

2524 """ 

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

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

2527 

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

2529 """ 

2530 value = value and 1 or 0 

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

2532 

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

2534 """ 

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

2536 seconds. ``time`` can be represented by an integer or a Python 

2537 timedelta object. 

2538 

2539 For more information, see https://redis.io/commands/setex 

2540 """ 

2541 if isinstance(time, datetime.timedelta): 

2542 time = int(time.total_seconds()) 

2543 return self.execute_command("SETEX", name, time, value) 

2544 

2545 def setnx(self, name: KeyT, value: EncodableT) -> ResponseT: 

2546 """ 

2547 Set the value of key ``name`` to ``value`` if key doesn't exist 

2548 

2549 For more information, see https://redis.io/commands/setnx 

2550 """ 

2551 return self.execute_command("SETNX", name, value) 

2552 

2553 def setrange(self, name: KeyT, offset: int, value: EncodableT) -> ResponseT: 

2554 """ 

2555 Overwrite bytes in the value of ``name`` starting at ``offset`` with 

2556 ``value``. If ``offset`` plus the length of ``value`` exceeds the 

2557 length of the original value, the new value will be larger than before. 

2558 If ``offset`` exceeds the length of the original value, null bytes 

2559 will be used to pad between the end of the previous value and the start 

2560 of what's being injected. 

2561 

2562 Returns the length of the new string. 

2563 

2564 For more information, see https://redis.io/commands/setrange 

2565 """ 

2566 return self.execute_command("SETRANGE", name, offset, value) 

2567 

2568 def stralgo( 

2569 self, 

2570 algo: Literal["LCS"], 

2571 value1: KeyT, 

2572 value2: KeyT, 

2573 specific_argument: Union[Literal["strings"], Literal["keys"]] = "strings", 

2574 len: bool = False, 

2575 idx: bool = False, 

2576 minmatchlen: Optional[int] = None, 

2577 withmatchlen: bool = False, 

2578 **kwargs, 

2579 ) -> ResponseT: 

2580 """ 

2581 Implements complex algorithms that operate on strings. 

2582 Right now the only algorithm implemented is the LCS algorithm 

2583 (longest common substring). However new algorithms could be 

2584 implemented in the future. 

2585 

2586 ``algo`` Right now must be LCS 

2587 ``value1`` and ``value2`` Can be two strings or two keys 

2588 ``specific_argument`` Specifying if the arguments to the algorithm 

2589 will be keys or strings. strings is the default. 

2590 ``len`` Returns just the len of the match. 

2591 ``idx`` Returns the match positions in each string. 

2592 ``minmatchlen`` Restrict the list of matches to the ones of a given 

2593 minimal length. Can be provided only when ``idx`` set to True. 

2594 ``withmatchlen`` Returns the matches with the len of the match. 

2595 Can be provided only when ``idx`` set to True. 

2596 

2597 For more information, see https://redis.io/commands/stralgo 

2598 """ 

2599 # check validity 

2600 supported_algo = ["LCS"] 

2601 if algo not in supported_algo: 

2602 supported_algos_str = ", ".join(supported_algo) 

2603 raise DataError(f"The supported algorithms are: {supported_algos_str}") 

2604 if specific_argument not in ["keys", "strings"]: 

2605 raise DataError("specific_argument can be only keys or strings") 

2606 if len and idx: 

2607 raise DataError("len and idx cannot be provided together.") 

2608 

2609 pieces: list[EncodableT] = [algo, specific_argument.upper(), value1, value2] 

2610 if len: 

2611 pieces.append(b"LEN") 

2612 if idx: 

2613 pieces.append(b"IDX") 

2614 try: 

2615 int(minmatchlen) 

2616 pieces.extend([b"MINMATCHLEN", minmatchlen]) 

2617 except TypeError: 

2618 pass 

2619 if withmatchlen: 

2620 pieces.append(b"WITHMATCHLEN") 

2621 

2622 return self.execute_command( 

2623 "STRALGO", 

2624 *pieces, 

2625 len=len, 

2626 idx=idx, 

2627 minmatchlen=minmatchlen, 

2628 withmatchlen=withmatchlen, 

2629 **kwargs, 

2630 ) 

2631 

2632 def strlen(self, name: KeyT) -> ResponseT: 

2633 """ 

2634 Return the number of bytes stored in the value of ``name`` 

2635 

2636 For more information, see https://redis.io/commands/strlen 

2637 """ 

2638 return self.execute_command("STRLEN", name, keys=[name]) 

2639 

2640 def substr(self, name: KeyT, start: int, end: int = -1) -> ResponseT: 

2641 """ 

2642 Return a substring of the string at key ``name``. ``start`` and ``end`` 

2643 are 0-based integers specifying the portion of the string to return. 

2644 """ 

2645 return self.execute_command("SUBSTR", name, start, end, keys=[name]) 

2646 

2647 def touch(self, *args: KeyT) -> ResponseT: 

2648 """ 

2649 Alters the last access time of a key(s) ``*args``. A key is ignored 

2650 if it does not exist. 

2651 

2652 For more information, see https://redis.io/commands/touch 

2653 """ 

2654 return self.execute_command("TOUCH", *args) 

2655 

2656 def ttl(self, name: KeyT) -> ResponseT: 

2657 """ 

2658 Returns the number of seconds until the key ``name`` will expire 

2659 

2660 For more information, see https://redis.io/commands/ttl 

2661 """ 

2662 return self.execute_command("TTL", name) 

2663 

2664 def type(self, name: KeyT) -> ResponseT: 

2665 """ 

2666 Returns the type of key ``name`` 

2667 

2668 For more information, see https://redis.io/commands/type 

2669 """ 

2670 return self.execute_command("TYPE", name, keys=[name]) 

2671 

2672 def watch(self, *names: KeyT) -> None: 

2673 """ 

2674 Watches the values at keys ``names``, or None if the key doesn't exist 

2675 

2676 For more information, see https://redis.io/commands/watch 

2677 """ 

2678 warnings.warn(DeprecationWarning("Call WATCH from a Pipeline object")) 

2679 

2680 def unwatch(self) -> None: 

2681 """ 

2682 Unwatches all previously watched keys for a transaction 

2683 

2684 For more information, see https://redis.io/commands/unwatch 

2685 """ 

2686 warnings.warn(DeprecationWarning("Call UNWATCH from a Pipeline object")) 

2687 

2688 def unlink(self, *names: KeyT) -> ResponseT: 

2689 """ 

2690 Unlink one or more keys specified by ``names`` 

2691 

2692 For more information, see https://redis.io/commands/unlink 

2693 """ 

2694 return self.execute_command("UNLINK", *names) 

2695 

2696 def lcs( 

2697 self, 

2698 key1: str, 

2699 key2: str, 

2700 len: Optional[bool] = False, 

2701 idx: Optional[bool] = False, 

2702 minmatchlen: Optional[int] = 0, 

2703 withmatchlen: Optional[bool] = False, 

2704 ) -> Union[str, int, list]: 

2705 """ 

2706 Find the longest common subsequence between ``key1`` and ``key2``. 

2707 If ``len`` is true the length of the match will will be returned. 

2708 If ``idx`` is true the match position in each strings will be returned. 

2709 ``minmatchlen`` restrict the list of matches to the ones of 

2710 the given ``minmatchlen``. 

2711 If ``withmatchlen`` the length of the match also will be returned. 

2712 For more information, see https://redis.io/commands/lcs 

2713 """ 

2714 pieces = [key1, key2] 

2715 if len: 

2716 pieces.append("LEN") 

2717 if idx: 

2718 pieces.append("IDX") 

2719 if minmatchlen != 0: 

2720 pieces.extend(["MINMATCHLEN", minmatchlen]) 

2721 if withmatchlen: 

2722 pieces.append("WITHMATCHLEN") 

2723 return self.execute_command("LCS", *pieces, keys=[key1, key2]) 

2724 

2725 

2726class AsyncBasicKeyCommands(BasicKeyCommands): 

2727 def __delitem__(self, name: KeyT): 

2728 raise TypeError("Async Redis client does not support class deletion") 

2729 

2730 def __contains__(self, name: KeyT): 

2731 raise TypeError("Async Redis client does not support class inclusion") 

2732 

2733 def __getitem__(self, name: KeyT): 

2734 raise TypeError("Async Redis client does not support class retrieval") 

2735 

2736 def __setitem__(self, name: KeyT, value: EncodableT): 

2737 raise TypeError("Async Redis client does not support class assignment") 

2738 

2739 async def watch(self, *names: KeyT) -> None: 

2740 return super().watch(*names) 

2741 

2742 async def unwatch(self) -> None: 

2743 return super().unwatch() 

2744 

2745 

2746class ListCommands(CommandsProtocol): 

2747 """ 

2748 Redis commands for List data type. 

2749 see: https://redis.io/topics/data-types#lists 

2750 """ 

2751 

2752 def blpop( 

2753 self, keys: List, timeout: Optional[Number] = 0 

2754 ) -> Union[Awaitable[list], list]: 

2755 """ 

2756 LPOP a value off of the first non-empty list 

2757 named in the ``keys`` list. 

2758 

2759 If none of the lists in ``keys`` has a value to LPOP, then block 

2760 for ``timeout`` seconds, or until a value gets pushed on to one 

2761 of the lists. 

2762 

2763 If timeout is 0, then block indefinitely. 

2764 

2765 For more information, see https://redis.io/commands/blpop 

2766 """ 

2767 if timeout is None: 

2768 timeout = 0 

2769 keys = list_or_args(keys, None) 

2770 keys.append(timeout) 

2771 return self.execute_command("BLPOP", *keys) 

2772 

2773 def brpop( 

2774 self, keys: List, timeout: Optional[Number] = 0 

2775 ) -> Union[Awaitable[list], list]: 

2776 """ 

2777 RPOP a value off of the first non-empty list 

2778 named in the ``keys`` list. 

2779 

2780 If none of the lists in ``keys`` has a value to RPOP, then block 

2781 for ``timeout`` seconds, or until a value gets pushed on to one 

2782 of the lists. 

2783 

2784 If timeout is 0, then block indefinitely. 

2785 

2786 For more information, see https://redis.io/commands/brpop 

2787 """ 

2788 if timeout is None: 

2789 timeout = 0 

2790 keys = list_or_args(keys, None) 

2791 keys.append(timeout) 

2792 return self.execute_command("BRPOP", *keys) 

2793 

2794 def brpoplpush( 

2795 self, src: KeyT, dst: KeyT, timeout: Optional[Number] = 0 

2796 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

2797 """ 

2798 Pop a value off the tail of ``src``, push it on the head of ``dst`` 

2799 and then return it. 

2800 

2801 This command blocks until a value is in ``src`` or until ``timeout`` 

2802 seconds elapse, whichever is first. A ``timeout`` value of 0 blocks 

2803 forever. 

2804 

2805 For more information, see https://redis.io/commands/brpoplpush 

2806 """ 

2807 if timeout is None: 

2808 timeout = 0 

2809 return self.execute_command("BRPOPLPUSH", src, dst, timeout) 

2810 

2811 def blmpop( 

2812 self, 

2813 timeout: float, 

2814 numkeys: int, 

2815 *args: str, 

2816 direction: str, 

2817 count: Optional[int] = 1, 

2818 ) -> Optional[list]: 

2819 """ 

2820 Pop ``count`` values (default 1) from first non-empty in the list 

2821 of provided key names. 

2822 

2823 When all lists are empty this command blocks the connection until another 

2824 client pushes to it or until the timeout, timeout of 0 blocks indefinitely 

2825 

2826 For more information, see https://redis.io/commands/blmpop 

2827 """ 

2828 cmd_args = [timeout, numkeys, *args, direction, "COUNT", count] 

2829 

2830 return self.execute_command("BLMPOP", *cmd_args) 

2831 

2832 def lmpop( 

2833 self, 

2834 num_keys: int, 

2835 *args: str, 

2836 direction: str, 

2837 count: Optional[int] = 1, 

2838 ) -> Union[Awaitable[list], list]: 

2839 """ 

2840 Pop ``count`` values (default 1) first non-empty list key from the list 

2841 of args provided key names. 

2842 

2843 For more information, see https://redis.io/commands/lmpop 

2844 """ 

2845 cmd_args = [num_keys] + list(args) + [direction] 

2846 if count != 1: 

2847 cmd_args.extend(["COUNT", count]) 

2848 

2849 return self.execute_command("LMPOP", *cmd_args) 

2850 

2851 def lindex( 

2852 self, name: KeyT, index: int 

2853 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

2854 """ 

2855 Return the item from list ``name`` at position ``index`` 

2856 

2857 Negative indexes are supported and will return an item at the 

2858 end of the list 

2859 

2860 For more information, see https://redis.io/commands/lindex 

2861 """ 

2862 return self.execute_command("LINDEX", name, index, keys=[name]) 

2863 

2864 def linsert( 

2865 self, name: KeyT, where: str, refvalue: str, value: str 

2866 ) -> Union[Awaitable[int], int]: 

2867 """ 

2868 Insert ``value`` in list ``name`` either immediately before or after 

2869 [``where``] ``refvalue`` 

2870 

2871 Returns the new length of the list on success or -1 if ``refvalue`` 

2872 is not in the list. 

2873 

2874 For more information, see https://redis.io/commands/linsert 

2875 """ 

2876 return self.execute_command("LINSERT", name, where, refvalue, value) 

2877 

2878 def llen(self, name: KeyT) -> Union[Awaitable[int], int]: 

2879 """ 

2880 Return the length of the list ``name`` 

2881 

2882 For more information, see https://redis.io/commands/llen 

2883 """ 

2884 return self.execute_command("LLEN", name, keys=[name]) 

2885 

2886 def lpop( 

2887 self, 

2888 name: KeyT, 

2889 count: Optional[int] = None, 

2890 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]: 

2891 """ 

2892 Removes and returns the first elements of the list ``name``. 

2893 

2894 By default, the command pops a single element from the beginning of 

2895 the list. When provided with the optional ``count`` argument, the reply 

2896 will consist of up to count elements, depending on the list's length. 

2897 

2898 For more information, see https://redis.io/commands/lpop 

2899 """ 

2900 if count is not None: 

2901 return self.execute_command("LPOP", name, count) 

2902 else: 

2903 return self.execute_command("LPOP", name) 

2904 

2905 def lpush(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]: 

2906 """ 

2907 Push ``values`` onto the head of the list ``name`` 

2908 

2909 For more information, see https://redis.io/commands/lpush 

2910 """ 

2911 return self.execute_command("LPUSH", name, *values) 

2912 

2913 def lpushx(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]: 

2914 """ 

2915 Push ``value`` onto the head of the list ``name`` if ``name`` exists 

2916 

2917 For more information, see https://redis.io/commands/lpushx 

2918 """ 

2919 return self.execute_command("LPUSHX", name, *values) 

2920 

2921 def lrange(self, name: KeyT, start: int, end: int) -> Union[Awaitable[list], list]: 

2922 """ 

2923 Return a slice of the list ``name`` between 

2924 position ``start`` and ``end`` 

2925 

2926 ``start`` and ``end`` can be negative numbers just like 

2927 Python slicing notation 

2928 

2929 For more information, see https://redis.io/commands/lrange 

2930 """ 

2931 return self.execute_command("LRANGE", name, start, end, keys=[name]) 

2932 

2933 def lrem(self, name: KeyT, count: int, value: str) -> Union[Awaitable[int], int]: 

2934 """ 

2935 Remove the first ``count`` occurrences of elements equal to ``value`` 

2936 from the list stored at ``name``. 

2937 

2938 The count argument influences the operation in the following ways: 

2939 count > 0: Remove elements equal to value moving from head to tail. 

2940 count < 0: Remove elements equal to value moving from tail to head. 

2941 count = 0: Remove all elements equal to value. 

2942 

2943 For more information, see https://redis.io/commands/lrem 

2944 """ 

2945 return self.execute_command("LREM", name, count, value) 

2946 

2947 def lset(self, name: KeyT, index: int, value: str) -> Union[Awaitable[str], str]: 

2948 """ 

2949 Set element at ``index`` of list ``name`` to ``value`` 

2950 

2951 For more information, see https://redis.io/commands/lset 

2952 """ 

2953 return self.execute_command("LSET", name, index, value) 

2954 

2955 def ltrim(self, name: KeyT, start: int, end: int) -> Union[Awaitable[str], str]: 

2956 """ 

2957 Trim the list ``name``, removing all values not within the slice 

2958 between ``start`` and ``end`` 

2959 

2960 ``start`` and ``end`` can be negative numbers just like 

2961 Python slicing notation 

2962 

2963 For more information, see https://redis.io/commands/ltrim 

2964 """ 

2965 return self.execute_command("LTRIM", name, start, end) 

2966 

2967 def rpop( 

2968 self, 

2969 name: KeyT, 

2970 count: Optional[int] = None, 

2971 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]: 

2972 """ 

2973 Removes and returns the last elements of the list ``name``. 

2974 

2975 By default, the command pops a single element from the end of the list. 

2976 When provided with the optional ``count`` argument, the reply will 

2977 consist of up to count elements, depending on the list's length. 

2978 

2979 For more information, see https://redis.io/commands/rpop 

2980 """ 

2981 if count is not None: 

2982 return self.execute_command("RPOP", name, count) 

2983 else: 

2984 return self.execute_command("RPOP", name) 

2985 

2986 def rpoplpush(self, src: KeyT, dst: KeyT) -> Union[Awaitable[str], str]: 

2987 """ 

2988 RPOP a value off of the ``src`` list and atomically LPUSH it 

2989 on to the ``dst`` list. Returns the value. 

2990 

2991 For more information, see https://redis.io/commands/rpoplpush 

2992 """ 

2993 return self.execute_command("RPOPLPUSH", src, dst) 

2994 

2995 def rpush(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]: 

2996 """ 

2997 Push ``values`` onto the tail of the list ``name`` 

2998 

2999 For more information, see https://redis.io/commands/rpush 

3000 """ 

3001 return self.execute_command("RPUSH", name, *values) 

3002 

3003 def rpushx(self, name: KeyT, *values: str) -> Union[Awaitable[int], int]: 

3004 """ 

3005 Push ``value`` onto the tail of the list ``name`` if ``name`` exists 

3006 

3007 For more information, see https://redis.io/commands/rpushx 

3008 """ 

3009 return self.execute_command("RPUSHX", name, *values) 

3010 

3011 def lpos( 

3012 self, 

3013 name: KeyT, 

3014 value: str, 

3015 rank: Optional[int] = None, 

3016 count: Optional[int] = None, 

3017 maxlen: Optional[int] = None, 

3018 ) -> Union[str, List, None]: 

3019 """ 

3020 Get position of ``value`` within the list ``name`` 

3021 

3022 If specified, ``rank`` indicates the "rank" of the first element to 

3023 return in case there are multiple copies of ``value`` in the list. 

3024 By default, LPOS returns the position of the first occurrence of 

3025 ``value`` in the list. When ``rank`` 2, LPOS returns the position of 

3026 the second ``value`` in the list. If ``rank`` is negative, LPOS 

3027 searches the list in reverse. For example, -1 would return the 

3028 position of the last occurrence of ``value`` and -2 would return the 

3029 position of the next to last occurrence of ``value``. 

3030 

3031 If specified, ``count`` indicates that LPOS should return a list of 

3032 up to ``count`` positions. A ``count`` of 2 would return a list of 

3033 up to 2 positions. A ``count`` of 0 returns a list of all positions 

3034 matching ``value``. When ``count`` is specified and but ``value`` 

3035 does not exist in the list, an empty list is returned. 

3036 

3037 If specified, ``maxlen`` indicates the maximum number of list 

3038 elements to scan. A ``maxlen`` of 1000 will only return the 

3039 position(s) of items within the first 1000 entries in the list. 

3040 A ``maxlen`` of 0 (the default) will scan the entire list. 

3041 

3042 For more information, see https://redis.io/commands/lpos 

3043 """ 

3044 pieces: list[EncodableT] = [name, value] 

3045 if rank is not None: 

3046 pieces.extend(["RANK", rank]) 

3047 

3048 if count is not None: 

3049 pieces.extend(["COUNT", count]) 

3050 

3051 if maxlen is not None: 

3052 pieces.extend(["MAXLEN", maxlen]) 

3053 

3054 return self.execute_command("LPOS", *pieces, keys=[name]) 

3055 

3056 def sort( 

3057 self, 

3058 name: KeyT, 

3059 start: Optional[int] = None, 

3060 num: Optional[int] = None, 

3061 by: Optional[str] = None, 

3062 get: Optional[List[str]] = None, 

3063 desc: bool = False, 

3064 alpha: bool = False, 

3065 store: Optional[str] = None, 

3066 groups: Optional[bool] = False, 

3067 ) -> Union[List, int]: 

3068 """ 

3069 Sort and return the list, set or sorted set at ``name``. 

3070 

3071 ``start`` and ``num`` allow for paging through the sorted data 

3072 

3073 ``by`` allows using an external key to weight and sort the items. 

3074 Use an "*" to indicate where in the key the item value is located 

3075 

3076 ``get`` allows for returning items from external keys rather than the 

3077 sorted data itself. Use an "*" to indicate where in the key 

3078 the item value is located 

3079 

3080 ``desc`` allows for reversing the sort 

3081 

3082 ``alpha`` allows for sorting lexicographically rather than numerically 

3083 

3084 ``store`` allows for storing the result of the sort into 

3085 the key ``store`` 

3086 

3087 ``groups`` if set to True and if ``get`` contains at least two 

3088 elements, sort will return a list of tuples, each containing the 

3089 values fetched from the arguments to ``get``. 

3090 

3091 For more information, see https://redis.io/commands/sort 

3092 """ 

3093 if (start is not None and num is None) or (num is not None and start is None): 

3094 raise DataError("``start`` and ``num`` must both be specified") 

3095 

3096 pieces: list[EncodableT] = [name] 

3097 if by is not None: 

3098 pieces.extend([b"BY", by]) 

3099 if start is not None and num is not None: 

3100 pieces.extend([b"LIMIT", start, num]) 

3101 if get is not None: 

3102 # If get is a string assume we want to get a single value. 

3103 # Otherwise assume it's an interable and we want to get multiple 

3104 # values. We can't just iterate blindly because strings are 

3105 # iterable. 

3106 if isinstance(get, (bytes, str)): 

3107 pieces.extend([b"GET", get]) 

3108 else: 

3109 for g in get: 

3110 pieces.extend([b"GET", g]) 

3111 if desc: 

3112 pieces.append(b"DESC") 

3113 if alpha: 

3114 pieces.append(b"ALPHA") 

3115 if store is not None: 

3116 pieces.extend([b"STORE", store]) 

3117 if groups: 

3118 if not get or isinstance(get, (bytes, str)) or len(get) < 2: 

3119 raise DataError( 

3120 'when using "groups" the "get" argument ' 

3121 "must be specified and contain at least " 

3122 "two keys" 

3123 ) 

3124 

3125 options = {"groups": len(get) if groups else None} 

3126 options["keys"] = [name] 

3127 return self.execute_command("SORT", *pieces, **options) 

3128 

3129 def sort_ro( 

3130 self, 

3131 key: str, 

3132 start: Optional[int] = None, 

3133 num: Optional[int] = None, 

3134 by: Optional[str] = None, 

3135 get: Optional[List[str]] = None, 

3136 desc: bool = False, 

3137 alpha: bool = False, 

3138 ) -> list: 

3139 """ 

3140 Returns the elements contained in the list, set or sorted set at key. 

3141 (read-only variant of the SORT command) 

3142 

3143 ``start`` and ``num`` allow for paging through the sorted data 

3144 

3145 ``by`` allows using an external key to weight and sort the items. 

3146 Use an "*" to indicate where in the key the item value is located 

3147 

3148 ``get`` allows for returning items from external keys rather than the 

3149 sorted data itself. Use an "*" to indicate where in the key 

3150 the item value is located 

3151 

3152 ``desc`` allows for reversing the sort 

3153 

3154 ``alpha`` allows for sorting lexicographically rather than numerically 

3155 

3156 For more information, see https://redis.io/commands/sort_ro 

3157 """ 

3158 return self.sort( 

3159 key, start=start, num=num, by=by, get=get, desc=desc, alpha=alpha 

3160 ) 

3161 

3162 

3163AsyncListCommands = ListCommands 

3164 

3165 

3166class ScanCommands(CommandsProtocol): 

3167 """ 

3168 Redis SCAN commands. 

3169 see: https://redis.io/commands/scan 

3170 """ 

3171 

3172 def scan( 

3173 self, 

3174 cursor: int = 0, 

3175 match: Union[PatternT, None] = None, 

3176 count: Optional[int] = None, 

3177 _type: Optional[str] = None, 

3178 **kwargs, 

3179 ) -> ResponseT: 

3180 """ 

3181 Incrementally return lists of key names. Also return a cursor 

3182 indicating the scan position. 

3183 

3184 ``match`` allows for filtering the keys by pattern 

3185 

3186 ``count`` provides a hint to Redis about the number of keys to 

3187 return per batch. 

3188 

3189 ``_type`` filters the returned values by a particular Redis type. 

3190 Stock Redis instances allow for the following types: 

3191 HASH, LIST, SET, STREAM, STRING, ZSET 

3192 Additionally, Redis modules can expose other types as well. 

3193 

3194 For more information, see https://redis.io/commands/scan 

3195 """ 

3196 pieces: list[EncodableT] = [cursor] 

3197 if match is not None: 

3198 pieces.extend([b"MATCH", match]) 

3199 if count is not None: 

3200 pieces.extend([b"COUNT", count]) 

3201 if _type is not None: 

3202 pieces.extend([b"TYPE", _type]) 

3203 return self.execute_command("SCAN", *pieces, **kwargs) 

3204 

3205 def scan_iter( 

3206 self, 

3207 match: Union[PatternT, None] = None, 

3208 count: Optional[int] = None, 

3209 _type: Optional[str] = None, 

3210 **kwargs, 

3211 ) -> Iterator: 

3212 """ 

3213 Make an iterator using the SCAN command so that the client doesn't 

3214 need to remember the cursor position. 

3215 

3216 ``match`` allows for filtering the keys by pattern 

3217 

3218 ``count`` provides a hint to Redis about the number of keys to 

3219 return per batch. 

3220 

3221 ``_type`` filters the returned values by a particular Redis type. 

3222 Stock Redis instances allow for the following types: 

3223 HASH, LIST, SET, STREAM, STRING, ZSET 

3224 Additionally, Redis modules can expose other types as well. 

3225 """ 

3226 cursor = "0" 

3227 while cursor != 0: 

3228 cursor, data = self.scan( 

3229 cursor=cursor, match=match, count=count, _type=_type, **kwargs 

3230 ) 

3231 yield from data 

3232 

3233 def sscan( 

3234 self, 

3235 name: KeyT, 

3236 cursor: int = 0, 

3237 match: Union[PatternT, None] = None, 

3238 count: Optional[int] = None, 

3239 ) -> ResponseT: 

3240 """ 

3241 Incrementally return lists of elements in a set. Also return a cursor 

3242 indicating the scan position. 

3243 

3244 ``match`` allows for filtering the keys by pattern 

3245 

3246 ``count`` allows for hint the minimum number of returns 

3247 

3248 For more information, see https://redis.io/commands/sscan 

3249 """ 

3250 pieces: list[EncodableT] = [name, cursor] 

3251 if match is not None: 

3252 pieces.extend([b"MATCH", match]) 

3253 if count is not None: 

3254 pieces.extend([b"COUNT", count]) 

3255 return self.execute_command("SSCAN", *pieces) 

3256 

3257 def sscan_iter( 

3258 self, 

3259 name: KeyT, 

3260 match: Union[PatternT, None] = None, 

3261 count: Optional[int] = None, 

3262 ) -> Iterator: 

3263 """ 

3264 Make an iterator using the SSCAN command so that the client doesn't 

3265 need to remember the cursor position. 

3266 

3267 ``match`` allows for filtering the keys by pattern 

3268 

3269 ``count`` allows for hint the minimum number of returns 

3270 """ 

3271 cursor = "0" 

3272 while cursor != 0: 

3273 cursor, data = self.sscan(name, cursor=cursor, match=match, count=count) 

3274 yield from data 

3275 

3276 def hscan( 

3277 self, 

3278 name: KeyT, 

3279 cursor: int = 0, 

3280 match: Union[PatternT, None] = None, 

3281 count: Optional[int] = None, 

3282 no_values: Union[bool, None] = None, 

3283 ) -> ResponseT: 

3284 """ 

3285 Incrementally return key/value slices in a hash. Also return a cursor 

3286 indicating the scan position. 

3287 

3288 ``match`` allows for filtering the keys by pattern 

3289 

3290 ``count`` allows for hint the minimum number of returns 

3291 

3292 ``no_values`` indicates to return only the keys, without values. 

3293 

3294 For more information, see https://redis.io/commands/hscan 

3295 """ 

3296 pieces: list[EncodableT] = [name, cursor] 

3297 if match is not None: 

3298 pieces.extend([b"MATCH", match]) 

3299 if count is not None: 

3300 pieces.extend([b"COUNT", count]) 

3301 if no_values is not None: 

3302 pieces.extend([b"NOVALUES"]) 

3303 return self.execute_command("HSCAN", *pieces, no_values=no_values) 

3304 

3305 def hscan_iter( 

3306 self, 

3307 name: str, 

3308 match: Union[PatternT, None] = None, 

3309 count: Optional[int] = None, 

3310 no_values: Union[bool, None] = None, 

3311 ) -> Iterator: 

3312 """ 

3313 Make an iterator using the HSCAN command so that the client doesn't 

3314 need to remember the cursor position. 

3315 

3316 ``match`` allows for filtering the keys by pattern 

3317 

3318 ``count`` allows for hint the minimum number of returns 

3319 

3320 ``no_values`` indicates to return only the keys, without values 

3321 """ 

3322 cursor = "0" 

3323 while cursor != 0: 

3324 cursor, data = self.hscan( 

3325 name, cursor=cursor, match=match, count=count, no_values=no_values 

3326 ) 

3327 if no_values: 

3328 yield from data 

3329 else: 

3330 yield from data.items() 

3331 

3332 def zscan( 

3333 self, 

3334 name: KeyT, 

3335 cursor: int = 0, 

3336 match: Union[PatternT, None] = None, 

3337 count: Optional[int] = None, 

3338 score_cast_func: Union[type, Callable] = float, 

3339 ) -> ResponseT: 

3340 """ 

3341 Incrementally return lists of elements in a sorted set. Also return a 

3342 cursor indicating the scan position. 

3343 

3344 ``match`` allows for filtering the keys by pattern 

3345 

3346 ``count`` allows for hint the minimum number of returns 

3347 

3348 ``score_cast_func`` a callable used to cast the score return value 

3349 

3350 For more information, see https://redis.io/commands/zscan 

3351 """ 

3352 pieces = [name, cursor] 

3353 if match is not None: 

3354 pieces.extend([b"MATCH", match]) 

3355 if count is not None: 

3356 pieces.extend([b"COUNT", count]) 

3357 options = {"score_cast_func": score_cast_func} 

3358 return self.execute_command("ZSCAN", *pieces, **options) 

3359 

3360 def zscan_iter( 

3361 self, 

3362 name: KeyT, 

3363 match: Union[PatternT, None] = None, 

3364 count: Optional[int] = None, 

3365 score_cast_func: Union[type, Callable] = float, 

3366 ) -> Iterator: 

3367 """ 

3368 Make an iterator using the ZSCAN command so that the client doesn't 

3369 need to remember the cursor position. 

3370 

3371 ``match`` allows for filtering the keys by pattern 

3372 

3373 ``count`` allows for hint the minimum number of returns 

3374 

3375 ``score_cast_func`` a callable used to cast the score return value 

3376 """ 

3377 cursor = "0" 

3378 while cursor != 0: 

3379 cursor, data = self.zscan( 

3380 name, 

3381 cursor=cursor, 

3382 match=match, 

3383 count=count, 

3384 score_cast_func=score_cast_func, 

3385 ) 

3386 yield from data 

3387 

3388 

3389class AsyncScanCommands(ScanCommands): 

3390 async def scan_iter( 

3391 self, 

3392 match: Union[PatternT, None] = None, 

3393 count: Optional[int] = None, 

3394 _type: Optional[str] = None, 

3395 **kwargs, 

3396 ) -> AsyncIterator: 

3397 """ 

3398 Make an iterator using the SCAN command so that the client doesn't 

3399 need to remember the cursor position. 

3400 

3401 ``match`` allows for filtering the keys by pattern 

3402 

3403 ``count`` provides a hint to Redis about the number of keys to 

3404 return per batch. 

3405 

3406 ``_type`` filters the returned values by a particular Redis type. 

3407 Stock Redis instances allow for the following types: 

3408 HASH, LIST, SET, STREAM, STRING, ZSET 

3409 Additionally, Redis modules can expose other types as well. 

3410 """ 

3411 cursor = "0" 

3412 while cursor != 0: 

3413 cursor, data = await self.scan( 

3414 cursor=cursor, match=match, count=count, _type=_type, **kwargs 

3415 ) 

3416 for d in data: 

3417 yield d 

3418 

3419 async def sscan_iter( 

3420 self, 

3421 name: KeyT, 

3422 match: Union[PatternT, None] = None, 

3423 count: Optional[int] = None, 

3424 ) -> AsyncIterator: 

3425 """ 

3426 Make an iterator using the SSCAN command so that the client doesn't 

3427 need to remember the cursor position. 

3428 

3429 ``match`` allows for filtering the keys by pattern 

3430 

3431 ``count`` allows for hint the minimum number of returns 

3432 """ 

3433 cursor = "0" 

3434 while cursor != 0: 

3435 cursor, data = await self.sscan( 

3436 name, cursor=cursor, match=match, count=count 

3437 ) 

3438 for d in data: 

3439 yield d 

3440 

3441 async def hscan_iter( 

3442 self, 

3443 name: str, 

3444 match: Union[PatternT, None] = None, 

3445 count: Optional[int] = None, 

3446 no_values: Union[bool, None] = None, 

3447 ) -> AsyncIterator: 

3448 """ 

3449 Make an iterator using the HSCAN command so that the client doesn't 

3450 need to remember the cursor position. 

3451 

3452 ``match`` allows for filtering the keys by pattern 

3453 

3454 ``count`` allows for hint the minimum number of returns 

3455 

3456 ``no_values`` indicates to return only the keys, without values 

3457 """ 

3458 cursor = "0" 

3459 while cursor != 0: 

3460 cursor, data = await self.hscan( 

3461 name, cursor=cursor, match=match, count=count, no_values=no_values 

3462 ) 

3463 if no_values: 

3464 for it in data: 

3465 yield it 

3466 else: 

3467 for it in data.items(): 

3468 yield it 

3469 

3470 async def zscan_iter( 

3471 self, 

3472 name: KeyT, 

3473 match: Union[PatternT, None] = None, 

3474 count: Optional[int] = None, 

3475 score_cast_func: Union[type, Callable] = float, 

3476 ) -> AsyncIterator: 

3477 """ 

3478 Make an iterator using the ZSCAN command so that the client doesn't 

3479 need to remember the cursor position. 

3480 

3481 ``match`` allows for filtering the keys by pattern 

3482 

3483 ``count`` allows for hint the minimum number of returns 

3484 

3485 ``score_cast_func`` a callable used to cast the score return value 

3486 """ 

3487 cursor = "0" 

3488 while cursor != 0: 

3489 cursor, data = await self.zscan( 

3490 name, 

3491 cursor=cursor, 

3492 match=match, 

3493 count=count, 

3494 score_cast_func=score_cast_func, 

3495 ) 

3496 for d in data: 

3497 yield d 

3498 

3499 

3500class SetCommands(CommandsProtocol): 

3501 """ 

3502 Redis commands for Set data type. 

3503 see: https://redis.io/topics/data-types#sets 

3504 """ 

3505 

3506 def sadd(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]: 

3507 """ 

3508 Add ``value(s)`` to set ``name`` 

3509 

3510 For more information, see https://redis.io/commands/sadd 

3511 """ 

3512 return self.execute_command("SADD", name, *values) 

3513 

3514 def scard(self, name: KeyT) -> Union[Awaitable[int], int]: 

3515 """ 

3516 Return the number of elements in set ``name`` 

3517 

3518 For more information, see https://redis.io/commands/scard 

3519 """ 

3520 return self.execute_command("SCARD", name, keys=[name]) 

3521 

3522 def sdiff(self, keys: List, *args: List) -> Union[Awaitable[list], list]: 

3523 """ 

3524 Return the difference of sets specified by ``keys`` 

3525 

3526 For more information, see https://redis.io/commands/sdiff 

3527 """ 

3528 args = list_or_args(keys, args) 

3529 return self.execute_command("SDIFF", *args, keys=args) 

3530 

3531 def sdiffstore( 

3532 self, dest: str, keys: List, *args: List 

3533 ) -> Union[Awaitable[int], int]: 

3534 """ 

3535 Store the difference of sets specified by ``keys`` into a new 

3536 set named ``dest``. Returns the number of keys in the new set. 

3537 

3538 For more information, see https://redis.io/commands/sdiffstore 

3539 """ 

3540 args = list_or_args(keys, args) 

3541 return self.execute_command("SDIFFSTORE", dest, *args) 

3542 

3543 def sinter(self, keys: List, *args: List) -> Union[Awaitable[list], list]: 

3544 """ 

3545 Return the intersection of sets specified by ``keys`` 

3546 

3547 For more information, see https://redis.io/commands/sinter 

3548 """ 

3549 args = list_or_args(keys, args) 

3550 return self.execute_command("SINTER", *args, keys=args) 

3551 

3552 def sintercard( 

3553 self, numkeys: int, keys: List[KeyT], limit: int = 0 

3554 ) -> Union[Awaitable[int], int]: 

3555 """ 

3556 Return the cardinality of the intersect of multiple sets specified by ``keys``. 

3557 

3558 When LIMIT provided (defaults to 0 and means unlimited), if the intersection 

3559 cardinality reaches limit partway through the computation, the algorithm will 

3560 exit and yield limit as the cardinality 

3561 

3562 For more information, see https://redis.io/commands/sintercard 

3563 """ 

3564 args = [numkeys, *keys, "LIMIT", limit] 

3565 return self.execute_command("SINTERCARD", *args, keys=keys) 

3566 

3567 def sinterstore( 

3568 self, dest: KeyT, keys: List, *args: List 

3569 ) -> Union[Awaitable[int], int]: 

3570 """ 

3571 Store the intersection of sets specified by ``keys`` into a new 

3572 set named ``dest``. Returns the number of keys in the new set. 

3573 

3574 For more information, see https://redis.io/commands/sinterstore 

3575 """ 

3576 args = list_or_args(keys, args) 

3577 return self.execute_command("SINTERSTORE", dest, *args) 

3578 

3579 def sismember( 

3580 self, name: KeyT, value: str 

3581 ) -> Union[Awaitable[Union[Literal[0], Literal[1]]], Union[Literal[0], Literal[1]]]: 

3582 """ 

3583 Return whether ``value`` is a member of set ``name``: 

3584 - 1 if the value is a member of the set. 

3585 - 0 if the value is not a member of the set or if key does not exist. 

3586 

3587 For more information, see https://redis.io/commands/sismember 

3588 """ 

3589 return self.execute_command("SISMEMBER", name, value, keys=[name]) 

3590 

3591 def smembers(self, name: KeyT) -> Union[Awaitable[Set], Set]: 

3592 """ 

3593 Return all members of the set ``name`` 

3594 

3595 For more information, see https://redis.io/commands/smembers 

3596 """ 

3597 return self.execute_command("SMEMBERS", name, keys=[name]) 

3598 

3599 def smismember( 

3600 self, name: KeyT, values: List, *args: List 

3601 ) -> Union[ 

3602 Awaitable[List[Union[Literal[0], Literal[1]]]], 

3603 List[Union[Literal[0], Literal[1]]], 

3604 ]: 

3605 """ 

3606 Return whether each value in ``values`` is a member of the set ``name`` 

3607 as a list of ``int`` in the order of ``values``: 

3608 - 1 if the value is a member of the set. 

3609 - 0 if the value is not a member of the set or if key does not exist. 

3610 

3611 For more information, see https://redis.io/commands/smismember 

3612 """ 

3613 args = list_or_args(values, args) 

3614 return self.execute_command("SMISMEMBER", name, *args, keys=[name]) 

3615 

3616 def smove(self, src: KeyT, dst: KeyT, value: str) -> Union[Awaitable[bool], bool]: 

3617 """ 

3618 Move ``value`` from set ``src`` to set ``dst`` atomically 

3619 

3620 For more information, see https://redis.io/commands/smove 

3621 """ 

3622 return self.execute_command("SMOVE", src, dst, value) 

3623 

3624 def spop(self, name: KeyT, count: Optional[int] = None) -> Union[str, List, None]: 

3625 """ 

3626 Remove and return a random member of set ``name`` 

3627 

3628 For more information, see https://redis.io/commands/spop 

3629 """ 

3630 args = (count is not None) and [count] or [] 

3631 return self.execute_command("SPOP", name, *args) 

3632 

3633 def srandmember( 

3634 self, name: KeyT, number: Optional[int] = None 

3635 ) -> Union[str, List, None]: 

3636 """ 

3637 If ``number`` is None, returns a random member of set ``name``. 

3638 

3639 If ``number`` is supplied, returns a list of ``number`` random 

3640 members of set ``name``. Note this is only available when running 

3641 Redis 2.6+. 

3642 

3643 For more information, see https://redis.io/commands/srandmember 

3644 """ 

3645 args = (number is not None) and [number] or [] 

3646 return self.execute_command("SRANDMEMBER", name, *args) 

3647 

3648 def srem(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]: 

3649 """ 

3650 Remove ``values`` from set ``name`` 

3651 

3652 For more information, see https://redis.io/commands/srem 

3653 """ 

3654 return self.execute_command("SREM", name, *values) 

3655 

3656 def sunion(self, keys: List, *args: List) -> Union[Awaitable[List], List]: 

3657 """ 

3658 Return the union of sets specified by ``keys`` 

3659 

3660 For more information, see https://redis.io/commands/sunion 

3661 """ 

3662 args = list_or_args(keys, args) 

3663 return self.execute_command("SUNION", *args, keys=args) 

3664 

3665 def sunionstore( 

3666 self, dest: KeyT, keys: List, *args: List 

3667 ) -> Union[Awaitable[int], int]: 

3668 """ 

3669 Store the union of sets specified by ``keys`` into a new 

3670 set named ``dest``. Returns the number of keys in the new set. 

3671 

3672 For more information, see https://redis.io/commands/sunionstore 

3673 """ 

3674 args = list_or_args(keys, args) 

3675 return self.execute_command("SUNIONSTORE", dest, *args) 

3676 

3677 

3678AsyncSetCommands = SetCommands 

3679 

3680 

3681class StreamCommands(CommandsProtocol): 

3682 """ 

3683 Redis commands for Stream data type. 

3684 see: https://redis.io/topics/streams-intro 

3685 """ 

3686 

3687 def xack(self, name: KeyT, groupname: GroupT, *ids: StreamIdT) -> ResponseT: 

3688 """ 

3689 Acknowledges the successful processing of one or more messages. 

3690 

3691 Args: 

3692 name: name of the stream. 

3693 groupname: name of the consumer group. 

3694 *ids: message ids to acknowledge. 

3695 

3696 For more information, see https://redis.io/commands/xack 

3697 """ 

3698 return self.execute_command("XACK", name, groupname, *ids) 

3699 

3700 def xackdel( 

3701 self, 

3702 name: KeyT, 

3703 groupname: GroupT, 

3704 *ids: StreamIdT, 

3705 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF", 

3706 ) -> ResponseT: 

3707 """ 

3708 Combines the functionality of XACK and XDEL. Acknowledges the specified 

3709 message IDs in the given consumer group and simultaneously attempts to 

3710 delete the corresponding entries from the stream. 

3711 """ 

3712 if not ids: 

3713 raise DataError("XACKDEL requires at least one message ID") 

3714 

3715 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

3716 raise DataError("XACKDEL ref_policy must be one of: KEEPREF, DELREF, ACKED") 

3717 

3718 pieces = [name, groupname, ref_policy, "IDS", len(ids)] 

3719 pieces.extend(ids) 

3720 return self.execute_command("XACKDEL", *pieces) 

3721 

3722 def xadd( 

3723 self, 

3724 name: KeyT, 

3725 fields: Dict[FieldT, EncodableT], 

3726 id: StreamIdT = "*", 

3727 maxlen: Optional[int] = None, 

3728 approximate: bool = True, 

3729 nomkstream: bool = False, 

3730 minid: Union[StreamIdT, None] = None, 

3731 limit: Optional[int] = None, 

3732 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None, 

3733 ) -> ResponseT: 

3734 """ 

3735 Add to a stream. 

3736 name: name of the stream 

3737 fields: dict of field/value pairs to insert into the stream 

3738 id: Location to insert this record. By default it is appended. 

3739 maxlen: truncate old stream members beyond this size. 

3740 Can't be specified with minid. 

3741 approximate: actual stream length may be slightly more than maxlen 

3742 nomkstream: When set to true, do not make a stream 

3743 minid: the minimum id in the stream to query. 

3744 Can't be specified with maxlen. 

3745 limit: specifies the maximum number of entries to retrieve 

3746 ref_policy: optional reference policy for consumer groups when trimming: 

3747 - KEEPREF (default): When trimming, preserves references in consumer groups' PEL 

3748 - DELREF: When trimming, removes all references from consumer groups' PEL 

3749 - ACKED: When trimming, only removes entries acknowledged by all consumer groups 

3750 

3751 For more information, see https://redis.io/commands/xadd 

3752 """ 

3753 pieces: list[EncodableT] = [] 

3754 if maxlen is not None and minid is not None: 

3755 raise DataError("Only one of ```maxlen``` or ```minid``` may be specified") 

3756 

3757 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

3758 raise DataError("XADD ref_policy must be one of: KEEPREF, DELREF, ACKED") 

3759 

3760 if maxlen is not None: 

3761 if not isinstance(maxlen, int) or maxlen < 0: 

3762 raise DataError("XADD maxlen must be non-negative integer") 

3763 pieces.append(b"MAXLEN") 

3764 if approximate: 

3765 pieces.append(b"~") 

3766 pieces.append(str(maxlen)) 

3767 if minid is not None: 

3768 pieces.append(b"MINID") 

3769 if approximate: 

3770 pieces.append(b"~") 

3771 pieces.append(minid) 

3772 if limit is not None: 

3773 pieces.extend([b"LIMIT", limit]) 

3774 if nomkstream: 

3775 pieces.append(b"NOMKSTREAM") 

3776 if ref_policy is not None: 

3777 pieces.append(ref_policy) 

3778 pieces.append(id) 

3779 if not isinstance(fields, dict) or len(fields) == 0: 

3780 raise DataError("XADD fields must be a non-empty dict") 

3781 for pair in fields.items(): 

3782 pieces.extend(pair) 

3783 return self.execute_command("XADD", name, *pieces) 

3784 

3785 def xautoclaim( 

3786 self, 

3787 name: KeyT, 

3788 groupname: GroupT, 

3789 consumername: ConsumerT, 

3790 min_idle_time: int, 

3791 start_id: StreamIdT = "0-0", 

3792 count: Optional[int] = None, 

3793 justid: bool = False, 

3794 ) -> ResponseT: 

3795 """ 

3796 Transfers ownership of pending stream entries that match the specified 

3797 criteria. Conceptually, equivalent to calling XPENDING and then XCLAIM, 

3798 but provides a more straightforward way to deal with message delivery 

3799 failures via SCAN-like semantics. 

3800 name: name of the stream. 

3801 groupname: name of the consumer group. 

3802 consumername: name of a consumer that claims the message. 

3803 min_idle_time: filter messages that were idle less than this amount of 

3804 milliseconds. 

3805 start_id: filter messages with equal or greater ID. 

3806 count: optional integer, upper limit of the number of entries that the 

3807 command attempts to claim. Set to 100 by default. 

3808 justid: optional boolean, false by default. Return just an array of IDs 

3809 of messages successfully claimed, without returning the actual message 

3810 

3811 For more information, see https://redis.io/commands/xautoclaim 

3812 """ 

3813 try: 

3814 if int(min_idle_time) < 0: 

3815 raise DataError( 

3816 "XAUTOCLAIM min_idle_time must be a nonnegative integer" 

3817 ) 

3818 except TypeError: 

3819 pass 

3820 

3821 kwargs = {} 

3822 pieces = [name, groupname, consumername, min_idle_time, start_id] 

3823 

3824 try: 

3825 if int(count) < 0: 

3826 raise DataError("XPENDING count must be a integer >= 0") 

3827 pieces.extend([b"COUNT", count]) 

3828 except TypeError: 

3829 pass 

3830 if justid: 

3831 pieces.append(b"JUSTID") 

3832 kwargs["parse_justid"] = True 

3833 

3834 return self.execute_command("XAUTOCLAIM", *pieces, **kwargs) 

3835 

3836 def xclaim( 

3837 self, 

3838 name: KeyT, 

3839 groupname: GroupT, 

3840 consumername: ConsumerT, 

3841 min_idle_time: int, 

3842 message_ids: Union[List[StreamIdT], Tuple[StreamIdT]], 

3843 idle: Optional[int] = None, 

3844 time: Optional[int] = None, 

3845 retrycount: Optional[int] = None, 

3846 force: bool = False, 

3847 justid: bool = False, 

3848 ) -> ResponseT: 

3849 """ 

3850 Changes the ownership of a pending message. 

3851 

3852 name: name of the stream. 

3853 

3854 groupname: name of the consumer group. 

3855 

3856 consumername: name of a consumer that claims the message. 

3857 

3858 min_idle_time: filter messages that were idle less than this amount of 

3859 milliseconds 

3860 

3861 message_ids: non-empty list or tuple of message IDs to claim 

3862 

3863 idle: optional. Set the idle time (last time it was delivered) of the 

3864 message in ms 

3865 

3866 time: optional integer. This is the same as idle but instead of a 

3867 relative amount of milliseconds, it sets the idle time to a specific 

3868 Unix time (in milliseconds). 

3869 

3870 retrycount: optional integer. set the retry counter to the specified 

3871 value. This counter is incremented every time a message is delivered 

3872 again. 

3873 

3874 force: optional boolean, false by default. Creates the pending message 

3875 entry in the PEL even if certain specified IDs are not already in the 

3876 PEL assigned to a different client. 

3877 

3878 justid: optional boolean, false by default. Return just an array of IDs 

3879 of messages successfully claimed, without returning the actual message 

3880 

3881 For more information, see https://redis.io/commands/xclaim 

3882 """ 

3883 if not isinstance(min_idle_time, int) or min_idle_time < 0: 

3884 raise DataError("XCLAIM min_idle_time must be a non negative integer") 

3885 if not isinstance(message_ids, (list, tuple)) or not message_ids: 

3886 raise DataError( 

3887 "XCLAIM message_ids must be a non empty list or " 

3888 "tuple of message IDs to claim" 

3889 ) 

3890 

3891 kwargs = {} 

3892 pieces: list[EncodableT] = [name, groupname, consumername, str(min_idle_time)] 

3893 pieces.extend(list(message_ids)) 

3894 

3895 if idle is not None: 

3896 if not isinstance(idle, int): 

3897 raise DataError("XCLAIM idle must be an integer") 

3898 pieces.extend((b"IDLE", str(idle))) 

3899 if time is not None: 

3900 if not isinstance(time, int): 

3901 raise DataError("XCLAIM time must be an integer") 

3902 pieces.extend((b"TIME", str(time))) 

3903 if retrycount is not None: 

3904 if not isinstance(retrycount, int): 

3905 raise DataError("XCLAIM retrycount must be an integer") 

3906 pieces.extend((b"RETRYCOUNT", str(retrycount))) 

3907 

3908 if force: 

3909 if not isinstance(force, bool): 

3910 raise DataError("XCLAIM force must be a boolean") 

3911 pieces.append(b"FORCE") 

3912 if justid: 

3913 if not isinstance(justid, bool): 

3914 raise DataError("XCLAIM justid must be a boolean") 

3915 pieces.append(b"JUSTID") 

3916 kwargs["parse_justid"] = True 

3917 return self.execute_command("XCLAIM", *pieces, **kwargs) 

3918 

3919 def xdel(self, name: KeyT, *ids: StreamIdT) -> ResponseT: 

3920 """ 

3921 Deletes one or more messages from a stream. 

3922 

3923 Args: 

3924 name: name of the stream. 

3925 *ids: message ids to delete. 

3926 

3927 For more information, see https://redis.io/commands/xdel 

3928 """ 

3929 return self.execute_command("XDEL", name, *ids) 

3930 

3931 def xdelex( 

3932 self, 

3933 name: KeyT, 

3934 *ids: StreamIdT, 

3935 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF", 

3936 ) -> ResponseT: 

3937 """ 

3938 Extended version of XDEL that provides more control over how message entries 

3939 are deleted concerning consumer groups. 

3940 """ 

3941 if not ids: 

3942 raise DataError("XDELEX requires at least one message ID") 

3943 

3944 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

3945 raise DataError("XDELEX ref_policy must be one of: KEEPREF, DELREF, ACKED") 

3946 

3947 pieces = [name, ref_policy, "IDS", len(ids)] 

3948 pieces.extend(ids) 

3949 return self.execute_command("XDELEX", *pieces) 

3950 

3951 def xgroup_create( 

3952 self, 

3953 name: KeyT, 

3954 groupname: GroupT, 

3955 id: StreamIdT = "$", 

3956 mkstream: bool = False, 

3957 entries_read: Optional[int] = None, 

3958 ) -> ResponseT: 

3959 """ 

3960 Create a new consumer group associated with a stream. 

3961 name: name of the stream. 

3962 groupname: name of the consumer group. 

3963 id: ID of the last item in the stream to consider already delivered. 

3964 

3965 For more information, see https://redis.io/commands/xgroup-create 

3966 """ 

3967 pieces: list[EncodableT] = ["XGROUP CREATE", name, groupname, id] 

3968 if mkstream: 

3969 pieces.append(b"MKSTREAM") 

3970 if entries_read is not None: 

3971 pieces.extend(["ENTRIESREAD", entries_read]) 

3972 

3973 return self.execute_command(*pieces) 

3974 

3975 def xgroup_delconsumer( 

3976 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

3977 ) -> ResponseT: 

3978 """ 

3979 Remove a specific consumer from a consumer group. 

3980 Returns the number of pending messages that the consumer had before it 

3981 was deleted. 

3982 name: name of the stream. 

3983 groupname: name of the consumer group. 

3984 consumername: name of consumer to delete 

3985 

3986 For more information, see https://redis.io/commands/xgroup-delconsumer 

3987 """ 

3988 return self.execute_command("XGROUP DELCONSUMER", name, groupname, consumername) 

3989 

3990 def xgroup_destroy(self, name: KeyT, groupname: GroupT) -> ResponseT: 

3991 """ 

3992 Destroy a consumer group. 

3993 name: name of the stream. 

3994 groupname: name of the consumer group. 

3995 

3996 For more information, see https://redis.io/commands/xgroup-destroy 

3997 """ 

3998 return self.execute_command("XGROUP DESTROY", name, groupname) 

3999 

4000 def xgroup_createconsumer( 

4001 self, name: KeyT, groupname: GroupT, consumername: ConsumerT 

4002 ) -> ResponseT: 

4003 """ 

4004 Consumers in a consumer group are auto-created every time a new 

4005 consumer name is mentioned by some command. 

4006 They can be explicitly created by using this command. 

4007 name: name of the stream. 

4008 groupname: name of the consumer group. 

4009 consumername: name of consumer to create. 

4010 

4011 See: https://redis.io/commands/xgroup-createconsumer 

4012 """ 

4013 return self.execute_command( 

4014 "XGROUP CREATECONSUMER", name, groupname, consumername 

4015 ) 

4016 

4017 def xgroup_setid( 

4018 self, 

4019 name: KeyT, 

4020 groupname: GroupT, 

4021 id: StreamIdT, 

4022 entries_read: Optional[int] = None, 

4023 ) -> ResponseT: 

4024 """ 

4025 Set the consumer group last delivered ID to something else. 

4026 name: name of the stream. 

4027 groupname: name of the consumer group. 

4028 id: ID of the last item in the stream to consider already delivered. 

4029 

4030 For more information, see https://redis.io/commands/xgroup-setid 

4031 """ 

4032 pieces = [name, groupname, id] 

4033 if entries_read is not None: 

4034 pieces.extend(["ENTRIESREAD", entries_read]) 

4035 return self.execute_command("XGROUP SETID", *pieces) 

4036 

4037 def xinfo_consumers(self, name: KeyT, groupname: GroupT) -> ResponseT: 

4038 """ 

4039 Returns general information about the consumers in the group. 

4040 name: name of the stream. 

4041 groupname: name of the consumer group. 

4042 

4043 For more information, see https://redis.io/commands/xinfo-consumers 

4044 """ 

4045 return self.execute_command("XINFO CONSUMERS", name, groupname) 

4046 

4047 def xinfo_groups(self, name: KeyT) -> ResponseT: 

4048 """ 

4049 Returns general information about the consumer groups of the stream. 

4050 name: name of the stream. 

4051 

4052 For more information, see https://redis.io/commands/xinfo-groups 

4053 """ 

4054 return self.execute_command("XINFO GROUPS", name) 

4055 

4056 def xinfo_stream(self, name: KeyT, full: bool = False) -> ResponseT: 

4057 """ 

4058 Returns general information about the stream. 

4059 name: name of the stream. 

4060 full: optional boolean, false by default. Return full summary 

4061 

4062 For more information, see https://redis.io/commands/xinfo-stream 

4063 """ 

4064 pieces = [name] 

4065 options = {} 

4066 if full: 

4067 pieces.append(b"FULL") 

4068 options = {"full": full} 

4069 return self.execute_command("XINFO STREAM", *pieces, **options) 

4070 

4071 def xlen(self, name: KeyT) -> ResponseT: 

4072 """ 

4073 Returns the number of elements in a given stream. 

4074 

4075 For more information, see https://redis.io/commands/xlen 

4076 """ 

4077 return self.execute_command("XLEN", name, keys=[name]) 

4078 

4079 def xpending(self, name: KeyT, groupname: GroupT) -> ResponseT: 

4080 """ 

4081 Returns information about pending messages of a group. 

4082 name: name of the stream. 

4083 groupname: name of the consumer group. 

4084 

4085 For more information, see https://redis.io/commands/xpending 

4086 """ 

4087 return self.execute_command("XPENDING", name, groupname, keys=[name]) 

4088 

4089 def xpending_range( 

4090 self, 

4091 name: KeyT, 

4092 groupname: GroupT, 

4093 min: StreamIdT, 

4094 max: StreamIdT, 

4095 count: int, 

4096 consumername: Union[ConsumerT, None] = None, 

4097 idle: Optional[int] = None, 

4098 ) -> ResponseT: 

4099 """ 

4100 Returns information about pending messages, in a range. 

4101 

4102 name: name of the stream. 

4103 groupname: name of the consumer group. 

4104 idle: available from version 6.2. filter entries by their 

4105 idle-time, given in milliseconds (optional). 

4106 min: minimum stream ID. 

4107 max: maximum stream ID. 

4108 count: number of messages to return 

4109 consumername: name of a consumer to filter by (optional). 

4110 """ 

4111 if {min, max, count} == {None}: 

4112 if idle is not None or consumername is not None: 

4113 raise DataError( 

4114 "if XPENDING is provided with idle time" 

4115 " or consumername, it must be provided" 

4116 " with min, max and count parameters" 

4117 ) 

4118 return self.xpending(name, groupname) 

4119 

4120 pieces = [name, groupname] 

4121 if min is None or max is None or count is None: 

4122 raise DataError( 

4123 "XPENDING must be provided with min, max " 

4124 "and count parameters, or none of them." 

4125 ) 

4126 # idle 

4127 try: 

4128 if int(idle) < 0: 

4129 raise DataError("XPENDING idle must be a integer >= 0") 

4130 pieces.extend(["IDLE", idle]) 

4131 except TypeError: 

4132 pass 

4133 # count 

4134 try: 

4135 if int(count) < 0: 

4136 raise DataError("XPENDING count must be a integer >= 0") 

4137 pieces.extend([min, max, count]) 

4138 except TypeError: 

4139 pass 

4140 # consumername 

4141 if consumername: 

4142 pieces.append(consumername) 

4143 

4144 return self.execute_command("XPENDING", *pieces, parse_detail=True) 

4145 

4146 def xrange( 

4147 self, 

4148 name: KeyT, 

4149 min: StreamIdT = "-", 

4150 max: StreamIdT = "+", 

4151 count: Optional[int] = None, 

4152 ) -> ResponseT: 

4153 """ 

4154 Read stream values within an interval. 

4155 

4156 name: name of the stream. 

4157 

4158 start: first stream ID. defaults to '-', 

4159 meaning the earliest available. 

4160 

4161 finish: last stream ID. defaults to '+', 

4162 meaning the latest available. 

4163 

4164 count: if set, only return this many items, beginning with the 

4165 earliest available. 

4166 

4167 For more information, see https://redis.io/commands/xrange 

4168 """ 

4169 pieces = [min, max] 

4170 if count is not None: 

4171 if not isinstance(count, int) or count < 1: 

4172 raise DataError("XRANGE count must be a positive integer") 

4173 pieces.append(b"COUNT") 

4174 pieces.append(str(count)) 

4175 

4176 return self.execute_command("XRANGE", name, *pieces, keys=[name]) 

4177 

4178 def xread( 

4179 self, 

4180 streams: Dict[KeyT, StreamIdT], 

4181 count: Optional[int] = None, 

4182 block: Optional[int] = None, 

4183 ) -> ResponseT: 

4184 """ 

4185 Block and monitor multiple streams for new data. 

4186 

4187 streams: a dict of stream names to stream IDs, where 

4188 IDs indicate the last ID already seen. 

4189 

4190 count: if set, only return this many items, beginning with the 

4191 earliest available. 

4192 

4193 block: number of milliseconds to wait, if nothing already present. 

4194 

4195 For more information, see https://redis.io/commands/xread 

4196 """ 

4197 pieces = [] 

4198 if block is not None: 

4199 if not isinstance(block, int) or block < 0: 

4200 raise DataError("XREAD block must be a non-negative integer") 

4201 pieces.append(b"BLOCK") 

4202 pieces.append(str(block)) 

4203 if count is not None: 

4204 if not isinstance(count, int) or count < 1: 

4205 raise DataError("XREAD count must be a positive integer") 

4206 pieces.append(b"COUNT") 

4207 pieces.append(str(count)) 

4208 if not isinstance(streams, dict) or len(streams) == 0: 

4209 raise DataError("XREAD streams must be a non empty dict") 

4210 pieces.append(b"STREAMS") 

4211 keys, values = zip(*streams.items()) 

4212 pieces.extend(keys) 

4213 pieces.extend(values) 

4214 return self.execute_command("XREAD", *pieces, keys=keys) 

4215 

4216 def xreadgroup( 

4217 self, 

4218 groupname: str, 

4219 consumername: str, 

4220 streams: Dict[KeyT, StreamIdT], 

4221 count: Optional[int] = None, 

4222 block: Optional[int] = None, 

4223 noack: bool = False, 

4224 claim_min_idle_time: Optional[int] = None, 

4225 ) -> ResponseT: 

4226 """ 

4227 Read from a stream via a consumer group. 

4228 

4229 groupname: name of the consumer group. 

4230 

4231 consumername: name of the requesting consumer. 

4232 

4233 streams: a dict of stream names to stream IDs, where 

4234 IDs indicate the last ID already seen. 

4235 

4236 count: if set, only return this many items, beginning with the 

4237 earliest available. 

4238 

4239 block: number of milliseconds to wait, if nothing already present. 

4240 noack: do not add messages to the PEL 

4241 

4242 claim_min_idle_time: accepts an integer type and represents a 

4243 time interval in milliseconds 

4244 

4245 For more information, see https://redis.io/commands/xreadgroup 

4246 """ 

4247 options = {} 

4248 pieces: list[EncodableT] = [b"GROUP", groupname, consumername] 

4249 if count is not None: 

4250 if not isinstance(count, int) or count < 1: 

4251 raise DataError("XREADGROUP count must be a positive integer") 

4252 pieces.append(b"COUNT") 

4253 pieces.append(str(count)) 

4254 if block is not None: 

4255 if not isinstance(block, int) or block < 0: 

4256 raise DataError("XREADGROUP block must be a non-negative integer") 

4257 pieces.append(b"BLOCK") 

4258 pieces.append(str(block)) 

4259 if noack: 

4260 pieces.append(b"NOACK") 

4261 if claim_min_idle_time is not None: 

4262 if not isinstance(claim_min_idle_time, int) or claim_min_idle_time < 0: 

4263 raise DataError( 

4264 "XREADGROUP claim_min_idle_time must be a non-negative integer" 

4265 ) 

4266 pieces.append(b"CLAIM") 

4267 pieces.append(claim_min_idle_time) 

4268 options["claim_min_idle_time"] = claim_min_idle_time 

4269 if not isinstance(streams, dict) or len(streams) == 0: 

4270 raise DataError("XREADGROUP streams must be a non empty dict") 

4271 pieces.append(b"STREAMS") 

4272 pieces.extend(streams.keys()) 

4273 pieces.extend(streams.values()) 

4274 return self.execute_command("XREADGROUP", *pieces, **options) 

4275 

4276 def xrevrange( 

4277 self, 

4278 name: KeyT, 

4279 max: StreamIdT = "+", 

4280 min: StreamIdT = "-", 

4281 count: Optional[int] = None, 

4282 ) -> ResponseT: 

4283 """ 

4284 Read stream values within an interval, in reverse order. 

4285 

4286 name: name of the stream 

4287 

4288 start: first stream ID. defaults to '+', 

4289 meaning the latest available. 

4290 

4291 finish: last stream ID. defaults to '-', 

4292 meaning the earliest available. 

4293 

4294 count: if set, only return this many items, beginning with the 

4295 latest available. 

4296 

4297 For more information, see https://redis.io/commands/xrevrange 

4298 """ 

4299 pieces: list[EncodableT] = [max, min] 

4300 if count is not None: 

4301 if not isinstance(count, int) or count < 1: 

4302 raise DataError("XREVRANGE count must be a positive integer") 

4303 pieces.append(b"COUNT") 

4304 pieces.append(str(count)) 

4305 

4306 return self.execute_command("XREVRANGE", name, *pieces, keys=[name]) 

4307 

4308 def xtrim( 

4309 self, 

4310 name: KeyT, 

4311 maxlen: Optional[int] = None, 

4312 approximate: bool = True, 

4313 minid: Union[StreamIdT, None] = None, 

4314 limit: Optional[int] = None, 

4315 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None, 

4316 ) -> ResponseT: 

4317 """ 

4318 Trims old messages from a stream. 

4319 name: name of the stream. 

4320 maxlen: truncate old stream messages beyond this size 

4321 Can't be specified with minid. 

4322 approximate: actual stream length may be slightly more than maxlen 

4323 minid: the minimum id in the stream to query 

4324 Can't be specified with maxlen. 

4325 limit: specifies the maximum number of entries to retrieve 

4326 ref_policy: optional reference policy for consumer groups: 

4327 - KEEPREF (default): Trims entries but preserves references in consumer groups' PEL 

4328 - DELREF: Trims entries and removes all references from consumer groups' PEL 

4329 - ACKED: Only trims entries that were read and acknowledged by all consumer groups 

4330 

4331 For more information, see https://redis.io/commands/xtrim 

4332 """ 

4333 pieces: list[EncodableT] = [] 

4334 if maxlen is not None and minid is not None: 

4335 raise DataError("Only one of ``maxlen`` or ``minid`` may be specified") 

4336 

4337 if maxlen is None and minid is None: 

4338 raise DataError("One of ``maxlen`` or ``minid`` must be specified") 

4339 

4340 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}: 

4341 raise DataError("XTRIM ref_policy must be one of: KEEPREF, DELREF, ACKED") 

4342 

4343 if maxlen is not None: 

4344 pieces.append(b"MAXLEN") 

4345 if minid is not None: 

4346 pieces.append(b"MINID") 

4347 if approximate: 

4348 pieces.append(b"~") 

4349 if maxlen is not None: 

4350 pieces.append(maxlen) 

4351 if minid is not None: 

4352 pieces.append(minid) 

4353 if limit is not None: 

4354 pieces.append(b"LIMIT") 

4355 pieces.append(limit) 

4356 if ref_policy is not None: 

4357 pieces.append(ref_policy) 

4358 

4359 return self.execute_command("XTRIM", name, *pieces) 

4360 

4361 

4362AsyncStreamCommands = StreamCommands 

4363 

4364 

4365class SortedSetCommands(CommandsProtocol): 

4366 """ 

4367 Redis commands for Sorted Sets data type. 

4368 see: https://redis.io/topics/data-types-intro#redis-sorted-sets 

4369 """ 

4370 

4371 def zadd( 

4372 self, 

4373 name: KeyT, 

4374 mapping: Mapping[AnyKeyT, EncodableT], 

4375 nx: bool = False, 

4376 xx: bool = False, 

4377 ch: bool = False, 

4378 incr: bool = False, 

4379 gt: bool = False, 

4380 lt: bool = False, 

4381 ) -> ResponseT: 

4382 """ 

4383 Set any number of element-name, score pairs to the key ``name``. Pairs 

4384 are specified as a dict of element-names keys to score values. 

4385 

4386 ``nx`` forces ZADD to only create new elements and not to update 

4387 scores for elements that already exist. 

4388 

4389 ``xx`` forces ZADD to only update scores of elements that already 

4390 exist. New elements will not be added. 

4391 

4392 ``ch`` modifies the return value to be the numbers of elements changed. 

4393 Changed elements include new elements that were added and elements 

4394 whose scores changed. 

4395 

4396 ``incr`` modifies ZADD to behave like ZINCRBY. In this mode only a 

4397 single element/score pair can be specified and the score is the amount 

4398 the existing score will be incremented by. When using this mode the 

4399 return value of ZADD will be the new score of the element. 

4400 

4401 ``lt`` only updates existing elements if the new score is less than 

4402 the current score. This flag doesn't prevent adding new elements. 

4403 

4404 ``gt`` only updates existing elements if the new score is greater than 

4405 the current score. This flag doesn't prevent adding new elements. 

4406 

4407 The return value of ZADD varies based on the mode specified. With no 

4408 options, ZADD returns the number of new elements added to the sorted 

4409 set. 

4410 

4411 ``nx``, ``lt``, and ``gt`` are mutually exclusive options. 

4412 

4413 See: https://redis.io/commands/ZADD 

4414 """ 

4415 if not mapping: 

4416 raise DataError("ZADD requires at least one element/score pair") 

4417 if nx and xx: 

4418 raise DataError("ZADD allows either 'nx' or 'xx', not both") 

4419 if gt and lt: 

4420 raise DataError("ZADD allows either 'gt' or 'lt', not both") 

4421 if incr and len(mapping) != 1: 

4422 raise DataError( 

4423 "ZADD option 'incr' only works when passing a single element/score pair" 

4424 ) 

4425 if nx and (gt or lt): 

4426 raise DataError("Only one of 'nx', 'lt', or 'gr' may be defined.") 

4427 

4428 pieces: list[EncodableT] = [] 

4429 options = {} 

4430 if nx: 

4431 pieces.append(b"NX") 

4432 if xx: 

4433 pieces.append(b"XX") 

4434 if ch: 

4435 pieces.append(b"CH") 

4436 if incr: 

4437 pieces.append(b"INCR") 

4438 options["as_score"] = True 

4439 if gt: 

4440 pieces.append(b"GT") 

4441 if lt: 

4442 pieces.append(b"LT") 

4443 for pair in mapping.items(): 

4444 pieces.append(pair[1]) 

4445 pieces.append(pair[0]) 

4446 return self.execute_command("ZADD", name, *pieces, **options) 

4447 

4448 def zcard(self, name: KeyT) -> ResponseT: 

4449 """ 

4450 Return the number of elements in the sorted set ``name`` 

4451 

4452 For more information, see https://redis.io/commands/zcard 

4453 """ 

4454 return self.execute_command("ZCARD", name, keys=[name]) 

4455 

4456 def zcount(self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT) -> ResponseT: 

4457 """ 

4458 Returns the number of elements in the sorted set at key ``name`` with 

4459 a score between ``min`` and ``max``. 

4460 

4461 For more information, see https://redis.io/commands/zcount 

4462 """ 

4463 return self.execute_command("ZCOUNT", name, min, max, keys=[name]) 

4464 

4465 def zdiff(self, keys: KeysT, withscores: bool = False) -> ResponseT: 

4466 """ 

4467 Returns the difference between the first and all successive input 

4468 sorted sets provided in ``keys``. 

4469 

4470 For more information, see https://redis.io/commands/zdiff 

4471 """ 

4472 pieces = [len(keys), *keys] 

4473 if withscores: 

4474 pieces.append("WITHSCORES") 

4475 return self.execute_command("ZDIFF", *pieces, keys=keys) 

4476 

4477 def zdiffstore(self, dest: KeyT, keys: KeysT) -> ResponseT: 

4478 """ 

4479 Computes the difference between the first and all successive input 

4480 sorted sets provided in ``keys`` and stores the result in ``dest``. 

4481 

4482 For more information, see https://redis.io/commands/zdiffstore 

4483 """ 

4484 pieces = [len(keys), *keys] 

4485 return self.execute_command("ZDIFFSTORE", dest, *pieces) 

4486 

4487 def zincrby(self, name: KeyT, amount: float, value: EncodableT) -> ResponseT: 

4488 """ 

4489 Increment the score of ``value`` in sorted set ``name`` by ``amount`` 

4490 

4491 For more information, see https://redis.io/commands/zincrby 

4492 """ 

4493 return self.execute_command("ZINCRBY", name, amount, value) 

4494 

4495 def zinter( 

4496 self, keys: KeysT, aggregate: Optional[str] = None, withscores: bool = False 

4497 ) -> ResponseT: 

4498 """ 

4499 Return the intersect of multiple sorted sets specified by ``keys``. 

4500 With the ``aggregate`` option, it is possible to specify how the 

4501 results of the union are aggregated. This option defaults to SUM, 

4502 where the score of an element is summed across the inputs where it 

4503 exists. When this option is set to either MIN or MAX, the resulting 

4504 set will contain the minimum or maximum score of an element across 

4505 the inputs where it exists. 

4506 

4507 For more information, see https://redis.io/commands/zinter 

4508 """ 

4509 return self._zaggregate("ZINTER", None, keys, aggregate, withscores=withscores) 

4510 

4511 def zinterstore( 

4512 self, 

4513 dest: KeyT, 

4514 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

4515 aggregate: Optional[str] = None, 

4516 ) -> ResponseT: 

4517 """ 

4518 Intersect multiple sorted sets specified by ``keys`` into a new 

4519 sorted set, ``dest``. Scores in the destination will be aggregated 

4520 based on the ``aggregate``. This option defaults to SUM, where the 

4521 score of an element is summed across the inputs where it exists. 

4522 When this option is set to either MIN or MAX, the resulting set will 

4523 contain the minimum or maximum score of an element across the inputs 

4524 where it exists. 

4525 

4526 For more information, see https://redis.io/commands/zinterstore 

4527 """ 

4528 return self._zaggregate("ZINTERSTORE", dest, keys, aggregate) 

4529 

4530 def zintercard( 

4531 self, numkeys: int, keys: List[str], limit: int = 0 

4532 ) -> Union[Awaitable[int], int]: 

4533 """ 

4534 Return the cardinality of the intersect of multiple sorted sets 

4535 specified by ``keys``. 

4536 When LIMIT provided (defaults to 0 and means unlimited), if the intersection 

4537 cardinality reaches limit partway through the computation, the algorithm will 

4538 exit and yield limit as the cardinality 

4539 

4540 For more information, see https://redis.io/commands/zintercard 

4541 """ 

4542 args = [numkeys, *keys, "LIMIT", limit] 

4543 return self.execute_command("ZINTERCARD", *args, keys=keys) 

4544 

4545 def zlexcount(self, name, min, max): 

4546 """ 

4547 Return the number of items in the sorted set ``name`` between the 

4548 lexicographical range ``min`` and ``max``. 

4549 

4550 For more information, see https://redis.io/commands/zlexcount 

4551 """ 

4552 return self.execute_command("ZLEXCOUNT", name, min, max, keys=[name]) 

4553 

4554 def zpopmax(self, name: KeyT, count: Optional[int] = None) -> ResponseT: 

4555 """ 

4556 Remove and return up to ``count`` members with the highest scores 

4557 from the sorted set ``name``. 

4558 

4559 For more information, see https://redis.io/commands/zpopmax 

4560 """ 

4561 args = (count is not None) and [count] or [] 

4562 options = {"withscores": True} 

4563 return self.execute_command("ZPOPMAX", name, *args, **options) 

4564 

4565 def zpopmin(self, name: KeyT, count: Optional[int] = None) -> ResponseT: 

4566 """ 

4567 Remove and return up to ``count`` members with the lowest scores 

4568 from the sorted set ``name``. 

4569 

4570 For more information, see https://redis.io/commands/zpopmin 

4571 """ 

4572 args = (count is not None) and [count] or [] 

4573 options = {"withscores": True} 

4574 return self.execute_command("ZPOPMIN", name, *args, **options) 

4575 

4576 def zrandmember( 

4577 self, key: KeyT, count: Optional[int] = None, withscores: bool = False 

4578 ) -> ResponseT: 

4579 """ 

4580 Return a random element from the sorted set value stored at key. 

4581 

4582 ``count`` if the argument is positive, return an array of distinct 

4583 fields. If called with a negative count, the behavior changes and 

4584 the command is allowed to return the same field multiple times. 

4585 In this case, the number of returned fields is the absolute value 

4586 of the specified count. 

4587 

4588 ``withscores`` The optional WITHSCORES modifier changes the reply so it 

4589 includes the respective scores of the randomly selected elements from 

4590 the sorted set. 

4591 

4592 For more information, see https://redis.io/commands/zrandmember 

4593 """ 

4594 params = [] 

4595 if count is not None: 

4596 params.append(count) 

4597 if withscores: 

4598 params.append("WITHSCORES") 

4599 

4600 return self.execute_command("ZRANDMEMBER", key, *params) 

4601 

4602 def bzpopmax(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4603 """ 

4604 ZPOPMAX a value off of the first non-empty sorted set 

4605 named in the ``keys`` list. 

4606 

4607 If none of the sorted sets in ``keys`` has a value to ZPOPMAX, 

4608 then block for ``timeout`` seconds, or until a member gets added 

4609 to one of the sorted sets. 

4610 

4611 If timeout is 0, then block indefinitely. 

4612 

4613 For more information, see https://redis.io/commands/bzpopmax 

4614 """ 

4615 if timeout is None: 

4616 timeout = 0 

4617 keys = list_or_args(keys, None) 

4618 keys.append(timeout) 

4619 return self.execute_command("BZPOPMAX", *keys) 

4620 

4621 def bzpopmin(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT: 

4622 """ 

4623 ZPOPMIN a value off of the first non-empty sorted set 

4624 named in the ``keys`` list. 

4625 

4626 If none of the sorted sets in ``keys`` has a value to ZPOPMIN, 

4627 then block for ``timeout`` seconds, or until a member gets added 

4628 to one of the sorted sets. 

4629 

4630 If timeout is 0, then block indefinitely. 

4631 

4632 For more information, see https://redis.io/commands/bzpopmin 

4633 """ 

4634 if timeout is None: 

4635 timeout = 0 

4636 keys: list[EncodableT] = list_or_args(keys, None) 

4637 keys.append(timeout) 

4638 return self.execute_command("BZPOPMIN", *keys) 

4639 

4640 def zmpop( 

4641 self, 

4642 num_keys: int, 

4643 keys: List[str], 

4644 min: Optional[bool] = False, 

4645 max: Optional[bool] = False, 

4646 count: Optional[int] = 1, 

4647 ) -> Union[Awaitable[list], list]: 

4648 """ 

4649 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4650 named in the ``keys`` list. 

4651 For more information, see https://redis.io/commands/zmpop 

4652 """ 

4653 args = [num_keys] + keys 

4654 if (min and max) or (not min and not max): 

4655 raise DataError 

4656 elif min: 

4657 args.append("MIN") 

4658 else: 

4659 args.append("MAX") 

4660 if count != 1: 

4661 args.extend(["COUNT", count]) 

4662 

4663 return self.execute_command("ZMPOP", *args) 

4664 

4665 def bzmpop( 

4666 self, 

4667 timeout: float, 

4668 numkeys: int, 

4669 keys: List[str], 

4670 min: Optional[bool] = False, 

4671 max: Optional[bool] = False, 

4672 count: Optional[int] = 1, 

4673 ) -> Optional[list]: 

4674 """ 

4675 Pop ``count`` values (default 1) off of the first non-empty sorted set 

4676 named in the ``keys`` list. 

4677 

4678 If none of the sorted sets in ``keys`` has a value to pop, 

4679 then block for ``timeout`` seconds, or until a member gets added 

4680 to one of the sorted sets. 

4681 

4682 If timeout is 0, then block indefinitely. 

4683 

4684 For more information, see https://redis.io/commands/bzmpop 

4685 """ 

4686 args = [timeout, numkeys, *keys] 

4687 if (min and max) or (not min and not max): 

4688 raise DataError("Either min or max, but not both must be set") 

4689 elif min: 

4690 args.append("MIN") 

4691 else: 

4692 args.append("MAX") 

4693 args.extend(["COUNT", count]) 

4694 

4695 return self.execute_command("BZMPOP", *args) 

4696 

4697 def _zrange( 

4698 self, 

4699 command, 

4700 dest: Union[KeyT, None], 

4701 name: KeyT, 

4702 start: int, 

4703 end: int, 

4704 desc: bool = False, 

4705 byscore: bool = False, 

4706 bylex: bool = False, 

4707 withscores: bool = False, 

4708 score_cast_func: Union[type, Callable, None] = float, 

4709 offset: Optional[int] = None, 

4710 num: Optional[int] = None, 

4711 ) -> ResponseT: 

4712 if byscore and bylex: 

4713 raise DataError("``byscore`` and ``bylex`` can not be specified together.") 

4714 if (offset is not None and num is None) or (num is not None and offset is None): 

4715 raise DataError("``offset`` and ``num`` must both be specified.") 

4716 if bylex and withscores: 

4717 raise DataError( 

4718 "``withscores`` not supported in combination with ``bylex``." 

4719 ) 

4720 pieces = [command] 

4721 if dest: 

4722 pieces.append(dest) 

4723 pieces.extend([name, start, end]) 

4724 if byscore: 

4725 pieces.append("BYSCORE") 

4726 if bylex: 

4727 pieces.append("BYLEX") 

4728 if desc: 

4729 pieces.append("REV") 

4730 if offset is not None and num is not None: 

4731 pieces.extend(["LIMIT", offset, num]) 

4732 if withscores: 

4733 pieces.append("WITHSCORES") 

4734 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4735 options["keys"] = [name] 

4736 return self.execute_command(*pieces, **options) 

4737 

4738 def zrange( 

4739 self, 

4740 name: KeyT, 

4741 start: int, 

4742 end: int, 

4743 desc: bool = False, 

4744 withscores: bool = False, 

4745 score_cast_func: Union[type, Callable] = float, 

4746 byscore: bool = False, 

4747 bylex: bool = False, 

4748 offset: Optional[int] = None, 

4749 num: Optional[int] = None, 

4750 ) -> ResponseT: 

4751 """ 

4752 Return a range of values from sorted set ``name`` between 

4753 ``start`` and ``end`` sorted in ascending order. 

4754 

4755 ``start`` and ``end`` can be negative, indicating the end of the range. 

4756 

4757 ``desc`` a boolean indicating whether to sort the results in reversed 

4758 order. 

4759 

4760 ``withscores`` indicates to return the scores along with the values. 

4761 The return type is a list of (value, score) pairs. 

4762 

4763 ``score_cast_func`` a callable used to cast the score return value. 

4764 

4765 ``byscore`` when set to True, returns the range of elements from the 

4766 sorted set having scores equal or between ``start`` and ``end``. 

4767 

4768 ``bylex`` when set to True, returns the range of elements from the 

4769 sorted set between the ``start`` and ``end`` lexicographical closed 

4770 range intervals. 

4771 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

4772 whether the range interval is exclusive or inclusive, respectively. 

4773 

4774 ``offset`` and ``num`` are specified, then return a slice of the range. 

4775 Can't be provided when using ``bylex``. 

4776 

4777 For more information, see https://redis.io/commands/zrange 

4778 """ 

4779 # Need to support ``desc`` also when using old redis version 

4780 # because it was supported in 3.5.3 (of redis-py) 

4781 if not byscore and not bylex and (offset is None and num is None) and desc: 

4782 return self.zrevrange(name, start, end, withscores, score_cast_func) 

4783 

4784 return self._zrange( 

4785 "ZRANGE", 

4786 None, 

4787 name, 

4788 start, 

4789 end, 

4790 desc, 

4791 byscore, 

4792 bylex, 

4793 withscores, 

4794 score_cast_func, 

4795 offset, 

4796 num, 

4797 ) 

4798 

4799 def zrevrange( 

4800 self, 

4801 name: KeyT, 

4802 start: int, 

4803 end: int, 

4804 withscores: bool = False, 

4805 score_cast_func: Union[type, Callable] = float, 

4806 ) -> ResponseT: 

4807 """ 

4808 Return a range of values from sorted set ``name`` between 

4809 ``start`` and ``end`` sorted in descending order. 

4810 

4811 ``start`` and ``end`` can be negative, indicating the end of the range. 

4812 

4813 ``withscores`` indicates to return the scores along with the values 

4814 The return type is a list of (value, score) pairs 

4815 

4816 ``score_cast_func`` a callable used to cast the score return value 

4817 

4818 For more information, see https://redis.io/commands/zrevrange 

4819 """ 

4820 pieces = ["ZREVRANGE", name, start, end] 

4821 if withscores: 

4822 pieces.append(b"WITHSCORES") 

4823 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4824 options["keys"] = name 

4825 return self.execute_command(*pieces, **options) 

4826 

4827 def zrangestore( 

4828 self, 

4829 dest: KeyT, 

4830 name: KeyT, 

4831 start: int, 

4832 end: int, 

4833 byscore: bool = False, 

4834 bylex: bool = False, 

4835 desc: bool = False, 

4836 offset: Optional[int] = None, 

4837 num: Optional[int] = None, 

4838 ) -> ResponseT: 

4839 """ 

4840 Stores in ``dest`` the result of a range of values from sorted set 

4841 ``name`` between ``start`` and ``end`` sorted in ascending order. 

4842 

4843 ``start`` and ``end`` can be negative, indicating the end of the range. 

4844 

4845 ``byscore`` when set to True, returns the range of elements from the 

4846 sorted set having scores equal or between ``start`` and ``end``. 

4847 

4848 ``bylex`` when set to True, returns the range of elements from the 

4849 sorted set between the ``start`` and ``end`` lexicographical closed 

4850 range intervals. 

4851 Valid ``start`` and ``end`` must start with ( or [, in order to specify 

4852 whether the range interval is exclusive or inclusive, respectively. 

4853 

4854 ``desc`` a boolean indicating whether to sort the results in reversed 

4855 order. 

4856 

4857 ``offset`` and ``num`` are specified, then return a slice of the range. 

4858 Can't be provided when using ``bylex``. 

4859 

4860 For more information, see https://redis.io/commands/zrangestore 

4861 """ 

4862 return self._zrange( 

4863 "ZRANGESTORE", 

4864 dest, 

4865 name, 

4866 start, 

4867 end, 

4868 desc, 

4869 byscore, 

4870 bylex, 

4871 False, 

4872 None, 

4873 offset, 

4874 num, 

4875 ) 

4876 

4877 def zrangebylex( 

4878 self, 

4879 name: KeyT, 

4880 min: EncodableT, 

4881 max: EncodableT, 

4882 start: Optional[int] = None, 

4883 num: Optional[int] = None, 

4884 ) -> ResponseT: 

4885 """ 

4886 Return the lexicographical range of values from sorted set ``name`` 

4887 between ``min`` and ``max``. 

4888 

4889 If ``start`` and ``num`` are specified, then return a slice of the 

4890 range. 

4891 

4892 For more information, see https://redis.io/commands/zrangebylex 

4893 """ 

4894 if (start is not None and num is None) or (num is not None and start is None): 

4895 raise DataError("``start`` and ``num`` must both be specified") 

4896 pieces = ["ZRANGEBYLEX", name, min, max] 

4897 if start is not None and num is not None: 

4898 pieces.extend([b"LIMIT", start, num]) 

4899 return self.execute_command(*pieces, keys=[name]) 

4900 

4901 def zrevrangebylex( 

4902 self, 

4903 name: KeyT, 

4904 max: EncodableT, 

4905 min: EncodableT, 

4906 start: Optional[int] = None, 

4907 num: Optional[int] = None, 

4908 ) -> ResponseT: 

4909 """ 

4910 Return the reversed lexicographical range of values from sorted set 

4911 ``name`` between ``max`` and ``min``. 

4912 

4913 If ``start`` and ``num`` are specified, then return a slice of the 

4914 range. 

4915 

4916 For more information, see https://redis.io/commands/zrevrangebylex 

4917 """ 

4918 if (start is not None and num is None) or (num is not None and start is None): 

4919 raise DataError("``start`` and ``num`` must both be specified") 

4920 pieces = ["ZREVRANGEBYLEX", name, max, min] 

4921 if start is not None and num is not None: 

4922 pieces.extend(["LIMIT", start, num]) 

4923 return self.execute_command(*pieces, keys=[name]) 

4924 

4925 def zrangebyscore( 

4926 self, 

4927 name: KeyT, 

4928 min: ZScoreBoundT, 

4929 max: ZScoreBoundT, 

4930 start: Optional[int] = None, 

4931 num: Optional[int] = None, 

4932 withscores: bool = False, 

4933 score_cast_func: Union[type, Callable] = float, 

4934 ) -> ResponseT: 

4935 """ 

4936 Return a range of values from the sorted set ``name`` with scores 

4937 between ``min`` and ``max``. 

4938 

4939 If ``start`` and ``num`` are specified, then return a slice 

4940 of the range. 

4941 

4942 ``withscores`` indicates to return the scores along with the values. 

4943 The return type is a list of (value, score) pairs 

4944 

4945 `score_cast_func`` a callable used to cast the score return value 

4946 

4947 For more information, see https://redis.io/commands/zrangebyscore 

4948 """ 

4949 if (start is not None and num is None) or (num is not None and start is None): 

4950 raise DataError("``start`` and ``num`` must both be specified") 

4951 pieces = ["ZRANGEBYSCORE", name, min, max] 

4952 if start is not None and num is not None: 

4953 pieces.extend(["LIMIT", start, num]) 

4954 if withscores: 

4955 pieces.append("WITHSCORES") 

4956 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4957 options["keys"] = [name] 

4958 return self.execute_command(*pieces, **options) 

4959 

4960 def zrevrangebyscore( 

4961 self, 

4962 name: KeyT, 

4963 max: ZScoreBoundT, 

4964 min: ZScoreBoundT, 

4965 start: Optional[int] = None, 

4966 num: Optional[int] = None, 

4967 withscores: bool = False, 

4968 score_cast_func: Union[type, Callable] = float, 

4969 ): 

4970 """ 

4971 Return a range of values from the sorted set ``name`` with scores 

4972 between ``min`` and ``max`` in descending order. 

4973 

4974 If ``start`` and ``num`` are specified, then return a slice 

4975 of the range. 

4976 

4977 ``withscores`` indicates to return the scores along with the values. 

4978 The return type is a list of (value, score) pairs 

4979 

4980 ``score_cast_func`` a callable used to cast the score return value 

4981 

4982 For more information, see https://redis.io/commands/zrevrangebyscore 

4983 """ 

4984 if (start is not None and num is None) or (num is not None and start is None): 

4985 raise DataError("``start`` and ``num`` must both be specified") 

4986 pieces = ["ZREVRANGEBYSCORE", name, max, min] 

4987 if start is not None and num is not None: 

4988 pieces.extend(["LIMIT", start, num]) 

4989 if withscores: 

4990 pieces.append("WITHSCORES") 

4991 options = {"withscores": withscores, "score_cast_func": score_cast_func} 

4992 options["keys"] = [name] 

4993 return self.execute_command(*pieces, **options) 

4994 

4995 def zrank( 

4996 self, 

4997 name: KeyT, 

4998 value: EncodableT, 

4999 withscore: bool = False, 

5000 score_cast_func: Union[type, Callable] = float, 

5001 ) -> ResponseT: 

5002 """ 

5003 Returns a 0-based value indicating the rank of ``value`` in sorted set 

5004 ``name``. 

5005 The optional WITHSCORE argument supplements the command's 

5006 reply with the score of the element returned. 

5007 

5008 ``score_cast_func`` a callable used to cast the score return value 

5009 

5010 For more information, see https://redis.io/commands/zrank 

5011 """ 

5012 pieces = ["ZRANK", name, value] 

5013 if withscore: 

5014 pieces.append("WITHSCORE") 

5015 

5016 options = {"withscore": withscore, "score_cast_func": score_cast_func} 

5017 

5018 return self.execute_command(*pieces, **options) 

5019 

5020 def zrem(self, name: KeyT, *values: FieldT) -> ResponseT: 

5021 """ 

5022 Remove member ``values`` from sorted set ``name`` 

5023 

5024 For more information, see https://redis.io/commands/zrem 

5025 """ 

5026 return self.execute_command("ZREM", name, *values) 

5027 

5028 def zremrangebylex(self, name: KeyT, min: EncodableT, max: EncodableT) -> ResponseT: 

5029 """ 

5030 Remove all elements in the sorted set ``name`` between the 

5031 lexicographical range specified by ``min`` and ``max``. 

5032 

5033 Returns the number of elements removed. 

5034 

5035 For more information, see https://redis.io/commands/zremrangebylex 

5036 """ 

5037 return self.execute_command("ZREMRANGEBYLEX", name, min, max) 

5038 

5039 def zremrangebyrank(self, name: KeyT, min: int, max: int) -> ResponseT: 

5040 """ 

5041 Remove all elements in the sorted set ``name`` with ranks between 

5042 ``min`` and ``max``. Values are 0-based, ordered from smallest score 

5043 to largest. Values can be negative indicating the highest scores. 

5044 Returns the number of elements removed 

5045 

5046 For more information, see https://redis.io/commands/zremrangebyrank 

5047 """ 

5048 return self.execute_command("ZREMRANGEBYRANK", name, min, max) 

5049 

5050 def zremrangebyscore( 

5051 self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT 

5052 ) -> ResponseT: 

5053 """ 

5054 Remove all elements in the sorted set ``name`` with scores 

5055 between ``min`` and ``max``. Returns the number of elements removed. 

5056 

5057 For more information, see https://redis.io/commands/zremrangebyscore 

5058 """ 

5059 return self.execute_command("ZREMRANGEBYSCORE", name, min, max) 

5060 

5061 def zrevrank( 

5062 self, 

5063 name: KeyT, 

5064 value: EncodableT, 

5065 withscore: bool = False, 

5066 score_cast_func: Union[type, Callable] = float, 

5067 ) -> ResponseT: 

5068 """ 

5069 Returns a 0-based value indicating the descending rank of 

5070 ``value`` in sorted set ``name``. 

5071 The optional ``withscore`` argument supplements the command's 

5072 reply with the score of the element returned. 

5073 

5074 ``score_cast_func`` a callable used to cast the score return value 

5075 

5076 For more information, see https://redis.io/commands/zrevrank 

5077 """ 

5078 pieces = ["ZREVRANK", name, value] 

5079 if withscore: 

5080 pieces.append("WITHSCORE") 

5081 

5082 options = {"withscore": withscore, "score_cast_func": score_cast_func} 

5083 

5084 return self.execute_command(*pieces, **options) 

5085 

5086 def zscore(self, name: KeyT, value: EncodableT) -> ResponseT: 

5087 """ 

5088 Return the score of element ``value`` in sorted set ``name`` 

5089 

5090 For more information, see https://redis.io/commands/zscore 

5091 """ 

5092 return self.execute_command("ZSCORE", name, value, keys=[name]) 

5093 

5094 def zunion( 

5095 self, 

5096 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

5097 aggregate: Optional[str] = None, 

5098 withscores: bool = False, 

5099 score_cast_func: Union[type, Callable] = float, 

5100 ) -> ResponseT: 

5101 """ 

5102 Return the union of multiple sorted sets specified by ``keys``. 

5103 ``keys`` can be provided as dictionary of keys and their weights. 

5104 Scores will be aggregated based on the ``aggregate``, or SUM if 

5105 none is provided. 

5106 

5107 ``score_cast_func`` a callable used to cast the score return value 

5108 

5109 For more information, see https://redis.io/commands/zunion 

5110 """ 

5111 return self._zaggregate( 

5112 "ZUNION", 

5113 None, 

5114 keys, 

5115 aggregate, 

5116 withscores=withscores, 

5117 score_cast_func=score_cast_func, 

5118 ) 

5119 

5120 def zunionstore( 

5121 self, 

5122 dest: KeyT, 

5123 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

5124 aggregate: Optional[str] = None, 

5125 ) -> ResponseT: 

5126 """ 

5127 Union multiple sorted sets specified by ``keys`` into 

5128 a new sorted set, ``dest``. Scores in the destination will be 

5129 aggregated based on the ``aggregate``, or SUM if none is provided. 

5130 

5131 For more information, see https://redis.io/commands/zunionstore 

5132 """ 

5133 return self._zaggregate("ZUNIONSTORE", dest, keys, aggregate) 

5134 

5135 def zmscore(self, key: KeyT, members: List[str]) -> ResponseT: 

5136 """ 

5137 Returns the scores associated with the specified members 

5138 in the sorted set stored at key. 

5139 ``members`` should be a list of the member name. 

5140 Return type is a list of score. 

5141 If the member does not exist, a None will be returned 

5142 in corresponding position. 

5143 

5144 For more information, see https://redis.io/commands/zmscore 

5145 """ 

5146 if not members: 

5147 raise DataError("ZMSCORE members must be a non-empty list") 

5148 pieces = [key] + members 

5149 return self.execute_command("ZMSCORE", *pieces, keys=[key]) 

5150 

5151 def _zaggregate( 

5152 self, 

5153 command: str, 

5154 dest: Union[KeyT, None], 

5155 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]], 

5156 aggregate: Optional[str] = None, 

5157 **options, 

5158 ) -> ResponseT: 

5159 pieces: list[EncodableT] = [command] 

5160 if dest is not None: 

5161 pieces.append(dest) 

5162 pieces.append(len(keys)) 

5163 if isinstance(keys, dict): 

5164 keys, weights = keys.keys(), keys.values() 

5165 else: 

5166 weights = None 

5167 pieces.extend(keys) 

5168 if weights: 

5169 pieces.append(b"WEIGHTS") 

5170 pieces.extend(weights) 

5171 if aggregate: 

5172 if aggregate.upper() in ["SUM", "MIN", "MAX"]: 

5173 pieces.append(b"AGGREGATE") 

5174 pieces.append(aggregate) 

5175 else: 

5176 raise DataError("aggregate can be sum, min or max.") 

5177 if options.get("withscores", False): 

5178 pieces.append(b"WITHSCORES") 

5179 options["keys"] = keys 

5180 return self.execute_command(*pieces, **options) 

5181 

5182 

5183AsyncSortedSetCommands = SortedSetCommands 

5184 

5185 

5186class HyperlogCommands(CommandsProtocol): 

5187 """ 

5188 Redis commands of HyperLogLogs data type. 

5189 see: https://redis.io/topics/data-types-intro#hyperloglogs 

5190 """ 

5191 

5192 def pfadd(self, name: KeyT, *values: FieldT) -> ResponseT: 

5193 """ 

5194 Adds the specified elements to the specified HyperLogLog. 

5195 

5196 For more information, see https://redis.io/commands/pfadd 

5197 """ 

5198 return self.execute_command("PFADD", name, *values) 

5199 

5200 def pfcount(self, *sources: KeyT) -> ResponseT: 

5201 """ 

5202 Return the approximated cardinality of 

5203 the set observed by the HyperLogLog at key(s). 

5204 

5205 For more information, see https://redis.io/commands/pfcount 

5206 """ 

5207 return self.execute_command("PFCOUNT", *sources) 

5208 

5209 def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT: 

5210 """ 

5211 Merge N different HyperLogLogs into a single one. 

5212 

5213 For more information, see https://redis.io/commands/pfmerge 

5214 """ 

5215 return self.execute_command("PFMERGE", dest, *sources) 

5216 

5217 

5218AsyncHyperlogCommands = HyperlogCommands 

5219 

5220 

5221class HashDataPersistOptions(Enum): 

5222 # set the value for each provided key to each 

5223 # provided value only if all do not already exist. 

5224 FNX = "FNX" 

5225 

5226 # set the value for each provided key to each 

5227 # provided value only if all already exist. 

5228 FXX = "FXX" 

5229 

5230 

5231class HashCommands(CommandsProtocol): 

5232 """ 

5233 Redis commands for Hash data type. 

5234 see: https://redis.io/topics/data-types-intro#redis-hashes 

5235 """ 

5236 

5237 def hdel(self, name: str, *keys: str) -> Union[Awaitable[int], int]: 

5238 """ 

5239 Delete ``keys`` from hash ``name`` 

5240 

5241 For more information, see https://redis.io/commands/hdel 

5242 """ 

5243 return self.execute_command("HDEL", name, *keys) 

5244 

5245 def hexists(self, name: str, key: str) -> Union[Awaitable[bool], bool]: 

5246 """ 

5247 Returns a boolean indicating if ``key`` exists within hash ``name`` 

5248 

5249 For more information, see https://redis.io/commands/hexists 

5250 """ 

5251 return self.execute_command("HEXISTS", name, key, keys=[name]) 

5252 

5253 def hget( 

5254 self, name: str, key: str 

5255 ) -> Union[Awaitable[Optional[str]], Optional[str]]: 

5256 """ 

5257 Return the value of ``key`` within the hash ``name`` 

5258 

5259 For more information, see https://redis.io/commands/hget 

5260 """ 

5261 return self.execute_command("HGET", name, key, keys=[name]) 

5262 

5263 def hgetall(self, name: str) -> Union[Awaitable[dict], dict]: 

5264 """ 

5265 Return a Python dict of the hash's name/value pairs 

5266 

5267 For more information, see https://redis.io/commands/hgetall 

5268 """ 

5269 return self.execute_command("HGETALL", name, keys=[name]) 

5270 

5271 def hgetdel( 

5272 self, name: str, *keys: str 

5273 ) -> Union[ 

5274 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]] 

5275 ]: 

5276 """ 

5277 Return the value of ``key`` within the hash ``name`` and 

5278 delete the field in the hash. 

5279 This command is similar to HGET, except for the fact that it also deletes 

5280 the key on success from the hash with the provided ```name```. 

5281 

5282 Available since Redis 8.0 

5283 For more information, see https://redis.io/commands/hgetdel 

5284 """ 

5285 if len(keys) == 0: 

5286 raise DataError("'hgetdel' should have at least one key provided") 

5287 

5288 return self.execute_command("HGETDEL", name, "FIELDS", len(keys), *keys) 

5289 

5290 def hgetex( 

5291 self, 

5292 name: KeyT, 

5293 *keys: str, 

5294 ex: Optional[ExpiryT] = None, 

5295 px: Optional[ExpiryT] = None, 

5296 exat: Optional[AbsExpiryT] = None, 

5297 pxat: Optional[AbsExpiryT] = None, 

5298 persist: bool = False, 

5299 ) -> Union[ 

5300 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]] 

5301 ]: 

5302 """ 

5303 Return the values of ``key`` and ``keys`` within the hash ``name`` 

5304 and optionally set their expiration. 

5305 

5306 ``ex`` sets an expire flag on ``kyes`` for ``ex`` seconds. 

5307 

5308 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds. 

5309 

5310 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds, 

5311 specified in unix time. 

5312 

5313 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds, 

5314 specified in unix time. 

5315 

5316 ``persist`` remove the time to live associated with the ``keys``. 

5317 

5318 Available since Redis 8.0 

5319 For more information, see https://redis.io/commands/hgetex 

5320 """ 

5321 if not keys: 

5322 raise DataError("'hgetex' should have at least one key provided") 

5323 

5324 if not at_most_one_value_set((ex, px, exat, pxat, persist)): 

5325 raise DataError( 

5326 "``ex``, ``px``, ``exat``, ``pxat``, " 

5327 "and ``persist`` are mutually exclusive." 

5328 ) 

5329 

5330 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat) 

5331 

5332 if persist: 

5333 exp_options.append("PERSIST") 

5334 

5335 return self.execute_command( 

5336 "HGETEX", 

5337 name, 

5338 *exp_options, 

5339 "FIELDS", 

5340 len(keys), 

5341 *keys, 

5342 ) 

5343 

5344 def hincrby( 

5345 self, name: str, key: str, amount: int = 1 

5346 ) -> Union[Awaitable[int], int]: 

5347 """ 

5348 Increment the value of ``key`` in hash ``name`` by ``amount`` 

5349 

5350 For more information, see https://redis.io/commands/hincrby 

5351 """ 

5352 return self.execute_command("HINCRBY", name, key, amount) 

5353 

5354 def hincrbyfloat( 

5355 self, name: str, key: str, amount: float = 1.0 

5356 ) -> Union[Awaitable[float], float]: 

5357 """ 

5358 Increment the value of ``key`` in hash ``name`` by floating ``amount`` 

5359 

5360 For more information, see https://redis.io/commands/hincrbyfloat 

5361 """ 

5362 return self.execute_command("HINCRBYFLOAT", name, key, amount) 

5363 

5364 def hkeys(self, name: str) -> Union[Awaitable[List], List]: 

5365 """ 

5366 Return the list of keys within hash ``name`` 

5367 

5368 For more information, see https://redis.io/commands/hkeys 

5369 """ 

5370 return self.execute_command("HKEYS", name, keys=[name]) 

5371 

5372 def hlen(self, name: str) -> Union[Awaitable[int], int]: 

5373 """ 

5374 Return the number of elements in hash ``name`` 

5375 

5376 For more information, see https://redis.io/commands/hlen 

5377 """ 

5378 return self.execute_command("HLEN", name, keys=[name]) 

5379 

5380 def hset( 

5381 self, 

5382 name: str, 

5383 key: Optional[str] = None, 

5384 value: Optional[str] = None, 

5385 mapping: Optional[dict] = None, 

5386 items: Optional[list] = None, 

5387 ) -> Union[Awaitable[int], int]: 

5388 """ 

5389 Set ``key`` to ``value`` within hash ``name``, 

5390 ``mapping`` accepts a dict of key/value pairs that will be 

5391 added to hash ``name``. 

5392 ``items`` accepts a list of key/value pairs that will be 

5393 added to hash ``name``. 

5394 Returns the number of fields that were added. 

5395 

5396 For more information, see https://redis.io/commands/hset 

5397 """ 

5398 

5399 if key is None and not mapping and not items: 

5400 raise DataError("'hset' with no key value pairs") 

5401 

5402 pieces = [] 

5403 if items: 

5404 pieces.extend(items) 

5405 if key is not None: 

5406 pieces.extend((key, value)) 

5407 if mapping: 

5408 for pair in mapping.items(): 

5409 pieces.extend(pair) 

5410 

5411 return self.execute_command("HSET", name, *pieces) 

5412 

5413 def hsetex( 

5414 self, 

5415 name: str, 

5416 key: Optional[str] = None, 

5417 value: Optional[str] = None, 

5418 mapping: Optional[dict] = None, 

5419 items: Optional[list] = None, 

5420 ex: Optional[ExpiryT] = None, 

5421 px: Optional[ExpiryT] = None, 

5422 exat: Optional[AbsExpiryT] = None, 

5423 pxat: Optional[AbsExpiryT] = None, 

5424 data_persist_option: Optional[HashDataPersistOptions] = None, 

5425 keepttl: bool = False, 

5426 ) -> Union[Awaitable[int], int]: 

5427 """ 

5428 Set ``key`` to ``value`` within hash ``name`` 

5429 

5430 ``mapping`` accepts a dict of key/value pairs that will be 

5431 added to hash ``name``. 

5432 

5433 ``items`` accepts a list of key/value pairs that will be 

5434 added to hash ``name``. 

5435 

5436 ``ex`` sets an expire flag on ``keys`` for ``ex`` seconds. 

5437 

5438 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds. 

5439 

5440 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds, 

5441 specified in unix time. 

5442 

5443 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds, 

5444 specified in unix time. 

5445 

5446 ``data_persist_option`` can be set to ``FNX`` or ``FXX`` to control the 

5447 behavior of the command. 

5448 ``FNX`` will set the value for each provided key to each 

5449 provided value only if all do not already exist. 

5450 ``FXX`` will set the value for each provided key to each 

5451 provided value only if all already exist. 

5452 

5453 ``keepttl`` if True, retain the time to live associated with the keys. 

5454 

5455 Returns the number of fields that were added. 

5456 

5457 Available since Redis 8.0 

5458 For more information, see https://redis.io/commands/hsetex 

5459 """ 

5460 if key is None and not mapping and not items: 

5461 raise DataError("'hsetex' with no key value pairs") 

5462 

5463 if items and len(items) % 2 != 0: 

5464 raise DataError( 

5465 "'hsetex' with odd number of items. " 

5466 "'items' must contain a list of key/value pairs." 

5467 ) 

5468 

5469 if not at_most_one_value_set((ex, px, exat, pxat, keepttl)): 

5470 raise DataError( 

5471 "``ex``, ``px``, ``exat``, ``pxat``, " 

5472 "and ``keepttl`` are mutually exclusive." 

5473 ) 

5474 

5475 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat) 

5476 if data_persist_option: 

5477 exp_options.append(data_persist_option.value) 

5478 

5479 if keepttl: 

5480 exp_options.append("KEEPTTL") 

5481 

5482 pieces = [] 

5483 if items: 

5484 pieces.extend(items) 

5485 if key is not None: 

5486 pieces.extend((key, value)) 

5487 if mapping: 

5488 for pair in mapping.items(): 

5489 pieces.extend(pair) 

5490 

5491 return self.execute_command( 

5492 "HSETEX", name, *exp_options, "FIELDS", int(len(pieces) / 2), *pieces 

5493 ) 

5494 

5495 def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool]: 

5496 """ 

5497 Set ``key`` to ``value`` within hash ``name`` if ``key`` does not 

5498 exist. Returns 1 if HSETNX created a field, otherwise 0. 

5499 

5500 For more information, see https://redis.io/commands/hsetnx 

5501 """ 

5502 return self.execute_command("HSETNX", name, key, value) 

5503 

5504 @deprecated_function( 

5505 version="4.0.0", 

5506 reason="Use 'hset' instead.", 

5507 name="hmset", 

5508 ) 

5509 def hmset(self, name: str, mapping: dict) -> Union[Awaitable[str], str]: 

5510 """ 

5511 Set key to value within hash ``name`` for each corresponding 

5512 key and value from the ``mapping`` dict. 

5513 

5514 For more information, see https://redis.io/commands/hmset 

5515 """ 

5516 if not mapping: 

5517 raise DataError("'hmset' with 'mapping' of length 0") 

5518 items = [] 

5519 for pair in mapping.items(): 

5520 items.extend(pair) 

5521 return self.execute_command("HMSET", name, *items) 

5522 

5523 def hmget(self, name: str, keys: List, *args: List) -> Union[Awaitable[List], List]: 

5524 """ 

5525 Returns a list of values ordered identically to ``keys`` 

5526 

5527 For more information, see https://redis.io/commands/hmget 

5528 """ 

5529 args = list_or_args(keys, args) 

5530 return self.execute_command("HMGET", name, *args, keys=[name]) 

5531 

5532 def hvals(self, name: str) -> Union[Awaitable[List], List]: 

5533 """ 

5534 Return the list of values within hash ``name`` 

5535 

5536 For more information, see https://redis.io/commands/hvals 

5537 """ 

5538 return self.execute_command("HVALS", name, keys=[name]) 

5539 

5540 def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]: 

5541 """ 

5542 Return the number of bytes stored in the value of ``key`` 

5543 within hash ``name`` 

5544 

5545 For more information, see https://redis.io/commands/hstrlen 

5546 """ 

5547 return self.execute_command("HSTRLEN", name, key, keys=[name]) 

5548 

5549 def hexpire( 

5550 self, 

5551 name: KeyT, 

5552 seconds: ExpiryT, 

5553 *fields: str, 

5554 nx: bool = False, 

5555 xx: bool = False, 

5556 gt: bool = False, 

5557 lt: bool = False, 

5558 ) -> ResponseT: 

5559 """ 

5560 Sets or updates the expiration time for fields within a hash key, using relative 

5561 time in seconds. 

5562 

5563 If a field already has an expiration time, the behavior of the update can be 

5564 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5565 

5566 The return value provides detailed information about the outcome for each field. 

5567 

5568 For more information, see https://redis.io/commands/hexpire 

5569 

5570 Args: 

5571 name: The name of the hash key. 

5572 seconds: Expiration time in seconds, relative. Can be an integer, or a 

5573 Python `timedelta` object. 

5574 fields: List of fields within the hash to apply the expiration time to. 

5575 nx: Set expiry only when the field has no expiry. 

5576 xx: Set expiry only when the field has an existing expiry. 

5577 gt: Set expiry only when the new expiry is greater than the current one. 

5578 lt: Set expiry only when the new expiry is less than the current one. 

5579 

5580 Returns: 

5581 Returns a list which contains for each field in the request: 

5582 - `-2` if the field does not exist, or if the key does not exist. 

5583 - `0` if the specified NX | XX | GT | LT condition was not met. 

5584 - `1` if the expiration time was set or updated. 

5585 - `2` if the field was deleted because the specified expiration time is 

5586 in the past. 

5587 """ 

5588 conditions = [nx, xx, gt, lt] 

5589 if sum(conditions) > 1: 

5590 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5591 

5592 if isinstance(seconds, datetime.timedelta): 

5593 seconds = int(seconds.total_seconds()) 

5594 

5595 options = [] 

5596 if nx: 

5597 options.append("NX") 

5598 if xx: 

5599 options.append("XX") 

5600 if gt: 

5601 options.append("GT") 

5602 if lt: 

5603 options.append("LT") 

5604 

5605 return self.execute_command( 

5606 "HEXPIRE", name, seconds, *options, "FIELDS", len(fields), *fields 

5607 ) 

5608 

5609 def hpexpire( 

5610 self, 

5611 name: KeyT, 

5612 milliseconds: ExpiryT, 

5613 *fields: str, 

5614 nx: bool = False, 

5615 xx: bool = False, 

5616 gt: bool = False, 

5617 lt: bool = False, 

5618 ) -> ResponseT: 

5619 """ 

5620 Sets or updates the expiration time for fields within a hash key, using relative 

5621 time in milliseconds. 

5622 

5623 If a field already has an expiration time, the behavior of the update can be 

5624 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5625 

5626 The return value provides detailed information about the outcome for each field. 

5627 

5628 For more information, see https://redis.io/commands/hpexpire 

5629 

5630 Args: 

5631 name: The name of the hash key. 

5632 milliseconds: Expiration time in milliseconds, relative. Can be an integer, 

5633 or a Python `timedelta` object. 

5634 fields: List of fields within the hash to apply the expiration time to. 

5635 nx: Set expiry only when the field has no expiry. 

5636 xx: Set expiry only when the field has an existing expiry. 

5637 gt: Set expiry only when the new expiry is greater than the current one. 

5638 lt: Set expiry only when the new expiry is less than the current one. 

5639 

5640 Returns: 

5641 Returns a list which contains for each field in the request: 

5642 - `-2` if the field does not exist, or if the key does not exist. 

5643 - `0` if the specified NX | XX | GT | LT condition was not met. 

5644 - `1` if the expiration time was set or updated. 

5645 - `2` if the field was deleted because the specified expiration time is 

5646 in the past. 

5647 """ 

5648 conditions = [nx, xx, gt, lt] 

5649 if sum(conditions) > 1: 

5650 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5651 

5652 if isinstance(milliseconds, datetime.timedelta): 

5653 milliseconds = int(milliseconds.total_seconds() * 1000) 

5654 

5655 options = [] 

5656 if nx: 

5657 options.append("NX") 

5658 if xx: 

5659 options.append("XX") 

5660 if gt: 

5661 options.append("GT") 

5662 if lt: 

5663 options.append("LT") 

5664 

5665 return self.execute_command( 

5666 "HPEXPIRE", name, milliseconds, *options, "FIELDS", len(fields), *fields 

5667 ) 

5668 

5669 def hexpireat( 

5670 self, 

5671 name: KeyT, 

5672 unix_time_seconds: AbsExpiryT, 

5673 *fields: str, 

5674 nx: bool = False, 

5675 xx: bool = False, 

5676 gt: bool = False, 

5677 lt: bool = False, 

5678 ) -> ResponseT: 

5679 """ 

5680 Sets or updates the expiration time for fields within a hash key, using an 

5681 absolute Unix timestamp in seconds. 

5682 

5683 If a field already has an expiration time, the behavior of the update can be 

5684 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5685 

5686 The return value provides detailed information about the outcome for each field. 

5687 

5688 For more information, see https://redis.io/commands/hexpireat 

5689 

5690 Args: 

5691 name: The name of the hash key. 

5692 unix_time_seconds: Expiration time as Unix timestamp in seconds. Can be an 

5693 integer or a Python `datetime` object. 

5694 fields: List of fields within the hash to apply the expiration time to. 

5695 nx: Set expiry only when the field has no expiry. 

5696 xx: Set expiry only when the field has an existing expiration time. 

5697 gt: Set expiry only when the new expiry is greater than the current one. 

5698 lt: Set expiry only when the new expiry is less than the current one. 

5699 

5700 Returns: 

5701 Returns a list which contains for each field in the request: 

5702 - `-2` if the field does not exist, or if the key does not exist. 

5703 - `0` if the specified NX | XX | GT | LT condition was not met. 

5704 - `1` if the expiration time was set or updated. 

5705 - `2` if the field was deleted because the specified expiration time is 

5706 in the past. 

5707 """ 

5708 conditions = [nx, xx, gt, lt] 

5709 if sum(conditions) > 1: 

5710 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5711 

5712 if isinstance(unix_time_seconds, datetime.datetime): 

5713 unix_time_seconds = int(unix_time_seconds.timestamp()) 

5714 

5715 options = [] 

5716 if nx: 

5717 options.append("NX") 

5718 if xx: 

5719 options.append("XX") 

5720 if gt: 

5721 options.append("GT") 

5722 if lt: 

5723 options.append("LT") 

5724 

5725 return self.execute_command( 

5726 "HEXPIREAT", 

5727 name, 

5728 unix_time_seconds, 

5729 *options, 

5730 "FIELDS", 

5731 len(fields), 

5732 *fields, 

5733 ) 

5734 

5735 def hpexpireat( 

5736 self, 

5737 name: KeyT, 

5738 unix_time_milliseconds: AbsExpiryT, 

5739 *fields: str, 

5740 nx: bool = False, 

5741 xx: bool = False, 

5742 gt: bool = False, 

5743 lt: bool = False, 

5744 ) -> ResponseT: 

5745 """ 

5746 Sets or updates the expiration time for fields within a hash key, using an 

5747 absolute Unix timestamp in milliseconds. 

5748 

5749 If a field already has an expiration time, the behavior of the update can be 

5750 controlled using the `nx`, `xx`, `gt`, and `lt` parameters. 

5751 

5752 The return value provides detailed information about the outcome for each field. 

5753 

5754 For more information, see https://redis.io/commands/hpexpireat 

5755 

5756 Args: 

5757 name: The name of the hash key. 

5758 unix_time_milliseconds: Expiration time as Unix timestamp in milliseconds. 

5759 Can be an integer or a Python `datetime` object. 

5760 fields: List of fields within the hash to apply the expiry. 

5761 nx: Set expiry only when the field has no expiry. 

5762 xx: Set expiry only when the field has an existing expiry. 

5763 gt: Set expiry only when the new expiry is greater than the current one. 

5764 lt: Set expiry only when the new expiry is less than the current one. 

5765 

5766 Returns: 

5767 Returns a list which contains for each field in the request: 

5768 - `-2` if the field does not exist, or if the key does not exist. 

5769 - `0` if the specified NX | XX | GT | LT condition was not met. 

5770 - `1` if the expiration time was set or updated. 

5771 - `2` if the field was deleted because the specified expiration time is 

5772 in the past. 

5773 """ 

5774 conditions = [nx, xx, gt, lt] 

5775 if sum(conditions) > 1: 

5776 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.") 

5777 

5778 if isinstance(unix_time_milliseconds, datetime.datetime): 

5779 unix_time_milliseconds = int(unix_time_milliseconds.timestamp() * 1000) 

5780 

5781 options = [] 

5782 if nx: 

5783 options.append("NX") 

5784 if xx: 

5785 options.append("XX") 

5786 if gt: 

5787 options.append("GT") 

5788 if lt: 

5789 options.append("LT") 

5790 

5791 return self.execute_command( 

5792 "HPEXPIREAT", 

5793 name, 

5794 unix_time_milliseconds, 

5795 *options, 

5796 "FIELDS", 

5797 len(fields), 

5798 *fields, 

5799 ) 

5800 

5801 def hpersist(self, name: KeyT, *fields: str) -> ResponseT: 

5802 """ 

5803 Removes the expiration time for each specified field in a hash. 

5804 

5805 For more information, see https://redis.io/commands/hpersist 

5806 

5807 Args: 

5808 name: The name of the hash key. 

5809 fields: A list of fields within the hash from which to remove the 

5810 expiration time. 

5811 

5812 Returns: 

5813 Returns a list which contains for each field in the request: 

5814 - `-2` if the field does not exist, or if the key does not exist. 

5815 - `-1` if the field exists but has no associated expiration time. 

5816 - `1` if the expiration time was successfully removed from the field. 

5817 """ 

5818 return self.execute_command("HPERSIST", name, "FIELDS", len(fields), *fields) 

5819 

5820 def hexpiretime(self, key: KeyT, *fields: str) -> ResponseT: 

5821 """ 

5822 Returns the expiration times of hash fields as Unix timestamps in seconds. 

5823 

5824 For more information, see https://redis.io/commands/hexpiretime 

5825 

5826 Args: 

5827 key: The hash key. 

5828 fields: A list of fields within the hash for which to get the expiration 

5829 time. 

5830 

5831 Returns: 

5832 Returns a list which contains for each field in the request: 

5833 - `-2` if the field does not exist, or if the key does not exist. 

5834 - `-1` if the field exists but has no associated expire time. 

5835 - A positive integer representing the expiration Unix timestamp in 

5836 seconds, if the field has an associated expiration time. 

5837 """ 

5838 return self.execute_command( 

5839 "HEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key] 

5840 ) 

5841 

5842 def hpexpiretime(self, key: KeyT, *fields: str) -> ResponseT: 

5843 """ 

5844 Returns the expiration times of hash fields as Unix timestamps in milliseconds. 

5845 

5846 For more information, see https://redis.io/commands/hpexpiretime 

5847 

5848 Args: 

5849 key: The hash key. 

5850 fields: A list of fields within the hash for which to get the expiration 

5851 time. 

5852 

5853 Returns: 

5854 Returns a list which contains for each field in the request: 

5855 - `-2` if the field does not exist, or if the key does not exist. 

5856 - `-1` if the field exists but has no associated expire time. 

5857 - A positive integer representing the expiration Unix timestamp in 

5858 milliseconds, if the field has an associated expiration time. 

5859 """ 

5860 return self.execute_command( 

5861 "HPEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key] 

5862 ) 

5863 

5864 def httl(self, key: KeyT, *fields: str) -> ResponseT: 

5865 """ 

5866 Returns the TTL (Time To Live) in seconds for each specified field within a hash 

5867 key. 

5868 

5869 For more information, see https://redis.io/commands/httl 

5870 

5871 Args: 

5872 key: The hash key. 

5873 fields: A list of fields within the hash for which to get the TTL. 

5874 

5875 Returns: 

5876 Returns a list which contains for each field in the request: 

5877 - `-2` if the field does not exist, or if the key does not exist. 

5878 - `-1` if the field exists but has no associated expire time. 

5879 - A positive integer representing the TTL in seconds if the field has 

5880 an associated expiration time. 

5881 """ 

5882 return self.execute_command( 

5883 "HTTL", key, "FIELDS", len(fields), *fields, keys=[key] 

5884 ) 

5885 

5886 def hpttl(self, key: KeyT, *fields: str) -> ResponseT: 

5887 """ 

5888 Returns the TTL (Time To Live) in milliseconds for each specified field within a 

5889 hash key. 

5890 

5891 For more information, see https://redis.io/commands/hpttl 

5892 

5893 Args: 

5894 key: The hash key. 

5895 fields: A list of fields within the hash for which to get the TTL. 

5896 

5897 Returns: 

5898 Returns a list which contains for each field in the request: 

5899 - `-2` if the field does not exist, or if the key does not exist. 

5900 - `-1` if the field exists but has no associated expire time. 

5901 - A positive integer representing the TTL in milliseconds if the field 

5902 has an associated expiration time. 

5903 """ 

5904 return self.execute_command( 

5905 "HPTTL", key, "FIELDS", len(fields), *fields, keys=[key] 

5906 ) 

5907 

5908 

5909AsyncHashCommands = HashCommands 

5910 

5911 

5912class Script: 

5913 """ 

5914 An executable Lua script object returned by ``register_script`` 

5915 """ 

5916 

5917 def __init__(self, registered_client: "redis.client.Redis", script: ScriptTextT): 

5918 self.registered_client = registered_client 

5919 self.script = script 

5920 # Precalculate and store the SHA1 hex digest of the script. 

5921 

5922 if isinstance(script, str): 

5923 # We need the encoding from the client in order to generate an 

5924 # accurate byte representation of the script 

5925 encoder = self.get_encoder() 

5926 script = encoder.encode(script) 

5927 self.sha = hashlib.sha1(script).hexdigest() 

5928 

5929 def __call__( 

5930 self, 

5931 keys: Union[Sequence[KeyT], None] = None, 

5932 args: Union[Iterable[EncodableT], None] = None, 

5933 client: Union["redis.client.Redis", None] = None, 

5934 ): 

5935 """Execute the script, passing any required ``args``""" 

5936 keys = keys or [] 

5937 args = args or [] 

5938 if client is None: 

5939 client = self.registered_client 

5940 args = tuple(keys) + tuple(args) 

5941 # make sure the Redis server knows about the script 

5942 from redis.client import Pipeline 

5943 

5944 if isinstance(client, Pipeline): 

5945 # Make sure the pipeline can register the script before executing. 

5946 client.scripts.add(self) 

5947 try: 

5948 return client.evalsha(self.sha, len(keys), *args) 

5949 except NoScriptError: 

5950 # Maybe the client is pointed to a different server than the client 

5951 # that created this instance? 

5952 # Overwrite the sha just in case there was a discrepancy. 

5953 self.sha = client.script_load(self.script) 

5954 return client.evalsha(self.sha, len(keys), *args) 

5955 

5956 def get_encoder(self): 

5957 """Get the encoder to encode string scripts into bytes.""" 

5958 try: 

5959 return self.registered_client.get_encoder() 

5960 except AttributeError: 

5961 # DEPRECATED 

5962 # In version <=4.1.2, this was the code we used to get the encoder. 

5963 # However, after 4.1.2 we added support for scripting in clustered 

5964 # redis. ClusteredRedis doesn't have a `.connection_pool` attribute 

5965 # so we changed the Script class to use 

5966 # `self.registered_client.get_encoder` (see above). 

5967 # However, that is technically a breaking change, as consumers who 

5968 # use Scripts directly might inject a `registered_client` that 

5969 # doesn't have a `.get_encoder` field. This try/except prevents us 

5970 # from breaking backward-compatibility. Ideally, it would be 

5971 # removed in the next major release. 

5972 return self.registered_client.connection_pool.get_encoder() 

5973 

5974 

5975class AsyncScript: 

5976 """ 

5977 An executable Lua script object returned by ``register_script`` 

5978 """ 

5979 

5980 def __init__( 

5981 self, 

5982 registered_client: "redis.asyncio.client.Redis", 

5983 script: ScriptTextT, 

5984 ): 

5985 self.registered_client = registered_client 

5986 self.script = script 

5987 # Precalculate and store the SHA1 hex digest of the script. 

5988 

5989 if isinstance(script, str): 

5990 # We need the encoding from the client in order to generate an 

5991 # accurate byte representation of the script 

5992 try: 

5993 encoder = registered_client.connection_pool.get_encoder() 

5994 except AttributeError: 

5995 # Cluster 

5996 encoder = registered_client.get_encoder() 

5997 script = encoder.encode(script) 

5998 self.sha = hashlib.sha1(script).hexdigest() 

5999 

6000 async def __call__( 

6001 self, 

6002 keys: Union[Sequence[KeyT], None] = None, 

6003 args: Union[Iterable[EncodableT], None] = None, 

6004 client: Union["redis.asyncio.client.Redis", None] = None, 

6005 ): 

6006 """Execute the script, passing any required ``args``""" 

6007 keys = keys or [] 

6008 args = args or [] 

6009 if client is None: 

6010 client = self.registered_client 

6011 args = tuple(keys) + tuple(args) 

6012 # make sure the Redis server knows about the script 

6013 from redis.asyncio.client import Pipeline 

6014 

6015 if isinstance(client, Pipeline): 

6016 # Make sure the pipeline can register the script before executing. 

6017 client.scripts.add(self) 

6018 try: 

6019 return await client.evalsha(self.sha, len(keys), *args) 

6020 except NoScriptError: 

6021 # Maybe the client is pointed to a different server than the client 

6022 # that created this instance? 

6023 # Overwrite the sha just in case there was a discrepancy. 

6024 self.sha = await client.script_load(self.script) 

6025 return await client.evalsha(self.sha, len(keys), *args) 

6026 

6027 

6028class PubSubCommands(CommandsProtocol): 

6029 """ 

6030 Redis PubSub commands. 

6031 see https://redis.io/topics/pubsub 

6032 """ 

6033 

6034 def publish(self, channel: ChannelT, message: EncodableT, **kwargs) -> ResponseT: 

6035 """ 

6036 Publish ``message`` on ``channel``. 

6037 Returns the number of subscribers the message was delivered to. 

6038 

6039 For more information, see https://redis.io/commands/publish 

6040 """ 

6041 return self.execute_command("PUBLISH", channel, message, **kwargs) 

6042 

6043 def spublish(self, shard_channel: ChannelT, message: EncodableT) -> ResponseT: 

6044 """ 

6045 Posts a message to the given shard channel. 

6046 Returns the number of clients that received the message 

6047 

6048 For more information, see https://redis.io/commands/spublish 

6049 """ 

6050 return self.execute_command("SPUBLISH", shard_channel, message) 

6051 

6052 def pubsub_channels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

6053 """ 

6054 Return a list of channels that have at least one subscriber 

6055 

6056 For more information, see https://redis.io/commands/pubsub-channels 

6057 """ 

6058 return self.execute_command("PUBSUB CHANNELS", pattern, **kwargs) 

6059 

6060 def pubsub_shardchannels(self, pattern: PatternT = "*", **kwargs) -> ResponseT: 

6061 """ 

6062 Return a list of shard_channels that have at least one subscriber 

6063 

6064 For more information, see https://redis.io/commands/pubsub-shardchannels 

6065 """ 

6066 return self.execute_command("PUBSUB SHARDCHANNELS", pattern, **kwargs) 

6067 

6068 def pubsub_numpat(self, **kwargs) -> ResponseT: 

6069 """ 

6070 Returns the number of subscriptions to patterns 

6071 

6072 For more information, see https://redis.io/commands/pubsub-numpat 

6073 """ 

6074 return self.execute_command("PUBSUB NUMPAT", **kwargs) 

6075 

6076 def pubsub_numsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

6077 """ 

6078 Return a list of (channel, number of subscribers) tuples 

6079 for each channel given in ``*args`` 

6080 

6081 For more information, see https://redis.io/commands/pubsub-numsub 

6082 """ 

6083 return self.execute_command("PUBSUB NUMSUB", *args, **kwargs) 

6084 

6085 def pubsub_shardnumsub(self, *args: ChannelT, **kwargs) -> ResponseT: 

6086 """ 

6087 Return a list of (shard_channel, number of subscribers) tuples 

6088 for each channel given in ``*args`` 

6089 

6090 For more information, see https://redis.io/commands/pubsub-shardnumsub 

6091 """ 

6092 return self.execute_command("PUBSUB SHARDNUMSUB", *args, **kwargs) 

6093 

6094 

6095AsyncPubSubCommands = PubSubCommands 

6096 

6097 

6098class ScriptCommands(CommandsProtocol): 

6099 """ 

6100 Redis Lua script commands. see: 

6101 https://redis.io/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/ 

6102 """ 

6103 

6104 def _eval( 

6105 self, 

6106 command: str, 

6107 script: str, 

6108 numkeys: int, 

6109 *keys_and_args: Union[KeyT, EncodableT], 

6110 ) -> Union[Awaitable[str], str]: 

6111 return self.execute_command(command, script, numkeys, *keys_and_args) 

6112 

6113 def eval( 

6114 self, script: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT] 

6115 ) -> Union[Awaitable[str], str]: 

6116 """ 

6117 Execute the Lua ``script``, specifying the ``numkeys`` the script 

6118 will touch and the key names and argument values in ``keys_and_args``. 

6119 Returns the result of the script. 

6120 

6121 In practice, use the object returned by ``register_script``. This 

6122 function exists purely for Redis API completion. 

6123 

6124 For more information, see https://redis.io/commands/eval 

6125 """ 

6126 return self._eval("EVAL", script, numkeys, *keys_and_args) 

6127 

6128 def eval_ro( 

6129 self, script: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT] 

6130 ) -> Union[Awaitable[str], str]: 

6131 """ 

6132 The read-only variant of the EVAL command 

6133 

6134 Execute the read-only Lua ``script`` specifying the ``numkeys`` the script 

6135 will touch and the key names and argument values in ``keys_and_args``. 

6136 Returns the result of the script. 

6137 

6138 For more information, see https://redis.io/commands/eval_ro 

6139 """ 

6140 return self._eval("EVAL_RO", script, numkeys, *keys_and_args) 

6141 

6142 def _evalsha( 

6143 self, 

6144 command: str, 

6145 sha: str, 

6146 numkeys: int, 

6147 *keys_and_args: Union[KeyT, EncodableT], 

6148 ) -> Union[Awaitable[str], str]: 

6149 return self.execute_command(command, sha, numkeys, *keys_and_args) 

6150 

6151 def evalsha( 

6152 self, sha: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT] 

6153 ) -> Union[Awaitable[str], str]: 

6154 """ 

6155 Use the ``sha`` to execute a Lua script already registered via EVAL 

6156 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

6157 key names and argument values in ``keys_and_args``. Returns the result 

6158 of the script. 

6159 

6160 In practice, use the object returned by ``register_script``. This 

6161 function exists purely for Redis API completion. 

6162 

6163 For more information, see https://redis.io/commands/evalsha 

6164 """ 

6165 return self._evalsha("EVALSHA", sha, numkeys, *keys_and_args) 

6166 

6167 def evalsha_ro( 

6168 self, sha: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT] 

6169 ) -> Union[Awaitable[str], str]: 

6170 """ 

6171 The read-only variant of the EVALSHA command 

6172 

6173 Use the ``sha`` to execute a read-only Lua script already registered via EVAL 

6174 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the 

6175 key names and argument values in ``keys_and_args``. Returns the result 

6176 of the script. 

6177 

6178 For more information, see https://redis.io/commands/evalsha_ro 

6179 """ 

6180 return self._evalsha("EVALSHA_RO", sha, numkeys, *keys_and_args) 

6181 

6182 def script_exists(self, *args: str) -> ResponseT: 

6183 """ 

6184 Check if a script exists in the script cache by specifying the SHAs of 

6185 each script as ``args``. Returns a list of boolean values indicating if 

6186 if each already script exists in the cache_data. 

6187 

6188 For more information, see https://redis.io/commands/script-exists 

6189 """ 

6190 return self.execute_command("SCRIPT EXISTS", *args) 

6191 

6192 def script_debug(self, *args) -> None: 

6193 raise NotImplementedError( 

6194 "SCRIPT DEBUG is intentionally not implemented in the client." 

6195 ) 

6196 

6197 def script_flush( 

6198 self, sync_type: Union[Literal["SYNC"], Literal["ASYNC"]] = None 

6199 ) -> ResponseT: 

6200 """Flush all scripts from the script cache_data. 

6201 

6202 ``sync_type`` is by default SYNC (synchronous) but it can also be 

6203 ASYNC. 

6204 

6205 For more information, see https://redis.io/commands/script-flush 

6206 """ 

6207 

6208 # Redis pre 6 had no sync_type. 

6209 if sync_type not in ["SYNC", "ASYNC", None]: 

6210 raise DataError( 

6211 "SCRIPT FLUSH defaults to SYNC in redis > 6.2, or " 

6212 "accepts SYNC/ASYNC. For older versions, " 

6213 "of redis leave as None." 

6214 ) 

6215 if sync_type is None: 

6216 pieces = [] 

6217 else: 

6218 pieces = [sync_type] 

6219 return self.execute_command("SCRIPT FLUSH", *pieces) 

6220 

6221 def script_kill(self) -> ResponseT: 

6222 """ 

6223 Kill the currently executing Lua script 

6224 

6225 For more information, see https://redis.io/commands/script-kill 

6226 """ 

6227 return self.execute_command("SCRIPT KILL") 

6228 

6229 def script_load(self, script: ScriptTextT) -> ResponseT: 

6230 """ 

6231 Load a Lua ``script`` into the script cache_data. Returns the SHA. 

6232 

6233 For more information, see https://redis.io/commands/script-load 

6234 """ 

6235 return self.execute_command("SCRIPT LOAD", script) 

6236 

6237 def register_script(self: "redis.client.Redis", script: ScriptTextT) -> Script: 

6238 """ 

6239 Register a Lua ``script`` specifying the ``keys`` it will touch. 

6240 Returns a Script object that is callable and hides the complexity of 

6241 deal with scripts, keys, and shas. This is the preferred way to work 

6242 with Lua scripts. 

6243 """ 

6244 return Script(self, script) 

6245 

6246 

6247class AsyncScriptCommands(ScriptCommands): 

6248 async def script_debug(self, *args) -> None: 

6249 return super().script_debug() 

6250 

6251 def register_script( 

6252 self: "redis.asyncio.client.Redis", 

6253 script: ScriptTextT, 

6254 ) -> AsyncScript: 

6255 """ 

6256 Register a Lua ``script`` specifying the ``keys`` it will touch. 

6257 Returns a Script object that is callable and hides the complexity of 

6258 deal with scripts, keys, and shas. This is the preferred way to work 

6259 with Lua scripts. 

6260 """ 

6261 return AsyncScript(self, script) 

6262 

6263 

6264class GeoCommands(CommandsProtocol): 

6265 """ 

6266 Redis Geospatial commands. 

6267 see: https://redis.com/redis-best-practices/indexing-patterns/geospatial/ 

6268 """ 

6269 

6270 def geoadd( 

6271 self, 

6272 name: KeyT, 

6273 values: Sequence[EncodableT], 

6274 nx: bool = False, 

6275 xx: bool = False, 

6276 ch: bool = False, 

6277 ) -> ResponseT: 

6278 """ 

6279 Add the specified geospatial items to the specified key identified 

6280 by the ``name`` argument. The Geospatial items are given as ordered 

6281 members of the ``values`` argument, each item or place is formed by 

6282 the triad longitude, latitude and name. 

6283 

6284 Note: You can use ZREM to remove elements. 

6285 

6286 ``nx`` forces ZADD to only create new elements and not to update 

6287 scores for elements that already exist. 

6288 

6289 ``xx`` forces ZADD to only update scores of elements that already 

6290 exist. New elements will not be added. 

6291 

6292 ``ch`` modifies the return value to be the numbers of elements changed. 

6293 Changed elements include new elements that were added and elements 

6294 whose scores changed. 

6295 

6296 For more information, see https://redis.io/commands/geoadd 

6297 """ 

6298 if nx and xx: 

6299 raise DataError("GEOADD allows either 'nx' or 'xx', not both") 

6300 if len(values) % 3 != 0: 

6301 raise DataError("GEOADD requires places with lon, lat and name values") 

6302 pieces = [name] 

6303 if nx: 

6304 pieces.append("NX") 

6305 if xx: 

6306 pieces.append("XX") 

6307 if ch: 

6308 pieces.append("CH") 

6309 pieces.extend(values) 

6310 return self.execute_command("GEOADD", *pieces) 

6311 

6312 def geodist( 

6313 self, name: KeyT, place1: FieldT, place2: FieldT, unit: Optional[str] = None 

6314 ) -> ResponseT: 

6315 """ 

6316 Return the distance between ``place1`` and ``place2`` members of the 

6317 ``name`` key. 

6318 The units must be one of the following : m, km mi, ft. By default 

6319 meters are used. 

6320 

6321 For more information, see https://redis.io/commands/geodist 

6322 """ 

6323 pieces: list[EncodableT] = [name, place1, place2] 

6324 if unit and unit not in ("m", "km", "mi", "ft"): 

6325 raise DataError("GEODIST invalid unit") 

6326 elif unit: 

6327 pieces.append(unit) 

6328 return self.execute_command("GEODIST", *pieces, keys=[name]) 

6329 

6330 def geohash(self, name: KeyT, *values: FieldT) -> ResponseT: 

6331 """ 

6332 Return the geo hash string for each item of ``values`` members of 

6333 the specified key identified by the ``name`` argument. 

6334 

6335 For more information, see https://redis.io/commands/geohash 

6336 """ 

6337 return self.execute_command("GEOHASH", name, *values, keys=[name]) 

6338 

6339 def geopos(self, name: KeyT, *values: FieldT) -> ResponseT: 

6340 """ 

6341 Return the positions of each item of ``values`` as members of 

6342 the specified key identified by the ``name`` argument. Each position 

6343 is represented by the pairs lon and lat. 

6344 

6345 For more information, see https://redis.io/commands/geopos 

6346 """ 

6347 return self.execute_command("GEOPOS", name, *values, keys=[name]) 

6348 

6349 def georadius( 

6350 self, 

6351 name: KeyT, 

6352 longitude: float, 

6353 latitude: float, 

6354 radius: float, 

6355 unit: Optional[str] = None, 

6356 withdist: bool = False, 

6357 withcoord: bool = False, 

6358 withhash: bool = False, 

6359 count: Optional[int] = None, 

6360 sort: Optional[str] = None, 

6361 store: Optional[KeyT] = None, 

6362 store_dist: Optional[KeyT] = None, 

6363 any: bool = False, 

6364 ) -> ResponseT: 

6365 """ 

6366 Return the members of the specified key identified by the 

6367 ``name`` argument which are within the borders of the area specified 

6368 with the ``latitude`` and ``longitude`` location and the maximum 

6369 distance from the center specified by the ``radius`` value. 

6370 

6371 The units must be one of the following : m, km mi, ft. By default 

6372 

6373 ``withdist`` indicates to return the distances of each place. 

6374 

6375 ``withcoord`` indicates to return the latitude and longitude of 

6376 each place. 

6377 

6378 ``withhash`` indicates to return the geohash string of each place. 

6379 

6380 ``count`` indicates to return the number of elements up to N. 

6381 

6382 ``sort`` indicates to return the places in a sorted way, ASC for 

6383 nearest to fairest and DESC for fairest to nearest. 

6384 

6385 ``store`` indicates to save the places names in a sorted set named 

6386 with a specific key, each element of the destination sorted set is 

6387 populated with the score got from the original geo sorted set. 

6388 

6389 ``store_dist`` indicates to save the places names in a sorted set 

6390 named with a specific key, instead of ``store`` the sorted set 

6391 destination score is set with the distance. 

6392 

6393 For more information, see https://redis.io/commands/georadius 

6394 """ 

6395 return self._georadiusgeneric( 

6396 "GEORADIUS", 

6397 name, 

6398 longitude, 

6399 latitude, 

6400 radius, 

6401 unit=unit, 

6402 withdist=withdist, 

6403 withcoord=withcoord, 

6404 withhash=withhash, 

6405 count=count, 

6406 sort=sort, 

6407 store=store, 

6408 store_dist=store_dist, 

6409 any=any, 

6410 ) 

6411 

6412 def georadiusbymember( 

6413 self, 

6414 name: KeyT, 

6415 member: FieldT, 

6416 radius: float, 

6417 unit: Optional[str] = None, 

6418 withdist: bool = False, 

6419 withcoord: bool = False, 

6420 withhash: bool = False, 

6421 count: Optional[int] = None, 

6422 sort: Optional[str] = None, 

6423 store: Union[KeyT, None] = None, 

6424 store_dist: Union[KeyT, None] = None, 

6425 any: bool = False, 

6426 ) -> ResponseT: 

6427 """ 

6428 This command is exactly like ``georadius`` with the sole difference 

6429 that instead of taking, as the center of the area to query, a longitude 

6430 and latitude value, it takes the name of a member already existing 

6431 inside the geospatial index represented by the sorted set. 

6432 

6433 For more information, see https://redis.io/commands/georadiusbymember 

6434 """ 

6435 return self._georadiusgeneric( 

6436 "GEORADIUSBYMEMBER", 

6437 name, 

6438 member, 

6439 radius, 

6440 unit=unit, 

6441 withdist=withdist, 

6442 withcoord=withcoord, 

6443 withhash=withhash, 

6444 count=count, 

6445 sort=sort, 

6446 store=store, 

6447 store_dist=store_dist, 

6448 any=any, 

6449 ) 

6450 

6451 def _georadiusgeneric( 

6452 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

6453 ) -> ResponseT: 

6454 pieces = list(args) 

6455 if kwargs["unit"] and kwargs["unit"] not in ("m", "km", "mi", "ft"): 

6456 raise DataError("GEORADIUS invalid unit") 

6457 elif kwargs["unit"]: 

6458 pieces.append(kwargs["unit"]) 

6459 else: 

6460 pieces.append("m") 

6461 

6462 if kwargs["any"] and kwargs["count"] is None: 

6463 raise DataError("``any`` can't be provided without ``count``") 

6464 

6465 for arg_name, byte_repr in ( 

6466 ("withdist", "WITHDIST"), 

6467 ("withcoord", "WITHCOORD"), 

6468 ("withhash", "WITHHASH"), 

6469 ): 

6470 if kwargs[arg_name]: 

6471 pieces.append(byte_repr) 

6472 

6473 if kwargs["count"] is not None: 

6474 pieces.extend(["COUNT", kwargs["count"]]) 

6475 if kwargs["any"]: 

6476 pieces.append("ANY") 

6477 

6478 if kwargs["sort"]: 

6479 if kwargs["sort"] == "ASC": 

6480 pieces.append("ASC") 

6481 elif kwargs["sort"] == "DESC": 

6482 pieces.append("DESC") 

6483 else: 

6484 raise DataError("GEORADIUS invalid sort") 

6485 

6486 if kwargs["store"] and kwargs["store_dist"]: 

6487 raise DataError("GEORADIUS store and store_dist cant be set together") 

6488 

6489 if kwargs["store"]: 

6490 pieces.extend([b"STORE", kwargs["store"]]) 

6491 

6492 if kwargs["store_dist"]: 

6493 pieces.extend([b"STOREDIST", kwargs["store_dist"]]) 

6494 

6495 return self.execute_command(command, *pieces, **kwargs) 

6496 

6497 def geosearch( 

6498 self, 

6499 name: KeyT, 

6500 member: Union[FieldT, None] = None, 

6501 longitude: Union[float, None] = None, 

6502 latitude: Union[float, None] = None, 

6503 unit: str = "m", 

6504 radius: Union[float, None] = None, 

6505 width: Union[float, None] = None, 

6506 height: Union[float, None] = None, 

6507 sort: Optional[str] = None, 

6508 count: Optional[int] = None, 

6509 any: bool = False, 

6510 withcoord: bool = False, 

6511 withdist: bool = False, 

6512 withhash: bool = False, 

6513 ) -> ResponseT: 

6514 """ 

6515 Return the members of specified key identified by the 

6516 ``name`` argument, which are within the borders of the 

6517 area specified by a given shape. This command extends the 

6518 GEORADIUS command, so in addition to searching within circular 

6519 areas, it supports searching within rectangular areas. 

6520 

6521 This command should be used in place of the deprecated 

6522 GEORADIUS and GEORADIUSBYMEMBER commands. 

6523 

6524 ``member`` Use the position of the given existing 

6525 member in the sorted set. Can't be given with ``longitude`` 

6526 and ``latitude``. 

6527 

6528 ``longitude`` and ``latitude`` Use the position given by 

6529 this coordinates. Can't be given with ``member`` 

6530 ``radius`` Similar to GEORADIUS, search inside circular 

6531 area according the given radius. Can't be given with 

6532 ``height`` and ``width``. 

6533 ``height`` and ``width`` Search inside an axis-aligned 

6534 rectangle, determined by the given height and width. 

6535 Can't be given with ``radius`` 

6536 

6537 ``unit`` must be one of the following : m, km, mi, ft. 

6538 `m` for meters (the default value), `km` for kilometers, 

6539 `mi` for miles and `ft` for feet. 

6540 

6541 ``sort`` indicates to return the places in a sorted way, 

6542 ASC for nearest to furthest and DESC for furthest to nearest. 

6543 

6544 ``count`` limit the results to the first count matching items. 

6545 

6546 ``any`` is set to True, the command will return as soon as 

6547 enough matches are found. Can't be provided without ``count`` 

6548 

6549 ``withdist`` indicates to return the distances of each place. 

6550 ``withcoord`` indicates to return the latitude and longitude of 

6551 each place. 

6552 

6553 ``withhash`` indicates to return the geohash string of each place. 

6554 

6555 For more information, see https://redis.io/commands/geosearch 

6556 """ 

6557 

6558 return self._geosearchgeneric( 

6559 "GEOSEARCH", 

6560 name, 

6561 member=member, 

6562 longitude=longitude, 

6563 latitude=latitude, 

6564 unit=unit, 

6565 radius=radius, 

6566 width=width, 

6567 height=height, 

6568 sort=sort, 

6569 count=count, 

6570 any=any, 

6571 withcoord=withcoord, 

6572 withdist=withdist, 

6573 withhash=withhash, 

6574 store=None, 

6575 store_dist=None, 

6576 ) 

6577 

6578 def geosearchstore( 

6579 self, 

6580 dest: KeyT, 

6581 name: KeyT, 

6582 member: Optional[FieldT] = None, 

6583 longitude: Optional[float] = None, 

6584 latitude: Optional[float] = None, 

6585 unit: str = "m", 

6586 radius: Optional[float] = None, 

6587 width: Optional[float] = None, 

6588 height: Optional[float] = None, 

6589 sort: Optional[str] = None, 

6590 count: Optional[int] = None, 

6591 any: bool = False, 

6592 storedist: bool = False, 

6593 ) -> ResponseT: 

6594 """ 

6595 This command is like GEOSEARCH, but stores the result in 

6596 ``dest``. By default, it stores the results in the destination 

6597 sorted set with their geospatial information. 

6598 if ``store_dist`` set to True, the command will stores the 

6599 items in a sorted set populated with their distance from the 

6600 center of the circle or box, as a floating-point number. 

6601 

6602 For more information, see https://redis.io/commands/geosearchstore 

6603 """ 

6604 return self._geosearchgeneric( 

6605 "GEOSEARCHSTORE", 

6606 dest, 

6607 name, 

6608 member=member, 

6609 longitude=longitude, 

6610 latitude=latitude, 

6611 unit=unit, 

6612 radius=radius, 

6613 width=width, 

6614 height=height, 

6615 sort=sort, 

6616 count=count, 

6617 any=any, 

6618 withcoord=None, 

6619 withdist=None, 

6620 withhash=None, 

6621 store=None, 

6622 store_dist=storedist, 

6623 ) 

6624 

6625 def _geosearchgeneric( 

6626 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None] 

6627 ) -> ResponseT: 

6628 pieces = list(args) 

6629 

6630 # FROMMEMBER or FROMLONLAT 

6631 if kwargs["member"] is None: 

6632 if kwargs["longitude"] is None or kwargs["latitude"] is None: 

6633 raise DataError("GEOSEARCH must have member or longitude and latitude") 

6634 if kwargs["member"]: 

6635 if kwargs["longitude"] or kwargs["latitude"]: 

6636 raise DataError( 

6637 "GEOSEARCH member and longitude or latitude cant be set together" 

6638 ) 

6639 pieces.extend([b"FROMMEMBER", kwargs["member"]]) 

6640 if kwargs["longitude"] is not None and kwargs["latitude"] is not None: 

6641 pieces.extend([b"FROMLONLAT", kwargs["longitude"], kwargs["latitude"]]) 

6642 

6643 # BYRADIUS or BYBOX 

6644 if kwargs["radius"] is None: 

6645 if kwargs["width"] is None or kwargs["height"] is None: 

6646 raise DataError("GEOSEARCH must have radius or width and height") 

6647 if kwargs["unit"] is None: 

6648 raise DataError("GEOSEARCH must have unit") 

6649 if kwargs["unit"].lower() not in ("m", "km", "mi", "ft"): 

6650 raise DataError("GEOSEARCH invalid unit") 

6651 if kwargs["radius"]: 

6652 if kwargs["width"] or kwargs["height"]: 

6653 raise DataError( 

6654 "GEOSEARCH radius and width or height cant be set together" 

6655 ) 

6656 pieces.extend([b"BYRADIUS", kwargs["radius"], kwargs["unit"]]) 

6657 if kwargs["width"] and kwargs["height"]: 

6658 pieces.extend([b"BYBOX", kwargs["width"], kwargs["height"], kwargs["unit"]]) 

6659 

6660 # sort 

6661 if kwargs["sort"]: 

6662 if kwargs["sort"].upper() == "ASC": 

6663 pieces.append(b"ASC") 

6664 elif kwargs["sort"].upper() == "DESC": 

6665 pieces.append(b"DESC") 

6666 else: 

6667 raise DataError("GEOSEARCH invalid sort") 

6668 

6669 # count any 

6670 if kwargs["count"]: 

6671 pieces.extend([b"COUNT", kwargs["count"]]) 

6672 if kwargs["any"]: 

6673 pieces.append(b"ANY") 

6674 elif kwargs["any"]: 

6675 raise DataError("GEOSEARCH ``any`` can't be provided without count") 

6676 

6677 # other properties 

6678 for arg_name, byte_repr in ( 

6679 ("withdist", b"WITHDIST"), 

6680 ("withcoord", b"WITHCOORD"), 

6681 ("withhash", b"WITHHASH"), 

6682 ("store_dist", b"STOREDIST"), 

6683 ): 

6684 if kwargs[arg_name]: 

6685 pieces.append(byte_repr) 

6686 

6687 kwargs["keys"] = [args[0] if command == "GEOSEARCH" else args[1]] 

6688 

6689 return self.execute_command(command, *pieces, **kwargs) 

6690 

6691 

6692AsyncGeoCommands = GeoCommands 

6693 

6694 

6695class ModuleCommands(CommandsProtocol): 

6696 """ 

6697 Redis Module commands. 

6698 see: https://redis.io/topics/modules-intro 

6699 """ 

6700 

6701 def module_load(self, path, *args) -> ResponseT: 

6702 """ 

6703 Loads the module from ``path``. 

6704 Passes all ``*args`` to the module, during loading. 

6705 Raises ``ModuleError`` if a module is not found at ``path``. 

6706 

6707 For more information, see https://redis.io/commands/module-load 

6708 """ 

6709 return self.execute_command("MODULE LOAD", path, *args) 

6710 

6711 def module_loadex( 

6712 self, 

6713 path: str, 

6714 options: Optional[List[str]] = None, 

6715 args: Optional[List[str]] = None, 

6716 ) -> ResponseT: 

6717 """ 

6718 Loads a module from a dynamic library at runtime with configuration directives. 

6719 

6720 For more information, see https://redis.io/commands/module-loadex 

6721 """ 

6722 pieces = [] 

6723 if options is not None: 

6724 pieces.append("CONFIG") 

6725 pieces.extend(options) 

6726 if args is not None: 

6727 pieces.append("ARGS") 

6728 pieces.extend(args) 

6729 

6730 return self.execute_command("MODULE LOADEX", path, *pieces) 

6731 

6732 def module_unload(self, name) -> ResponseT: 

6733 """ 

6734 Unloads the module ``name``. 

6735 Raises ``ModuleError`` if ``name`` is not in loaded modules. 

6736 

6737 For more information, see https://redis.io/commands/module-unload 

6738 """ 

6739 return self.execute_command("MODULE UNLOAD", name) 

6740 

6741 def module_list(self) -> ResponseT: 

6742 """ 

6743 Returns a list of dictionaries containing the name and version of 

6744 all loaded modules. 

6745 

6746 For more information, see https://redis.io/commands/module-list 

6747 """ 

6748 return self.execute_command("MODULE LIST") 

6749 

6750 def command_info(self) -> None: 

6751 raise NotImplementedError( 

6752 "COMMAND INFO is intentionally not implemented in the client." 

6753 ) 

6754 

6755 def command_count(self) -> ResponseT: 

6756 return self.execute_command("COMMAND COUNT") 

6757 

6758 def command_getkeys(self, *args) -> ResponseT: 

6759 return self.execute_command("COMMAND GETKEYS", *args) 

6760 

6761 def command(self) -> ResponseT: 

6762 return self.execute_command("COMMAND") 

6763 

6764 

6765class AsyncModuleCommands(ModuleCommands): 

6766 async def command_info(self) -> None: 

6767 return super().command_info() 

6768 

6769 

6770class ClusterCommands(CommandsProtocol): 

6771 """ 

6772 Class for Redis Cluster commands 

6773 """ 

6774 

6775 def cluster(self, cluster_arg, *args, **kwargs) -> ResponseT: 

6776 return self.execute_command(f"CLUSTER {cluster_arg.upper()}", *args, **kwargs) 

6777 

6778 def readwrite(self, **kwargs) -> ResponseT: 

6779 """ 

6780 Disables read queries for a connection to a Redis Cluster slave node. 

6781 

6782 For more information, see https://redis.io/commands/readwrite 

6783 """ 

6784 return self.execute_command("READWRITE", **kwargs) 

6785 

6786 def readonly(self, **kwargs) -> ResponseT: 

6787 """ 

6788 Enables read queries for a connection to a Redis Cluster replica node. 

6789 

6790 For more information, see https://redis.io/commands/readonly 

6791 """ 

6792 return self.execute_command("READONLY", **kwargs) 

6793 

6794 

6795AsyncClusterCommands = ClusterCommands 

6796 

6797 

6798class FunctionCommands: 

6799 """ 

6800 Redis Function commands 

6801 """ 

6802 

6803 def function_load( 

6804 self, code: str, replace: Optional[bool] = False 

6805 ) -> Union[Awaitable[str], str]: 

6806 """ 

6807 Load a library to Redis. 

6808 :param code: the source code (must start with 

6809 Shebang statement that provides a metadata about the library) 

6810 :param replace: changes the behavior to overwrite the existing library 

6811 with the new contents. 

6812 Return the library name that was loaded. 

6813 

6814 For more information, see https://redis.io/commands/function-load 

6815 """ 

6816 pieces = ["REPLACE"] if replace else [] 

6817 pieces.append(code) 

6818 return self.execute_command("FUNCTION LOAD", *pieces) 

6819 

6820 def function_delete(self, library: str) -> Union[Awaitable[str], str]: 

6821 """ 

6822 Delete the library called ``library`` and all its functions. 

6823 

6824 For more information, see https://redis.io/commands/function-delete 

6825 """ 

6826 return self.execute_command("FUNCTION DELETE", library) 

6827 

6828 def function_flush(self, mode: str = "SYNC") -> Union[Awaitable[str], str]: 

6829 """ 

6830 Deletes all the libraries. 

6831 

6832 For more information, see https://redis.io/commands/function-flush 

6833 """ 

6834 return self.execute_command("FUNCTION FLUSH", mode) 

6835 

6836 def function_list( 

6837 self, library: Optional[str] = "*", withcode: Optional[bool] = False 

6838 ) -> Union[Awaitable[List], List]: 

6839 """ 

6840 Return information about the functions and libraries. 

6841 

6842 Args: 

6843 

6844 library: specify a pattern for matching library names 

6845 withcode: cause the server to include the libraries source implementation 

6846 in the reply 

6847 """ 

6848 args = ["LIBRARYNAME", library] 

6849 if withcode: 

6850 args.append("WITHCODE") 

6851 return self.execute_command("FUNCTION LIST", *args) 

6852 

6853 def _fcall( 

6854 self, command: str, function, numkeys: int, *keys_and_args: Any 

6855 ) -> Union[Awaitable[str], str]: 

6856 return self.execute_command(command, function, numkeys, *keys_and_args) 

6857 

6858 def fcall( 

6859 self, function, numkeys: int, *keys_and_args: Any 

6860 ) -> Union[Awaitable[str], str]: 

6861 """ 

6862 Invoke a function. 

6863 

6864 For more information, see https://redis.io/commands/fcall 

6865 """ 

6866 return self._fcall("FCALL", function, numkeys, *keys_and_args) 

6867 

6868 def fcall_ro( 

6869 self, function, numkeys: int, *keys_and_args: Any 

6870 ) -> Union[Awaitable[str], str]: 

6871 """ 

6872 This is a read-only variant of the FCALL command that cannot 

6873 execute commands that modify data. 

6874 

6875 For more information, see https://redis.io/commands/fcall_ro 

6876 """ 

6877 return self._fcall("FCALL_RO", function, numkeys, *keys_and_args) 

6878 

6879 def function_dump(self) -> Union[Awaitable[str], str]: 

6880 """ 

6881 Return the serialized payload of loaded libraries. 

6882 

6883 For more information, see https://redis.io/commands/function-dump 

6884 """ 

6885 from redis.client import NEVER_DECODE 

6886 

6887 options = {} 

6888 options[NEVER_DECODE] = [] 

6889 

6890 return self.execute_command("FUNCTION DUMP", **options) 

6891 

6892 def function_restore( 

6893 self, payload: str, policy: Optional[str] = "APPEND" 

6894 ) -> Union[Awaitable[str], str]: 

6895 """ 

6896 Restore libraries from the serialized ``payload``. 

6897 You can use the optional policy argument to provide a policy 

6898 for handling existing libraries. 

6899 

6900 For more information, see https://redis.io/commands/function-restore 

6901 """ 

6902 return self.execute_command("FUNCTION RESTORE", payload, policy) 

6903 

6904 def function_kill(self) -> Union[Awaitable[str], str]: 

6905 """ 

6906 Kill a function that is currently executing. 

6907 

6908 For more information, see https://redis.io/commands/function-kill 

6909 """ 

6910 return self.execute_command("FUNCTION KILL") 

6911 

6912 def function_stats(self) -> Union[Awaitable[List], List]: 

6913 """ 

6914 Return information about the function that's currently running 

6915 and information about the available execution engines. 

6916 

6917 For more information, see https://redis.io/commands/function-stats 

6918 """ 

6919 return self.execute_command("FUNCTION STATS") 

6920 

6921 

6922AsyncFunctionCommands = FunctionCommands 

6923 

6924 

6925class DataAccessCommands( 

6926 BasicKeyCommands, 

6927 HyperlogCommands, 

6928 HashCommands, 

6929 GeoCommands, 

6930 ListCommands, 

6931 ScanCommands, 

6932 SetCommands, 

6933 StreamCommands, 

6934 SortedSetCommands, 

6935): 

6936 """ 

6937 A class containing all of the implemented data access redis commands. 

6938 This class is to be used as a mixin for synchronous Redis clients. 

6939 """ 

6940 

6941 

6942class AsyncDataAccessCommands( 

6943 AsyncBasicKeyCommands, 

6944 AsyncHyperlogCommands, 

6945 AsyncHashCommands, 

6946 AsyncGeoCommands, 

6947 AsyncListCommands, 

6948 AsyncScanCommands, 

6949 AsyncSetCommands, 

6950 AsyncStreamCommands, 

6951 AsyncSortedSetCommands, 

6952): 

6953 """ 

6954 A class containing all of the implemented data access redis commands. 

6955 This class is to be used as a mixin for asynchronous Redis clients. 

6956 """ 

6957 

6958 

6959class CoreCommands( 

6960 ACLCommands, 

6961 ClusterCommands, 

6962 DataAccessCommands, 

6963 ManagementCommands, 

6964 ModuleCommands, 

6965 PubSubCommands, 

6966 ScriptCommands, 

6967 FunctionCommands, 

6968): 

6969 """ 

6970 A class containing all of the implemented redis commands. This class is 

6971 to be used as a mixin for synchronous Redis clients. 

6972 """ 

6973 

6974 

6975class AsyncCoreCommands( 

6976 AsyncACLCommands, 

6977 AsyncClusterCommands, 

6978 AsyncDataAccessCommands, 

6979 AsyncManagementCommands, 

6980 AsyncModuleCommands, 

6981 AsyncPubSubCommands, 

6982 AsyncScriptCommands, 

6983 AsyncFunctionCommands, 

6984): 

6985 """ 

6986 A class containing all of the implemented redis commands. This class is 

6987 to be used as a mixin for asynchronous Redis clients. 

6988 """