1# from __future__ import annotations
2
3import datetime
4import hashlib
5import warnings
6from enum import Enum
7from typing import (
8 TYPE_CHECKING,
9 Any,
10 AsyncIterator,
11 Awaitable,
12 Callable,
13 Dict,
14 Iterable,
15 Iterator,
16 List,
17 Literal,
18 Mapping,
19 Optional,
20 Sequence,
21 Set,
22 Tuple,
23 Union,
24)
25
26from redis.exceptions import ConnectionError, DataError, NoScriptError, RedisError
27from redis.typing import (
28 AbsExpiryT,
29 AnyKeyT,
30 BitfieldOffsetT,
31 ChannelT,
32 CommandsProtocol,
33 ConsumerT,
34 EncodableT,
35 ExpiryT,
36 FieldT,
37 GroupT,
38 KeysT,
39 KeyT,
40 Number,
41 PatternT,
42 ResponseT,
43 ScriptTextT,
44 StreamIdT,
45 TimeoutSecT,
46 ZScoreBoundT,
47)
48from redis.utils import (
49 deprecated_function,
50 extract_expire_flags,
51)
52
53from .helpers import list_or_args
54
55if TYPE_CHECKING:
56 import redis.asyncio.client
57 import redis.client
58
59
60class ACLCommands(CommandsProtocol):
61 """
62 Redis Access Control List (ACL) commands.
63 see: https://redis.io/topics/acl
64 """
65
66 def acl_cat(self, category: Optional[str] = None, **kwargs) -> ResponseT:
67 """
68 Returns a list of categories or commands within a category.
69
70 If ``category`` is not supplied, returns a list of all categories.
71 If ``category`` is supplied, returns a list of all commands within
72 that category.
73
74 For more information, see https://redis.io/commands/acl-cat
75 """
76 pieces: list[EncodableT] = [category] if category else []
77 return self.execute_command("ACL CAT", *pieces, **kwargs)
78
79 def acl_dryrun(self, username, *args, **kwargs):
80 """
81 Simulate the execution of a given command by a given ``username``.
82
83 For more information, see https://redis.io/commands/acl-dryrun
84 """
85 return self.execute_command("ACL DRYRUN", username, *args, **kwargs)
86
87 def acl_deluser(self, *username: str, **kwargs) -> ResponseT:
88 """
89 Delete the ACL for the specified ``username``\\s
90
91 For more information, see https://redis.io/commands/acl-deluser
92 """
93 return self.execute_command("ACL DELUSER", *username, **kwargs)
94
95 def acl_genpass(self, bits: Optional[int] = None, **kwargs) -> ResponseT:
96 """Generate a random password value.
97 If ``bits`` is supplied then use this number of bits, rounded to
98 the next multiple of 4.
99 See: https://redis.io/commands/acl-genpass
100 """
101 pieces = []
102 if bits is not None:
103 try:
104 b = int(bits)
105 if b < 0 or b > 4096:
106 raise ValueError
107 pieces.append(b)
108 except ValueError:
109 raise DataError(
110 "genpass optionally accepts a bits argument, between 0 and 4096."
111 )
112 return self.execute_command("ACL GENPASS", *pieces, **kwargs)
113
114 def acl_getuser(self, username: str, **kwargs) -> ResponseT:
115 """
116 Get the ACL details for the specified ``username``.
117
118 If ``username`` does not exist, return None
119
120 For more information, see https://redis.io/commands/acl-getuser
121 """
122 return self.execute_command("ACL GETUSER", username, **kwargs)
123
124 def acl_help(self, **kwargs) -> ResponseT:
125 """The ACL HELP command returns helpful text describing
126 the different subcommands.
127
128 For more information, see https://redis.io/commands/acl-help
129 """
130 return self.execute_command("ACL HELP", **kwargs)
131
132 def acl_list(self, **kwargs) -> ResponseT:
133 """
134 Return a list of all ACLs on the server
135
136 For more information, see https://redis.io/commands/acl-list
137 """
138 return self.execute_command("ACL LIST", **kwargs)
139
140 def acl_log(self, count: Optional[int] = None, **kwargs) -> ResponseT:
141 """
142 Get ACL logs as a list.
143 :param int count: Get logs[0:count].
144 :rtype: List.
145
146 For more information, see https://redis.io/commands/acl-log
147 """
148 args = []
149 if count is not None:
150 if not isinstance(count, int):
151 raise DataError("ACL LOG count must be an integer")
152 args.append(count)
153
154 return self.execute_command("ACL LOG", *args, **kwargs)
155
156 def acl_log_reset(self, **kwargs) -> ResponseT:
157 """
158 Reset ACL logs.
159 :rtype: Boolean.
160
161 For more information, see https://redis.io/commands/acl-log
162 """
163 args = [b"RESET"]
164 return self.execute_command("ACL LOG", *args, **kwargs)
165
166 def acl_load(self, **kwargs) -> ResponseT:
167 """
168 Load ACL rules from the configured ``aclfile``.
169
170 Note that the server must be configured with the ``aclfile``
171 directive to be able to load ACL rules from an aclfile.
172
173 For more information, see https://redis.io/commands/acl-load
174 """
175 return self.execute_command("ACL LOAD", **kwargs)
176
177 def acl_save(self, **kwargs) -> ResponseT:
178 """
179 Save ACL rules to the configured ``aclfile``.
180
181 Note that the server must be configured with the ``aclfile``
182 directive to be able to save ACL rules to an aclfile.
183
184 For more information, see https://redis.io/commands/acl-save
185 """
186 return self.execute_command("ACL SAVE", **kwargs)
187
188 def acl_setuser(
189 self,
190 username: str,
191 enabled: bool = False,
192 nopass: bool = False,
193 passwords: Optional[Union[str, Iterable[str]]] = None,
194 hashed_passwords: Optional[Union[str, Iterable[str]]] = None,
195 categories: Optional[Iterable[str]] = None,
196 commands: Optional[Iterable[str]] = None,
197 keys: Optional[Iterable[KeyT]] = None,
198 channels: Optional[Iterable[ChannelT]] = None,
199 selectors: Optional[Iterable[Tuple[str, KeyT]]] = None,
200 reset: bool = False,
201 reset_keys: bool = False,
202 reset_channels: bool = False,
203 reset_passwords: bool = False,
204 **kwargs,
205 ) -> ResponseT:
206 """
207 Create or update an ACL user.
208
209 Create or update the ACL for `username`. If the user already exists,
210 the existing ACL is completely overwritten and replaced with the
211 specified values.
212
213 For more information, see https://redis.io/commands/acl-setuser
214
215 Args:
216 username: The name of the user whose ACL is to be created or updated.
217 enabled: Indicates whether the user should be allowed to authenticate.
218 Defaults to `False`.
219 nopass: Indicates whether the user can authenticate without a password.
220 This cannot be `True` if `passwords` are also specified.
221 passwords: A list of plain text passwords to add to or remove from the user.
222 Each password must be prefixed with a '+' to add or a '-' to
223 remove. For convenience, a single prefixed string can be used
224 when adding or removing a single password.
225 hashed_passwords: A list of SHA-256 hashed passwords to add to or remove
226 from the user. Each hashed password must be prefixed with
227 a '+' to add or a '-' to remove. For convenience, a single
228 prefixed string can be used when adding or removing a
229 single password.
230 categories: A list of strings representing category permissions. Each string
231 must be prefixed with either a '+' to add the category
232 permission or a '-' to remove the category permission.
233 commands: A list of strings representing command permissions. Each string
234 must be prefixed with either a '+' to add the command permission
235 or a '-' to remove the command permission.
236 keys: A list of key patterns to grant the user access to. Key patterns allow
237 ``'*'`` to support wildcard matching. For example, ``'*'`` grants
238 access to all keys while ``'cache:*'`` grants access to all keys that
239 are prefixed with ``cache:``.
240 `keys` should not be prefixed with a ``'~'``.
241 reset: Indicates whether the user should be fully reset prior to applying
242 the new ACL. Setting this to `True` will remove all existing
243 passwords, flags, and privileges from the user and then apply the
244 specified rules. If `False`, the user's existing passwords, flags,
245 and privileges will be kept and any new specified rules will be
246 applied on top.
247 reset_keys: Indicates whether the user's key permissions should be reset
248 prior to applying any new key permissions specified in `keys`.
249 If `False`, the user's existing key permissions will be kept and
250 any new specified key permissions will be applied on top.
251 reset_channels: Indicates whether the user's channel permissions should be
252 reset prior to applying any new channel permissions
253 specified in `channels`. If `False`, the user's existing
254 channel permissions will be kept and any new specified
255 channel permissions will be applied on top.
256 reset_passwords: Indicates whether to remove all existing passwords and the
257 `nopass` flag from the user prior to applying any new
258 passwords specified in `passwords` or `hashed_passwords`.
259 If `False`, the user's existing passwords and `nopass`
260 status will be kept and any new specified passwords or
261 hashed passwords will be applied on top.
262 """
263 encoder = self.get_encoder()
264 pieces: List[EncodableT] = [username]
265
266 if reset:
267 pieces.append(b"reset")
268
269 if reset_keys:
270 pieces.append(b"resetkeys")
271
272 if reset_channels:
273 pieces.append(b"resetchannels")
274
275 if reset_passwords:
276 pieces.append(b"resetpass")
277
278 if enabled:
279 pieces.append(b"on")
280 else:
281 pieces.append(b"off")
282
283 if (passwords or hashed_passwords) and nopass:
284 raise DataError(
285 "Cannot set 'nopass' and supply 'passwords' or 'hashed_passwords'"
286 )
287
288 if passwords:
289 # as most users will have only one password, allow remove_passwords
290 # to be specified as a simple string or a list
291 passwords = list_or_args(passwords, [])
292 for i, password in enumerate(passwords):
293 password = encoder.encode(password)
294 if password.startswith(b"+"):
295 pieces.append(b">%s" % password[1:])
296 elif password.startswith(b"-"):
297 pieces.append(b"<%s" % password[1:])
298 else:
299 raise DataError(
300 f"Password {i} must be prefixed with a "
301 f'"+" to add or a "-" to remove'
302 )
303
304 if hashed_passwords:
305 # as most users will have only one password, allow remove_passwords
306 # to be specified as a simple string or a list
307 hashed_passwords = list_or_args(hashed_passwords, [])
308 for i, hashed_password in enumerate(hashed_passwords):
309 hashed_password = encoder.encode(hashed_password)
310 if hashed_password.startswith(b"+"):
311 pieces.append(b"#%s" % hashed_password[1:])
312 elif hashed_password.startswith(b"-"):
313 pieces.append(b"!%s" % hashed_password[1:])
314 else:
315 raise DataError(
316 f"Hashed password {i} must be prefixed with a "
317 f'"+" to add or a "-" to remove'
318 )
319
320 if nopass:
321 pieces.append(b"nopass")
322
323 if categories:
324 for category in categories:
325 category = encoder.encode(category)
326 # categories can be prefixed with one of (+@, +, -@, -)
327 if category.startswith(b"+@"):
328 pieces.append(category)
329 elif category.startswith(b"+"):
330 pieces.append(b"+@%s" % category[1:])
331 elif category.startswith(b"-@"):
332 pieces.append(category)
333 elif category.startswith(b"-"):
334 pieces.append(b"-@%s" % category[1:])
335 else:
336 raise DataError(
337 f'Category "{encoder.decode(category, force=True)}" '
338 'must be prefixed with "+" or "-"'
339 )
340 if commands:
341 for cmd in commands:
342 cmd = encoder.encode(cmd)
343 if not cmd.startswith(b"+") and not cmd.startswith(b"-"):
344 raise DataError(
345 f'Command "{encoder.decode(cmd, force=True)}" '
346 'must be prefixed with "+" or "-"'
347 )
348 pieces.append(cmd)
349
350 if keys:
351 for key in keys:
352 key = encoder.encode(key)
353 if not key.startswith(b"%") and not key.startswith(b"~"):
354 key = b"~%s" % key
355 pieces.append(key)
356
357 if channels:
358 for channel in channels:
359 channel = encoder.encode(channel)
360 pieces.append(b"&%s" % channel)
361
362 if selectors:
363 for cmd, key in selectors:
364 cmd = encoder.encode(cmd)
365 if not cmd.startswith(b"+") and not cmd.startswith(b"-"):
366 raise DataError(
367 f'Command "{encoder.decode(cmd, force=True)}" '
368 'must be prefixed with "+" or "-"'
369 )
370
371 key = encoder.encode(key)
372 if not key.startswith(b"%") and not key.startswith(b"~"):
373 key = b"~%s" % key
374
375 pieces.append(b"(%s %s)" % (cmd, key))
376
377 return self.execute_command("ACL SETUSER", *pieces, **kwargs)
378
379 def acl_users(self, **kwargs) -> ResponseT:
380 """Returns a list of all registered users on the server.
381
382 For more information, see https://redis.io/commands/acl-users
383 """
384 return self.execute_command("ACL USERS", **kwargs)
385
386 def acl_whoami(self, **kwargs) -> ResponseT:
387 """Get the username for the current connection
388
389 For more information, see https://redis.io/commands/acl-whoami
390 """
391 return self.execute_command("ACL WHOAMI", **kwargs)
392
393
394AsyncACLCommands = ACLCommands
395
396
397class ManagementCommands(CommandsProtocol):
398 """
399 Redis management commands
400 """
401
402 def auth(self, password: str, username: Optional[str] = None, **kwargs):
403 """
404 Authenticates the user. If you do not pass username, Redis will try to
405 authenticate for the "default" user. If you do pass username, it will
406 authenticate for the given user.
407 For more information, see https://redis.io/commands/auth
408 """
409 pieces = []
410 if username is not None:
411 pieces.append(username)
412 pieces.append(password)
413 return self.execute_command("AUTH", *pieces, **kwargs)
414
415 def bgrewriteaof(self, **kwargs):
416 """Tell the Redis server to rewrite the AOF file from data in memory.
417
418 For more information, see https://redis.io/commands/bgrewriteaof
419 """
420 return self.execute_command("BGREWRITEAOF", **kwargs)
421
422 def bgsave(self, schedule: bool = True, **kwargs) -> ResponseT:
423 """
424 Tell the Redis server to save its data to disk. Unlike save(),
425 this method is asynchronous and returns immediately.
426
427 For more information, see https://redis.io/commands/bgsave
428 """
429 pieces = []
430 if schedule:
431 pieces.append("SCHEDULE")
432 return self.execute_command("BGSAVE", *pieces, **kwargs)
433
434 def role(self) -> ResponseT:
435 """
436 Provide information on the role of a Redis instance in
437 the context of replication, by returning if the instance
438 is currently a master, slave, or sentinel.
439
440 For more information, see https://redis.io/commands/role
441 """
442 return self.execute_command("ROLE")
443
444 def client_kill(self, address: str, **kwargs) -> ResponseT:
445 """Disconnects the client at ``address`` (ip:port)
446
447 For more information, see https://redis.io/commands/client-kill
448 """
449 return self.execute_command("CLIENT KILL", address, **kwargs)
450
451 def client_kill_filter(
452 self,
453 _id: Optional[str] = None,
454 _type: Optional[str] = None,
455 addr: Optional[str] = None,
456 skipme: Optional[bool] = None,
457 laddr: Optional[bool] = None,
458 user: Optional[str] = None,
459 maxage: Optional[int] = None,
460 **kwargs,
461 ) -> ResponseT:
462 """
463 Disconnects client(s) using a variety of filter options
464 :param _id: Kills a client by its unique ID field
465 :param _type: Kills a client by type where type is one of 'normal',
466 'master', 'slave' or 'pubsub'
467 :param addr: Kills a client by its 'address:port'
468 :param skipme: If True, then the client calling the command
469 will not get killed even if it is identified by one of the filter
470 options. If skipme is not provided, the server defaults to skipme=True
471 :param laddr: Kills a client by its 'local (bind) address:port'
472 :param user: Kills a client for a specific user name
473 :param maxage: Kills clients that are older than the specified age in seconds
474 """
475 args = []
476 if _type is not None:
477 client_types = ("normal", "master", "slave", "pubsub")
478 if str(_type).lower() not in client_types:
479 raise DataError(f"CLIENT KILL type must be one of {client_types!r}")
480 args.extend((b"TYPE", _type))
481 if skipme is not None:
482 if not isinstance(skipme, bool):
483 raise DataError("CLIENT KILL skipme must be a bool")
484 if skipme:
485 args.extend((b"SKIPME", b"YES"))
486 else:
487 args.extend((b"SKIPME", b"NO"))
488 if _id is not None:
489 args.extend((b"ID", _id))
490 if addr is not None:
491 args.extend((b"ADDR", addr))
492 if laddr is not None:
493 args.extend((b"LADDR", laddr))
494 if user is not None:
495 args.extend((b"USER", user))
496 if maxage is not None:
497 args.extend((b"MAXAGE", maxage))
498 if not args:
499 raise DataError(
500 "CLIENT KILL <filter> <value> ... ... <filter> "
501 "<value> must specify at least one filter"
502 )
503 return self.execute_command("CLIENT KILL", *args, **kwargs)
504
505 def client_info(self, **kwargs) -> ResponseT:
506 """
507 Returns information and statistics about the current
508 client connection.
509
510 For more information, see https://redis.io/commands/client-info
511 """
512 return self.execute_command("CLIENT INFO", **kwargs)
513
514 def client_list(
515 self, _type: Optional[str] = None, client_id: List[EncodableT] = [], **kwargs
516 ) -> ResponseT:
517 """
518 Returns a list of currently connected clients.
519 If type of client specified, only that type will be returned.
520
521 :param _type: optional. one of the client types (normal, master,
522 replica, pubsub)
523 :param client_id: optional. a list of client ids
524
525 For more information, see https://redis.io/commands/client-list
526 """
527 args = []
528 if _type is not None:
529 client_types = ("normal", "master", "replica", "pubsub")
530 if str(_type).lower() not in client_types:
531 raise DataError(f"CLIENT LIST _type must be one of {client_types!r}")
532 args.append(b"TYPE")
533 args.append(_type)
534 if not isinstance(client_id, list):
535 raise DataError("client_id must be a list")
536 if client_id:
537 args.append(b"ID")
538 args += client_id
539 return self.execute_command("CLIENT LIST", *args, **kwargs)
540
541 def client_getname(self, **kwargs) -> ResponseT:
542 """
543 Returns the current connection name
544
545 For more information, see https://redis.io/commands/client-getname
546 """
547 return self.execute_command("CLIENT GETNAME", **kwargs)
548
549 def client_getredir(self, **kwargs) -> ResponseT:
550 """
551 Returns the ID (an integer) of the client to whom we are
552 redirecting tracking notifications.
553
554 see: https://redis.io/commands/client-getredir
555 """
556 return self.execute_command("CLIENT GETREDIR", **kwargs)
557
558 def client_reply(
559 self, reply: Union[Literal["ON"], Literal["OFF"], Literal["SKIP"]], **kwargs
560 ) -> ResponseT:
561 """
562 Enable and disable redis server replies.
563
564 ``reply`` Must be ON OFF or SKIP,
565 ON - The default most with server replies to commands
566 OFF - Disable server responses to commands
567 SKIP - Skip the response of the immediately following command.
568
569 Note: When setting OFF or SKIP replies, you will need a client object
570 with a timeout specified in seconds, and will need to catch the
571 TimeoutError.
572 The test_client_reply unit test illustrates this, and
573 conftest.py has a client with a timeout.
574
575 See https://redis.io/commands/client-reply
576 """
577 replies = ["ON", "OFF", "SKIP"]
578 if reply not in replies:
579 raise DataError(f"CLIENT REPLY must be one of {replies!r}")
580 return self.execute_command("CLIENT REPLY", reply, **kwargs)
581
582 def client_id(self, **kwargs) -> ResponseT:
583 """
584 Returns the current connection id
585
586 For more information, see https://redis.io/commands/client-id
587 """
588 return self.execute_command("CLIENT ID", **kwargs)
589
590 def client_tracking_on(
591 self,
592 clientid: Optional[int] = None,
593 prefix: Sequence[KeyT] = [],
594 bcast: bool = False,
595 optin: bool = False,
596 optout: bool = False,
597 noloop: bool = False,
598 ) -> ResponseT:
599 """
600 Turn on the tracking mode.
601 For more information, about the options look at client_tracking func.
602
603 See https://redis.io/commands/client-tracking
604 """
605 return self.client_tracking(
606 True, clientid, prefix, bcast, optin, optout, noloop
607 )
608
609 def client_tracking_off(
610 self,
611 clientid: Optional[int] = None,
612 prefix: Sequence[KeyT] = [],
613 bcast: bool = False,
614 optin: bool = False,
615 optout: bool = False,
616 noloop: bool = False,
617 ) -> ResponseT:
618 """
619 Turn off the tracking mode.
620 For more information, about the options look at client_tracking func.
621
622 See https://redis.io/commands/client-tracking
623 """
624 return self.client_tracking(
625 False, clientid, prefix, bcast, optin, optout, noloop
626 )
627
628 def client_tracking(
629 self,
630 on: bool = True,
631 clientid: Optional[int] = None,
632 prefix: Sequence[KeyT] = [],
633 bcast: bool = False,
634 optin: bool = False,
635 optout: bool = False,
636 noloop: bool = False,
637 **kwargs,
638 ) -> ResponseT:
639 """
640 Enables the tracking feature of the Redis server, that is used
641 for server assisted client side caching.
642
643 ``on`` indicate for tracking on or tracking off. The default is on.
644
645 ``clientid`` send invalidation messages to the connection with
646 the specified ID.
647
648 ``bcast`` enable tracking in broadcasting mode. In this mode
649 invalidation messages are reported for all the prefixes
650 specified, regardless of the keys requested by the connection.
651
652 ``optin`` when broadcasting is NOT active, normally don't track
653 keys in read only commands, unless they are called immediately
654 after a CLIENT CACHING yes command.
655
656 ``optout`` when broadcasting is NOT active, normally track keys in
657 read only commands, unless they are called immediately after a
658 CLIENT CACHING no command.
659
660 ``noloop`` don't send notifications about keys modified by this
661 connection itself.
662
663 ``prefix`` for broadcasting, register a given key prefix, so that
664 notifications will be provided only for keys starting with this string.
665
666 See https://redis.io/commands/client-tracking
667 """
668
669 if len(prefix) != 0 and bcast is False:
670 raise DataError("Prefix can only be used with bcast")
671
672 pieces = ["ON"] if on else ["OFF"]
673 if clientid is not None:
674 pieces.extend(["REDIRECT", clientid])
675 for p in prefix:
676 pieces.extend(["PREFIX", p])
677 if bcast:
678 pieces.append("BCAST")
679 if optin:
680 pieces.append("OPTIN")
681 if optout:
682 pieces.append("OPTOUT")
683 if noloop:
684 pieces.append("NOLOOP")
685
686 return self.execute_command("CLIENT TRACKING", *pieces)
687
688 def client_trackinginfo(self, **kwargs) -> ResponseT:
689 """
690 Returns the information about the current client connection's
691 use of the server assisted client side cache.
692
693 See https://redis.io/commands/client-trackinginfo
694 """
695 return self.execute_command("CLIENT TRACKINGINFO", **kwargs)
696
697 def client_setname(self, name: str, **kwargs) -> ResponseT:
698 """
699 Sets the current connection name
700
701 For more information, see https://redis.io/commands/client-setname
702
703 .. note::
704 This method sets client name only for **current** connection.
705
706 If you want to set a common name for all connections managed
707 by this client, use ``client_name`` constructor argument.
708 """
709 return self.execute_command("CLIENT SETNAME", name, **kwargs)
710
711 def client_setinfo(self, attr: str, value: str, **kwargs) -> ResponseT:
712 """
713 Sets the current connection library name or version
714 For mor information see https://redis.io/commands/client-setinfo
715 """
716 return self.execute_command("CLIENT SETINFO", attr, value, **kwargs)
717
718 def client_unblock(
719 self, client_id: int, error: bool = False, **kwargs
720 ) -> ResponseT:
721 """
722 Unblocks a connection by its client id.
723 If ``error`` is True, unblocks the client with a special error message.
724 If ``error`` is False (default), the client is unblocked using the
725 regular timeout mechanism.
726
727 For more information, see https://redis.io/commands/client-unblock
728 """
729 args = ["CLIENT UNBLOCK", int(client_id)]
730 if error:
731 args.append(b"ERROR")
732 return self.execute_command(*args, **kwargs)
733
734 def client_pause(self, timeout: int, all: bool = True, **kwargs) -> ResponseT:
735 """
736 Suspend all the Redis clients for the specified amount of time.
737
738
739 For more information, see https://redis.io/commands/client-pause
740
741 Args:
742 timeout: milliseconds to pause clients
743 all: If true (default) all client commands are blocked.
744 otherwise, clients are only blocked if they attempt to execute
745 a write command.
746
747 For the WRITE mode, some commands have special behavior:
748
749 * EVAL/EVALSHA: Will block client for all scripts.
750 * PUBLISH: Will block client.
751 * PFCOUNT: Will block client.
752 * WAIT: Acknowledgments will be delayed, so this command will
753 appear blocked.
754 """
755 args = ["CLIENT PAUSE", str(timeout)]
756 if not isinstance(timeout, int):
757 raise DataError("CLIENT PAUSE timeout must be an integer")
758 if not all:
759 args.append("WRITE")
760 return self.execute_command(*args, **kwargs)
761
762 def client_unpause(self, **kwargs) -> ResponseT:
763 """
764 Unpause all redis clients
765
766 For more information, see https://redis.io/commands/client-unpause
767 """
768 return self.execute_command("CLIENT UNPAUSE", **kwargs)
769
770 def client_no_evict(self, mode: str) -> Union[Awaitable[str], str]:
771 """
772 Sets the client eviction mode for the current connection.
773
774 For more information, see https://redis.io/commands/client-no-evict
775 """
776 return self.execute_command("CLIENT NO-EVICT", mode)
777
778 def client_no_touch(self, mode: str) -> Union[Awaitable[str], str]:
779 """
780 # The command controls whether commands sent by the client will alter
781 # the LRU/LFU of the keys they access.
782 # When turned on, the current client will not change LFU/LRU stats,
783 # unless it sends the TOUCH command.
784
785 For more information, see https://redis.io/commands/client-no-touch
786 """
787 return self.execute_command("CLIENT NO-TOUCH", mode)
788
789 def command(self, **kwargs):
790 """
791 Returns dict reply of details about all Redis commands.
792
793 For more information, see https://redis.io/commands/command
794 """
795 return self.execute_command("COMMAND", **kwargs)
796
797 def command_info(self, **kwargs) -> None:
798 raise NotImplementedError(
799 "COMMAND INFO is intentionally not implemented in the client."
800 )
801
802 def command_count(self, **kwargs) -> ResponseT:
803 return self.execute_command("COMMAND COUNT", **kwargs)
804
805 def command_list(
806 self,
807 module: Optional[str] = None,
808 category: Optional[str] = None,
809 pattern: Optional[str] = None,
810 ) -> ResponseT:
811 """
812 Return an array of the server's command names.
813 You can use one of the following filters:
814 ``module``: get the commands that belong to the module
815 ``category``: get the commands in the ACL category
816 ``pattern``: get the commands that match the given pattern
817
818 For more information, see https://redis.io/commands/command-list/
819 """
820 pieces = []
821 if module is not None:
822 pieces.extend(["MODULE", module])
823 if category is not None:
824 pieces.extend(["ACLCAT", category])
825 if pattern is not None:
826 pieces.extend(["PATTERN", pattern])
827
828 if pieces:
829 pieces.insert(0, "FILTERBY")
830
831 return self.execute_command("COMMAND LIST", *pieces)
832
833 def command_getkeysandflags(self, *args: List[str]) -> List[Union[str, List[str]]]:
834 """
835 Returns array of keys from a full Redis command and their usage flags.
836
837 For more information, see https://redis.io/commands/command-getkeysandflags
838 """
839 return self.execute_command("COMMAND GETKEYSANDFLAGS", *args)
840
841 def command_docs(self, *args):
842 """
843 This function throws a NotImplementedError since it is intentionally
844 not supported.
845 """
846 raise NotImplementedError(
847 "COMMAND DOCS is intentionally not implemented in the client."
848 )
849
850 def config_get(
851 self, pattern: PatternT = "*", *args: List[PatternT], **kwargs
852 ) -> ResponseT:
853 """
854 Return a dictionary of configuration based on the ``pattern``
855
856 For more information, see https://redis.io/commands/config-get
857 """
858 return self.execute_command("CONFIG GET", pattern, *args, **kwargs)
859
860 def config_set(
861 self,
862 name: KeyT,
863 value: EncodableT,
864 *args: List[Union[KeyT, EncodableT]],
865 **kwargs,
866 ) -> ResponseT:
867 """Set config item ``name`` with ``value``
868
869 For more information, see https://redis.io/commands/config-set
870 """
871 return self.execute_command("CONFIG SET", name, value, *args, **kwargs)
872
873 def config_resetstat(self, **kwargs) -> ResponseT:
874 """
875 Reset runtime statistics
876
877 For more information, see https://redis.io/commands/config-resetstat
878 """
879 return self.execute_command("CONFIG RESETSTAT", **kwargs)
880
881 def config_rewrite(self, **kwargs) -> ResponseT:
882 """
883 Rewrite config file with the minimal change to reflect running config.
884
885 For more information, see https://redis.io/commands/config-rewrite
886 """
887 return self.execute_command("CONFIG REWRITE", **kwargs)
888
889 def dbsize(self, **kwargs) -> ResponseT:
890 """
891 Returns the number of keys in the current database
892
893 For more information, see https://redis.io/commands/dbsize
894 """
895 return self.execute_command("DBSIZE", **kwargs)
896
897 def debug_object(self, key: KeyT, **kwargs) -> ResponseT:
898 """
899 Returns version specific meta information about a given key
900
901 For more information, see https://redis.io/commands/debug-object
902 """
903 return self.execute_command("DEBUG OBJECT", key, **kwargs)
904
905 def debug_segfault(self, **kwargs) -> None:
906 raise NotImplementedError(
907 """
908 DEBUG SEGFAULT is intentionally not implemented in the client.
909
910 For more information, see https://redis.io/commands/debug-segfault
911 """
912 )
913
914 def echo(self, value: EncodableT, **kwargs) -> ResponseT:
915 """
916 Echo the string back from the server
917
918 For more information, see https://redis.io/commands/echo
919 """
920 return self.execute_command("ECHO", value, **kwargs)
921
922 def flushall(self, asynchronous: bool = False, **kwargs) -> ResponseT:
923 """
924 Delete all keys in all databases on the current host.
925
926 ``asynchronous`` indicates whether the operation is
927 executed asynchronously by the server.
928
929 For more information, see https://redis.io/commands/flushall
930 """
931 args = []
932 if asynchronous:
933 args.append(b"ASYNC")
934 return self.execute_command("FLUSHALL", *args, **kwargs)
935
936 def flushdb(self, asynchronous: bool = False, **kwargs) -> ResponseT:
937 """
938 Delete all keys in the current database.
939
940 ``asynchronous`` indicates whether the operation is
941 executed asynchronously by the server.
942
943 For more information, see https://redis.io/commands/flushdb
944 """
945 args = []
946 if asynchronous:
947 args.append(b"ASYNC")
948 return self.execute_command("FLUSHDB", *args, **kwargs)
949
950 def sync(self) -> ResponseT:
951 """
952 Initiates a replication stream from the master.
953
954 For more information, see https://redis.io/commands/sync
955 """
956 from redis.client import NEVER_DECODE
957
958 options = {}
959 options[NEVER_DECODE] = []
960 return self.execute_command("SYNC", **options)
961
962 def psync(self, replicationid: str, offset: int):
963 """
964 Initiates a replication stream from the master.
965 Newer version for `sync`.
966
967 For more information, see https://redis.io/commands/sync
968 """
969 from redis.client import NEVER_DECODE
970
971 options = {}
972 options[NEVER_DECODE] = []
973 return self.execute_command("PSYNC", replicationid, offset, **options)
974
975 def swapdb(self, first: int, second: int, **kwargs) -> ResponseT:
976 """
977 Swap two databases
978
979 For more information, see https://redis.io/commands/swapdb
980 """
981 return self.execute_command("SWAPDB", first, second, **kwargs)
982
983 def select(self, index: int, **kwargs) -> ResponseT:
984 """Select the Redis logical database at index.
985
986 See: https://redis.io/commands/select
987 """
988 return self.execute_command("SELECT", index, **kwargs)
989
990 def info(
991 self, section: Optional[str] = None, *args: List[str], **kwargs
992 ) -> 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 BasicKeyCommands(CommandsProtocol):
1565 """
1566 Redis basic key-based commands
1567 """
1568
1569 def append(self, key: KeyT, value: EncodableT) -> ResponseT:
1570 """
1571 Appends the string ``value`` to the value at ``key``. If ``key``
1572 doesn't already exist, create it with a value of ``value``.
1573 Returns the new length of the value at ``key``.
1574
1575 For more information, see https://redis.io/commands/append
1576 """
1577 return self.execute_command("APPEND", key, value)
1578
1579 def bitcount(
1580 self,
1581 key: KeyT,
1582 start: Optional[int] = None,
1583 end: Optional[int] = None,
1584 mode: Optional[str] = None,
1585 ) -> ResponseT:
1586 """
1587 Returns the count of set bits in the value of ``key``. Optional
1588 ``start`` and ``end`` parameters indicate which bytes to consider
1589
1590 For more information, see https://redis.io/commands/bitcount
1591 """
1592 params = [key]
1593 if start is not None and end is not None:
1594 params.append(start)
1595 params.append(end)
1596 elif (start is not None and end is None) or (end is not None and start is None):
1597 raise DataError("Both start and end must be specified")
1598 if mode is not None:
1599 params.append(mode)
1600 return self.execute_command("BITCOUNT", *params, keys=[key])
1601
1602 def bitfield(
1603 self: Union["redis.client.Redis", "redis.asyncio.client.Redis"],
1604 key: KeyT,
1605 default_overflow: Optional[str] = None,
1606 ) -> BitFieldOperation:
1607 """
1608 Return a BitFieldOperation instance to conveniently construct one or
1609 more bitfield operations on ``key``.
1610
1611 For more information, see https://redis.io/commands/bitfield
1612 """
1613 return BitFieldOperation(self, key, default_overflow=default_overflow)
1614
1615 def bitfield_ro(
1616 self: Union["redis.client.Redis", "redis.asyncio.client.Redis"],
1617 key: KeyT,
1618 encoding: str,
1619 offset: BitfieldOffsetT,
1620 items: Optional[list] = None,
1621 ) -> ResponseT:
1622 """
1623 Return an array of the specified bitfield values
1624 where the first value is found using ``encoding`` and ``offset``
1625 parameters and remaining values are result of corresponding
1626 encoding/offset pairs in optional list ``items``
1627 Read-only variant of the BITFIELD command.
1628
1629 For more information, see https://redis.io/commands/bitfield_ro
1630 """
1631 params = [key, "GET", encoding, offset]
1632
1633 items = items or []
1634 for encoding, offset in items:
1635 params.extend(["GET", encoding, offset])
1636 return self.execute_command("BITFIELD_RO", *params, keys=[key])
1637
1638 def bitop(self, operation: str, dest: KeyT, *keys: KeyT) -> ResponseT:
1639 """
1640 Perform a bitwise operation using ``operation`` between ``keys`` and
1641 store the result in ``dest``.
1642
1643 For more information, see https://redis.io/commands/bitop
1644 """
1645 return self.execute_command("BITOP", operation, dest, *keys)
1646
1647 def bitpos(
1648 self,
1649 key: KeyT,
1650 bit: int,
1651 start: Optional[int] = None,
1652 end: Optional[int] = None,
1653 mode: Optional[str] = None,
1654 ) -> ResponseT:
1655 """
1656 Return the position of the first bit set to 1 or 0 in a string.
1657 ``start`` and ``end`` defines search range. The range is interpreted
1658 as a range of bytes and not a range of bits, so start=0 and end=2
1659 means to look at the first three bytes.
1660
1661 For more information, see https://redis.io/commands/bitpos
1662 """
1663 if bit not in (0, 1):
1664 raise DataError("bit must be 0 or 1")
1665 params = [key, bit]
1666
1667 start is not None and params.append(start)
1668
1669 if start is not None and end is not None:
1670 params.append(end)
1671 elif start is None and end is not None:
1672 raise DataError("start argument is not set, when end is specified")
1673
1674 if mode is not None:
1675 params.append(mode)
1676 return self.execute_command("BITPOS", *params, keys=[key])
1677
1678 def copy(
1679 self,
1680 source: str,
1681 destination: str,
1682 destination_db: Optional[str] = None,
1683 replace: bool = False,
1684 ) -> ResponseT:
1685 """
1686 Copy the value stored in the ``source`` key to the ``destination`` key.
1687
1688 ``destination_db`` an alternative destination database. By default,
1689 the ``destination`` key is created in the source Redis database.
1690
1691 ``replace`` whether the ``destination`` key should be removed before
1692 copying the value to it. By default, the value is not copied if
1693 the ``destination`` key already exists.
1694
1695 For more information, see https://redis.io/commands/copy
1696 """
1697 params = [source, destination]
1698 if destination_db is not None:
1699 params.extend(["DB", destination_db])
1700 if replace:
1701 params.append("REPLACE")
1702 return self.execute_command("COPY", *params)
1703
1704 def decrby(self, name: KeyT, amount: int = 1) -> ResponseT:
1705 """
1706 Decrements the value of ``key`` by ``amount``. If no key exists,
1707 the value will be initialized as 0 - ``amount``
1708
1709 For more information, see https://redis.io/commands/decrby
1710 """
1711 return self.execute_command("DECRBY", name, amount)
1712
1713 decr = decrby
1714
1715 def delete(self, *names: KeyT) -> ResponseT:
1716 """
1717 Delete one or more keys specified by ``names``
1718 """
1719 return self.execute_command("DEL", *names)
1720
1721 def __delitem__(self, name: KeyT):
1722 self.delete(name)
1723
1724 def dump(self, name: KeyT) -> ResponseT:
1725 """
1726 Return a serialized version of the value stored at the specified key.
1727 If key does not exist a nil bulk reply is returned.
1728
1729 For more information, see https://redis.io/commands/dump
1730 """
1731 from redis.client import NEVER_DECODE
1732
1733 options = {}
1734 options[NEVER_DECODE] = []
1735 return self.execute_command("DUMP", name, **options)
1736
1737 def exists(self, *names: KeyT) -> ResponseT:
1738 """
1739 Returns the number of ``names`` that exist
1740
1741 For more information, see https://redis.io/commands/exists
1742 """
1743 return self.execute_command("EXISTS", *names, keys=names)
1744
1745 __contains__ = exists
1746
1747 def expire(
1748 self,
1749 name: KeyT,
1750 time: ExpiryT,
1751 nx: bool = False,
1752 xx: bool = False,
1753 gt: bool = False,
1754 lt: bool = False,
1755 ) -> ResponseT:
1756 """
1757 Set an expire flag on key ``name`` for ``time`` seconds with given
1758 ``option``. ``time`` can be represented by an integer or a Python timedelta
1759 object.
1760
1761 Valid options are:
1762 NX -> Set expiry only when the key has no expiry
1763 XX -> Set expiry only when the key has an existing expiry
1764 GT -> Set expiry only when the new expiry is greater than current one
1765 LT -> Set expiry only when the new expiry is less than current one
1766
1767 For more information, see https://redis.io/commands/expire
1768 """
1769 if isinstance(time, datetime.timedelta):
1770 time = int(time.total_seconds())
1771
1772 exp_option = list()
1773 if nx:
1774 exp_option.append("NX")
1775 if xx:
1776 exp_option.append("XX")
1777 if gt:
1778 exp_option.append("GT")
1779 if lt:
1780 exp_option.append("LT")
1781
1782 return self.execute_command("EXPIRE", name, time, *exp_option)
1783
1784 def expireat(
1785 self,
1786 name: KeyT,
1787 when: AbsExpiryT,
1788 nx: bool = False,
1789 xx: bool = False,
1790 gt: bool = False,
1791 lt: bool = False,
1792 ) -> ResponseT:
1793 """
1794 Set an expire flag on key ``name`` with given ``option``. ``when``
1795 can be represented as an integer indicating unix time or a Python
1796 datetime object.
1797
1798 Valid options are:
1799 -> NX -- Set expiry only when the key has no expiry
1800 -> XX -- Set expiry only when the key has an existing expiry
1801 -> GT -- Set expiry only when the new expiry is greater than current one
1802 -> LT -- Set expiry only when the new expiry is less than current one
1803
1804 For more information, see https://redis.io/commands/expireat
1805 """
1806 if isinstance(when, datetime.datetime):
1807 when = int(when.timestamp())
1808
1809 exp_option = list()
1810 if nx:
1811 exp_option.append("NX")
1812 if xx:
1813 exp_option.append("XX")
1814 if gt:
1815 exp_option.append("GT")
1816 if lt:
1817 exp_option.append("LT")
1818
1819 return self.execute_command("EXPIREAT", name, when, *exp_option)
1820
1821 def expiretime(self, key: str) -> int:
1822 """
1823 Returns the absolute Unix timestamp (since January 1, 1970) in seconds
1824 at which the given key will expire.
1825
1826 For more information, see https://redis.io/commands/expiretime
1827 """
1828 return self.execute_command("EXPIRETIME", key)
1829
1830 def get(self, name: KeyT) -> ResponseT:
1831 """
1832 Return the value at key ``name``, or None if the key doesn't exist
1833
1834 For more information, see https://redis.io/commands/get
1835 """
1836 return self.execute_command("GET", name, keys=[name])
1837
1838 def getdel(self, name: KeyT) -> ResponseT:
1839 """
1840 Get the value at key ``name`` and delete the key. This command
1841 is similar to GET, except for the fact that it also deletes
1842 the key on success (if and only if the key's value type
1843 is a string).
1844
1845 For more information, see https://redis.io/commands/getdel
1846 """
1847 return self.execute_command("GETDEL", name)
1848
1849 def getex(
1850 self,
1851 name: KeyT,
1852 ex: Optional[ExpiryT] = None,
1853 px: Optional[ExpiryT] = None,
1854 exat: Optional[AbsExpiryT] = None,
1855 pxat: Optional[AbsExpiryT] = None,
1856 persist: bool = False,
1857 ) -> ResponseT:
1858 """
1859 Get the value of key and optionally set its expiration.
1860 GETEX is similar to GET, but is a write command with
1861 additional options. All time parameters can be given as
1862 datetime.timedelta or integers.
1863
1864 ``ex`` sets an expire flag on key ``name`` for ``ex`` seconds.
1865
1866 ``px`` sets an expire flag on key ``name`` for ``px`` milliseconds.
1867
1868 ``exat`` sets an expire flag on key ``name`` for ``ex`` seconds,
1869 specified in unix time.
1870
1871 ``pxat`` sets an expire flag on key ``name`` for ``ex`` milliseconds,
1872 specified in unix time.
1873
1874 ``persist`` remove the time to live associated with ``name``.
1875
1876 For more information, see https://redis.io/commands/getex
1877 """
1878 opset = {ex, px, exat, pxat}
1879 if len(opset) > 2 or len(opset) > 1 and persist:
1880 raise DataError(
1881 "``ex``, ``px``, ``exat``, ``pxat``, "
1882 "and ``persist`` are mutually exclusive."
1883 )
1884
1885 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat)
1886
1887 if persist:
1888 exp_options.append("PERSIST")
1889
1890 return self.execute_command("GETEX", name, *exp_options)
1891
1892 def __getitem__(self, name: KeyT):
1893 """
1894 Return the value at key ``name``, raises a KeyError if the key
1895 doesn't exist.
1896 """
1897 value = self.get(name)
1898 if value is not None:
1899 return value
1900 raise KeyError(name)
1901
1902 def getbit(self, name: KeyT, offset: int) -> ResponseT:
1903 """
1904 Returns an integer indicating the value of ``offset`` in ``name``
1905
1906 For more information, see https://redis.io/commands/getbit
1907 """
1908 return self.execute_command("GETBIT", name, offset, keys=[name])
1909
1910 def getrange(self, key: KeyT, start: int, end: int) -> ResponseT:
1911 """
1912 Returns the substring of the string value stored at ``key``,
1913 determined by the offsets ``start`` and ``end`` (both are inclusive)
1914
1915 For more information, see https://redis.io/commands/getrange
1916 """
1917 return self.execute_command("GETRANGE", key, start, end, keys=[key])
1918
1919 def getset(self, name: KeyT, value: EncodableT) -> ResponseT:
1920 """
1921 Sets the value at key ``name`` to ``value``
1922 and returns the old value at key ``name`` atomically.
1923
1924 As per Redis 6.2, GETSET is considered deprecated.
1925 Please use SET with GET parameter in new code.
1926
1927 For more information, see https://redis.io/commands/getset
1928 """
1929 return self.execute_command("GETSET", name, value)
1930
1931 def incrby(self, name: KeyT, amount: int = 1) -> ResponseT:
1932 """
1933 Increments the value of ``key`` by ``amount``. If no key exists,
1934 the value will be initialized as ``amount``
1935
1936 For more information, see https://redis.io/commands/incrby
1937 """
1938 return self.execute_command("INCRBY", name, amount)
1939
1940 incr = incrby
1941
1942 def incrbyfloat(self, name: KeyT, amount: float = 1.0) -> ResponseT:
1943 """
1944 Increments the value at key ``name`` by floating ``amount``.
1945 If no key exists, the value will be initialized as ``amount``
1946
1947 For more information, see https://redis.io/commands/incrbyfloat
1948 """
1949 return self.execute_command("INCRBYFLOAT", name, amount)
1950
1951 def keys(self, pattern: PatternT = "*", **kwargs) -> ResponseT:
1952 """
1953 Returns a list of keys matching ``pattern``
1954
1955 For more information, see https://redis.io/commands/keys
1956 """
1957 return self.execute_command("KEYS", pattern, **kwargs)
1958
1959 def lmove(
1960 self, first_list: str, second_list: str, src: str = "LEFT", dest: str = "RIGHT"
1961 ) -> ResponseT:
1962 """
1963 Atomically returns and removes the first/last element of a list,
1964 pushing it as the first/last element on the destination list.
1965 Returns the element being popped and pushed.
1966
1967 For more information, see https://redis.io/commands/lmove
1968 """
1969 params = [first_list, second_list, src, dest]
1970 return self.execute_command("LMOVE", *params)
1971
1972 def blmove(
1973 self,
1974 first_list: str,
1975 second_list: str,
1976 timeout: int,
1977 src: str = "LEFT",
1978 dest: str = "RIGHT",
1979 ) -> ResponseT:
1980 """
1981 Blocking version of lmove.
1982
1983 For more information, see https://redis.io/commands/blmove
1984 """
1985 params = [first_list, second_list, src, dest, timeout]
1986 return self.execute_command("BLMOVE", *params)
1987
1988 def mget(self, keys: KeysT, *args: EncodableT) -> ResponseT:
1989 """
1990 Returns a list of values ordered identically to ``keys``
1991
1992 For more information, see https://redis.io/commands/mget
1993 """
1994 from redis.client import EMPTY_RESPONSE
1995
1996 args = list_or_args(keys, args)
1997 options = {}
1998 if not args:
1999 options[EMPTY_RESPONSE] = []
2000 options["keys"] = args
2001 return self.execute_command("MGET", *args, **options)
2002
2003 def mset(self, mapping: Mapping[AnyKeyT, EncodableT]) -> ResponseT:
2004 """
2005 Sets key/values based on a mapping. Mapping is a dictionary of
2006 key/value pairs. Both keys and values should be strings or types that
2007 can be cast to a string via str().
2008
2009 For more information, see https://redis.io/commands/mset
2010 """
2011 items = []
2012 for pair in mapping.items():
2013 items.extend(pair)
2014 return self.execute_command("MSET", *items)
2015
2016 def msetnx(self, mapping: Mapping[AnyKeyT, EncodableT]) -> ResponseT:
2017 """
2018 Sets key/values based on a mapping if none of the keys are already set.
2019 Mapping is a dictionary of key/value pairs. Both keys and values
2020 should be strings or types that can be cast to a string via str().
2021 Returns a boolean indicating if the operation was successful.
2022
2023 For more information, see https://redis.io/commands/msetnx
2024 """
2025 items = []
2026 for pair in mapping.items():
2027 items.extend(pair)
2028 return self.execute_command("MSETNX", *items)
2029
2030 def move(self, name: KeyT, db: int) -> ResponseT:
2031 """
2032 Moves the key ``name`` to a different Redis database ``db``
2033
2034 For more information, see https://redis.io/commands/move
2035 """
2036 return self.execute_command("MOVE", name, db)
2037
2038 def persist(self, name: KeyT) -> ResponseT:
2039 """
2040 Removes an expiration on ``name``
2041
2042 For more information, see https://redis.io/commands/persist
2043 """
2044 return self.execute_command("PERSIST", name)
2045
2046 def pexpire(
2047 self,
2048 name: KeyT,
2049 time: ExpiryT,
2050 nx: bool = False,
2051 xx: bool = False,
2052 gt: bool = False,
2053 lt: bool = False,
2054 ) -> ResponseT:
2055 """
2056 Set an expire flag on key ``name`` for ``time`` milliseconds
2057 with given ``option``. ``time`` can be represented by an
2058 integer or a Python timedelta object.
2059
2060 Valid options are:
2061 NX -> Set expiry only when the key has no expiry
2062 XX -> Set expiry only when the key has an existing expiry
2063 GT -> Set expiry only when the new expiry is greater than current one
2064 LT -> Set expiry only when the new expiry is less than current one
2065
2066 For more information, see https://redis.io/commands/pexpire
2067 """
2068 if isinstance(time, datetime.timedelta):
2069 time = int(time.total_seconds() * 1000)
2070
2071 exp_option = list()
2072 if nx:
2073 exp_option.append("NX")
2074 if xx:
2075 exp_option.append("XX")
2076 if gt:
2077 exp_option.append("GT")
2078 if lt:
2079 exp_option.append("LT")
2080 return self.execute_command("PEXPIRE", name, time, *exp_option)
2081
2082 def pexpireat(
2083 self,
2084 name: KeyT,
2085 when: AbsExpiryT,
2086 nx: bool = False,
2087 xx: bool = False,
2088 gt: bool = False,
2089 lt: bool = False,
2090 ) -> ResponseT:
2091 """
2092 Set an expire flag on key ``name`` with given ``option``. ``when``
2093 can be represented as an integer representing unix time in
2094 milliseconds (unix time * 1000) or a Python datetime object.
2095
2096 Valid options are:
2097 NX -> Set expiry only when the key has no expiry
2098 XX -> Set expiry only when the key has an existing expiry
2099 GT -> Set expiry only when the new expiry is greater than current one
2100 LT -> Set expiry only when the new expiry is less than current one
2101
2102 For more information, see https://redis.io/commands/pexpireat
2103 """
2104 if isinstance(when, datetime.datetime):
2105 when = int(when.timestamp() * 1000)
2106 exp_option = list()
2107 if nx:
2108 exp_option.append("NX")
2109 if xx:
2110 exp_option.append("XX")
2111 if gt:
2112 exp_option.append("GT")
2113 if lt:
2114 exp_option.append("LT")
2115 return self.execute_command("PEXPIREAT", name, when, *exp_option)
2116
2117 def pexpiretime(self, key: str) -> int:
2118 """
2119 Returns the absolute Unix timestamp (since January 1, 1970) in milliseconds
2120 at which the given key will expire.
2121
2122 For more information, see https://redis.io/commands/pexpiretime
2123 """
2124 return self.execute_command("PEXPIRETIME", key)
2125
2126 def psetex(self, name: KeyT, time_ms: ExpiryT, value: EncodableT):
2127 """
2128 Set the value of key ``name`` to ``value`` that expires in ``time_ms``
2129 milliseconds. ``time_ms`` can be represented by an integer or a Python
2130 timedelta object
2131
2132 For more information, see https://redis.io/commands/psetex
2133 """
2134 if isinstance(time_ms, datetime.timedelta):
2135 time_ms = int(time_ms.total_seconds() * 1000)
2136 return self.execute_command("PSETEX", name, time_ms, value)
2137
2138 def pttl(self, name: KeyT) -> ResponseT:
2139 """
2140 Returns the number of milliseconds until the key ``name`` will expire
2141
2142 For more information, see https://redis.io/commands/pttl
2143 """
2144 return self.execute_command("PTTL", name)
2145
2146 def hrandfield(
2147 self, key: str, count: Optional[int] = None, withvalues: bool = False
2148 ) -> ResponseT:
2149 """
2150 Return a random field from the hash value stored at key.
2151
2152 count: if the argument is positive, return an array of distinct fields.
2153 If called with a negative count, the behavior changes and the command
2154 is allowed to return the same field multiple times. In this case,
2155 the number of returned fields is the absolute value of the
2156 specified count.
2157 withvalues: The optional WITHVALUES modifier changes the reply so it
2158 includes the respective values of the randomly selected hash fields.
2159
2160 For more information, see https://redis.io/commands/hrandfield
2161 """
2162 params = []
2163 if count is not None:
2164 params.append(count)
2165 if withvalues:
2166 params.append("WITHVALUES")
2167
2168 return self.execute_command("HRANDFIELD", key, *params)
2169
2170 def randomkey(self, **kwargs) -> ResponseT:
2171 """
2172 Returns the name of a random key
2173
2174 For more information, see https://redis.io/commands/randomkey
2175 """
2176 return self.execute_command("RANDOMKEY", **kwargs)
2177
2178 def rename(self, src: KeyT, dst: KeyT) -> ResponseT:
2179 """
2180 Rename key ``src`` to ``dst``
2181
2182 For more information, see https://redis.io/commands/rename
2183 """
2184 return self.execute_command("RENAME", src, dst)
2185
2186 def renamenx(self, src: KeyT, dst: KeyT):
2187 """
2188 Rename key ``src`` to ``dst`` if ``dst`` doesn't already exist
2189
2190 For more information, see https://redis.io/commands/renamenx
2191 """
2192 return self.execute_command("RENAMENX", src, dst)
2193
2194 def restore(
2195 self,
2196 name: KeyT,
2197 ttl: float,
2198 value: EncodableT,
2199 replace: bool = False,
2200 absttl: bool = False,
2201 idletime: Optional[int] = None,
2202 frequency: Optional[int] = None,
2203 ) -> ResponseT:
2204 """
2205 Create a key using the provided serialized value, previously obtained
2206 using DUMP.
2207
2208 ``replace`` allows an existing key on ``name`` to be overridden. If
2209 it's not specified an error is raised on collision.
2210
2211 ``absttl`` if True, specified ``ttl`` should represent an absolute Unix
2212 timestamp in milliseconds in which the key will expire. (Redis 5.0 or
2213 greater).
2214
2215 ``idletime`` Used for eviction, this is the number of seconds the
2216 key must be idle, prior to execution.
2217
2218 ``frequency`` Used for eviction, this is the frequency counter of
2219 the object stored at the key, prior to execution.
2220
2221 For more information, see https://redis.io/commands/restore
2222 """
2223 params = [name, ttl, value]
2224 if replace:
2225 params.append("REPLACE")
2226 if absttl:
2227 params.append("ABSTTL")
2228 if idletime is not None:
2229 params.append("IDLETIME")
2230 try:
2231 params.append(int(idletime))
2232 except ValueError:
2233 raise DataError("idletimemust be an integer")
2234
2235 if frequency is not None:
2236 params.append("FREQ")
2237 try:
2238 params.append(int(frequency))
2239 except ValueError:
2240 raise DataError("frequency must be an integer")
2241
2242 return self.execute_command("RESTORE", *params)
2243
2244 def set(
2245 self,
2246 name: KeyT,
2247 value: EncodableT,
2248 ex: Optional[ExpiryT] = None,
2249 px: Optional[ExpiryT] = None,
2250 nx: bool = False,
2251 xx: bool = False,
2252 keepttl: bool = False,
2253 get: bool = False,
2254 exat: Optional[AbsExpiryT] = None,
2255 pxat: Optional[AbsExpiryT] = None,
2256 ) -> ResponseT:
2257 """
2258 Set the value at key ``name`` to ``value``
2259
2260 ``ex`` sets an expire flag on key ``name`` for ``ex`` seconds.
2261
2262 ``px`` sets an expire flag on key ``name`` for ``px`` milliseconds.
2263
2264 ``nx`` if set to True, set the value at key ``name`` to ``value`` only
2265 if it does not exist.
2266
2267 ``xx`` if set to True, set the value at key ``name`` to ``value`` only
2268 if it already exists.
2269
2270 ``keepttl`` if True, retain the time to live associated with the key.
2271 (Available since Redis 6.0)
2272
2273 ``get`` if True, set the value at key ``name`` to ``value`` and return
2274 the old value stored at key, or None if the key did not exist.
2275 (Available since Redis 6.2)
2276
2277 ``exat`` sets an expire flag on key ``name`` for ``ex`` seconds,
2278 specified in unix time.
2279
2280 ``pxat`` sets an expire flag on key ``name`` for ``ex`` milliseconds,
2281 specified in unix time.
2282
2283 For more information, see https://redis.io/commands/set
2284 """
2285 opset = {ex, px, exat, pxat}
2286 if len(opset) > 2 or len(opset) > 1 and keepttl:
2287 raise DataError(
2288 "``ex``, ``px``, ``exat``, ``pxat``, "
2289 "and ``keepttl`` are mutually exclusive."
2290 )
2291
2292 if nx and xx:
2293 raise DataError("``nx`` and ``xx`` are mutually exclusive.")
2294
2295 pieces: list[EncodableT] = [name, value]
2296 options = {}
2297
2298 pieces.extend(extract_expire_flags(ex, px, exat, pxat))
2299
2300 if keepttl:
2301 pieces.append("KEEPTTL")
2302
2303 if nx:
2304 pieces.append("NX")
2305 if xx:
2306 pieces.append("XX")
2307
2308 if get:
2309 pieces.append("GET")
2310 options["get"] = True
2311
2312 return self.execute_command("SET", *pieces, **options)
2313
2314 def __setitem__(self, name: KeyT, value: EncodableT):
2315 self.set(name, value)
2316
2317 def setbit(self, name: KeyT, offset: int, value: int) -> ResponseT:
2318 """
2319 Flag the ``offset`` in ``name`` as ``value``. Returns an integer
2320 indicating the previous value of ``offset``.
2321
2322 For more information, see https://redis.io/commands/setbit
2323 """
2324 value = value and 1 or 0
2325 return self.execute_command("SETBIT", name, offset, value)
2326
2327 def setex(self, name: KeyT, time: ExpiryT, value: EncodableT) -> ResponseT:
2328 """
2329 Set the value of key ``name`` to ``value`` that expires in ``time``
2330 seconds. ``time`` can be represented by an integer or a Python
2331 timedelta object.
2332
2333 For more information, see https://redis.io/commands/setex
2334 """
2335 if isinstance(time, datetime.timedelta):
2336 time = int(time.total_seconds())
2337 return self.execute_command("SETEX", name, time, value)
2338
2339 def setnx(self, name: KeyT, value: EncodableT) -> ResponseT:
2340 """
2341 Set the value of key ``name`` to ``value`` if key doesn't exist
2342
2343 For more information, see https://redis.io/commands/setnx
2344 """
2345 return self.execute_command("SETNX", name, value)
2346
2347 def setrange(self, name: KeyT, offset: int, value: EncodableT) -> ResponseT:
2348 """
2349 Overwrite bytes in the value of ``name`` starting at ``offset`` with
2350 ``value``. If ``offset`` plus the length of ``value`` exceeds the
2351 length of the original value, the new value will be larger than before.
2352 If ``offset`` exceeds the length of the original value, null bytes
2353 will be used to pad between the end of the previous value and the start
2354 of what's being injected.
2355
2356 Returns the length of the new string.
2357
2358 For more information, see https://redis.io/commands/setrange
2359 """
2360 return self.execute_command("SETRANGE", name, offset, value)
2361
2362 def stralgo(
2363 self,
2364 algo: Literal["LCS"],
2365 value1: KeyT,
2366 value2: KeyT,
2367 specific_argument: Union[Literal["strings"], Literal["keys"]] = "strings",
2368 len: bool = False,
2369 idx: bool = False,
2370 minmatchlen: Optional[int] = None,
2371 withmatchlen: bool = False,
2372 **kwargs,
2373 ) -> ResponseT:
2374 """
2375 Implements complex algorithms that operate on strings.
2376 Right now the only algorithm implemented is the LCS algorithm
2377 (longest common substring). However new algorithms could be
2378 implemented in the future.
2379
2380 ``algo`` Right now must be LCS
2381 ``value1`` and ``value2`` Can be two strings or two keys
2382 ``specific_argument`` Specifying if the arguments to the algorithm
2383 will be keys or strings. strings is the default.
2384 ``len`` Returns just the len of the match.
2385 ``idx`` Returns the match positions in each string.
2386 ``minmatchlen`` Restrict the list of matches to the ones of a given
2387 minimal length. Can be provided only when ``idx`` set to True.
2388 ``withmatchlen`` Returns the matches with the len of the match.
2389 Can be provided only when ``idx`` set to True.
2390
2391 For more information, see https://redis.io/commands/stralgo
2392 """
2393 # check validity
2394 supported_algo = ["LCS"]
2395 if algo not in supported_algo:
2396 supported_algos_str = ", ".join(supported_algo)
2397 raise DataError(f"The supported algorithms are: {supported_algos_str}")
2398 if specific_argument not in ["keys", "strings"]:
2399 raise DataError("specific_argument can be only keys or strings")
2400 if len and idx:
2401 raise DataError("len and idx cannot be provided together.")
2402
2403 pieces: list[EncodableT] = [algo, specific_argument.upper(), value1, value2]
2404 if len:
2405 pieces.append(b"LEN")
2406 if idx:
2407 pieces.append(b"IDX")
2408 try:
2409 int(minmatchlen)
2410 pieces.extend([b"MINMATCHLEN", minmatchlen])
2411 except TypeError:
2412 pass
2413 if withmatchlen:
2414 pieces.append(b"WITHMATCHLEN")
2415
2416 return self.execute_command(
2417 "STRALGO",
2418 *pieces,
2419 len=len,
2420 idx=idx,
2421 minmatchlen=minmatchlen,
2422 withmatchlen=withmatchlen,
2423 **kwargs,
2424 )
2425
2426 def strlen(self, name: KeyT) -> ResponseT:
2427 """
2428 Return the number of bytes stored in the value of ``name``
2429
2430 For more information, see https://redis.io/commands/strlen
2431 """
2432 return self.execute_command("STRLEN", name, keys=[name])
2433
2434 def substr(self, name: KeyT, start: int, end: int = -1) -> ResponseT:
2435 """
2436 Return a substring of the string at key ``name``. ``start`` and ``end``
2437 are 0-based integers specifying the portion of the string to return.
2438 """
2439 return self.execute_command("SUBSTR", name, start, end, keys=[name])
2440
2441 def touch(self, *args: KeyT) -> ResponseT:
2442 """
2443 Alters the last access time of a key(s) ``*args``. A key is ignored
2444 if it does not exist.
2445
2446 For more information, see https://redis.io/commands/touch
2447 """
2448 return self.execute_command("TOUCH", *args)
2449
2450 def ttl(self, name: KeyT) -> ResponseT:
2451 """
2452 Returns the number of seconds until the key ``name`` will expire
2453
2454 For more information, see https://redis.io/commands/ttl
2455 """
2456 return self.execute_command("TTL", name)
2457
2458 def type(self, name: KeyT) -> ResponseT:
2459 """
2460 Returns the type of key ``name``
2461
2462 For more information, see https://redis.io/commands/type
2463 """
2464 return self.execute_command("TYPE", name, keys=[name])
2465
2466 def watch(self, *names: KeyT) -> None:
2467 """
2468 Watches the values at keys ``names``, or None if the key doesn't exist
2469
2470 For more information, see https://redis.io/commands/watch
2471 """
2472 warnings.warn(DeprecationWarning("Call WATCH from a Pipeline object"))
2473
2474 def unwatch(self) -> None:
2475 """
2476 Unwatches all previously watched keys for a transaction
2477
2478 For more information, see https://redis.io/commands/unwatch
2479 """
2480 warnings.warn(DeprecationWarning("Call UNWATCH from a Pipeline object"))
2481
2482 def unlink(self, *names: KeyT) -> ResponseT:
2483 """
2484 Unlink one or more keys specified by ``names``
2485
2486 For more information, see https://redis.io/commands/unlink
2487 """
2488 return self.execute_command("UNLINK", *names)
2489
2490 def lcs(
2491 self,
2492 key1: str,
2493 key2: str,
2494 len: Optional[bool] = False,
2495 idx: Optional[bool] = False,
2496 minmatchlen: Optional[int] = 0,
2497 withmatchlen: Optional[bool] = False,
2498 ) -> Union[str, int, list]:
2499 """
2500 Find the longest common subsequence between ``key1`` and ``key2``.
2501 If ``len`` is true the length of the match will will be returned.
2502 If ``idx`` is true the match position in each strings will be returned.
2503 ``minmatchlen`` restrict the list of matches to the ones of
2504 the given ``minmatchlen``.
2505 If ``withmatchlen`` the length of the match also will be returned.
2506 For more information, see https://redis.io/commands/lcs
2507 """
2508 pieces = [key1, key2]
2509 if len:
2510 pieces.append("LEN")
2511 if idx:
2512 pieces.append("IDX")
2513 if minmatchlen != 0:
2514 pieces.extend(["MINMATCHLEN", minmatchlen])
2515 if withmatchlen:
2516 pieces.append("WITHMATCHLEN")
2517 return self.execute_command("LCS", *pieces, keys=[key1, key2])
2518
2519
2520class AsyncBasicKeyCommands(BasicKeyCommands):
2521 def __delitem__(self, name: KeyT):
2522 raise TypeError("Async Redis client does not support class deletion")
2523
2524 def __contains__(self, name: KeyT):
2525 raise TypeError("Async Redis client does not support class inclusion")
2526
2527 def __getitem__(self, name: KeyT):
2528 raise TypeError("Async Redis client does not support class retrieval")
2529
2530 def __setitem__(self, name: KeyT, value: EncodableT):
2531 raise TypeError("Async Redis client does not support class assignment")
2532
2533 async def watch(self, *names: KeyT) -> None:
2534 return super().watch(*names)
2535
2536 async def unwatch(self) -> None:
2537 return super().unwatch()
2538
2539
2540class ListCommands(CommandsProtocol):
2541 """
2542 Redis commands for List data type.
2543 see: https://redis.io/topics/data-types#lists
2544 """
2545
2546 def blpop(
2547 self, keys: List, timeout: Optional[Number] = 0
2548 ) -> Union[Awaitable[list], list]:
2549 """
2550 LPOP a value off of the first non-empty list
2551 named in the ``keys`` list.
2552
2553 If none of the lists in ``keys`` has a value to LPOP, then block
2554 for ``timeout`` seconds, or until a value gets pushed on to one
2555 of the lists.
2556
2557 If timeout is 0, then block indefinitely.
2558
2559 For more information, see https://redis.io/commands/blpop
2560 """
2561 if timeout is None:
2562 timeout = 0
2563 keys = list_or_args(keys, None)
2564 keys.append(timeout)
2565 return self.execute_command("BLPOP", *keys)
2566
2567 def brpop(
2568 self, keys: List, timeout: Optional[Number] = 0
2569 ) -> Union[Awaitable[list], list]:
2570 """
2571 RPOP a value off of the first non-empty list
2572 named in the ``keys`` list.
2573
2574 If none of the lists in ``keys`` has a value to RPOP, then block
2575 for ``timeout`` seconds, or until a value gets pushed on to one
2576 of the lists.
2577
2578 If timeout is 0, then block indefinitely.
2579
2580 For more information, see https://redis.io/commands/brpop
2581 """
2582 if timeout is None:
2583 timeout = 0
2584 keys = list_or_args(keys, None)
2585 keys.append(timeout)
2586 return self.execute_command("BRPOP", *keys)
2587
2588 def brpoplpush(
2589 self, src: str, dst: str, timeout: Optional[Number] = 0
2590 ) -> Union[Awaitable[Optional[str]], Optional[str]]:
2591 """
2592 Pop a value off the tail of ``src``, push it on the head of ``dst``
2593 and then return it.
2594
2595 This command blocks until a value is in ``src`` or until ``timeout``
2596 seconds elapse, whichever is first. A ``timeout`` value of 0 blocks
2597 forever.
2598
2599 For more information, see https://redis.io/commands/brpoplpush
2600 """
2601 if timeout is None:
2602 timeout = 0
2603 return self.execute_command("BRPOPLPUSH", src, dst, timeout)
2604
2605 def blmpop(
2606 self,
2607 timeout: float,
2608 numkeys: int,
2609 *args: List[str],
2610 direction: str,
2611 count: Optional[int] = 1,
2612 ) -> Optional[list]:
2613 """
2614 Pop ``count`` values (default 1) from first non-empty in the list
2615 of provided key names.
2616
2617 When all lists are empty this command blocks the connection until another
2618 client pushes to it or until the timeout, timeout of 0 blocks indefinitely
2619
2620 For more information, see https://redis.io/commands/blmpop
2621 """
2622 args = [timeout, numkeys, *args, direction, "COUNT", count]
2623
2624 return self.execute_command("BLMPOP", *args)
2625
2626 def lmpop(
2627 self,
2628 num_keys: int,
2629 *args: List[str],
2630 direction: str,
2631 count: Optional[int] = 1,
2632 ) -> Union[Awaitable[list], list]:
2633 """
2634 Pop ``count`` values (default 1) first non-empty list key from the list
2635 of args provided key names.
2636
2637 For more information, see https://redis.io/commands/lmpop
2638 """
2639 args = [num_keys] + list(args) + [direction]
2640 if count != 1:
2641 args.extend(["COUNT", count])
2642
2643 return self.execute_command("LMPOP", *args)
2644
2645 def lindex(
2646 self, name: str, index: int
2647 ) -> Union[Awaitable[Optional[str]], Optional[str]]:
2648 """
2649 Return the item from list ``name`` at position ``index``
2650
2651 Negative indexes are supported and will return an item at the
2652 end of the list
2653
2654 For more information, see https://redis.io/commands/lindex
2655 """
2656 return self.execute_command("LINDEX", name, index, keys=[name])
2657
2658 def linsert(
2659 self, name: str, where: str, refvalue: str, value: str
2660 ) -> Union[Awaitable[int], int]:
2661 """
2662 Insert ``value`` in list ``name`` either immediately before or after
2663 [``where``] ``refvalue``
2664
2665 Returns the new length of the list on success or -1 if ``refvalue``
2666 is not in the list.
2667
2668 For more information, see https://redis.io/commands/linsert
2669 """
2670 return self.execute_command("LINSERT", name, where, refvalue, value)
2671
2672 def llen(self, name: str) -> Union[Awaitable[int], int]:
2673 """
2674 Return the length of the list ``name``
2675
2676 For more information, see https://redis.io/commands/llen
2677 """
2678 return self.execute_command("LLEN", name, keys=[name])
2679
2680 def lpop(
2681 self,
2682 name: str,
2683 count: Optional[int] = None,
2684 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]:
2685 """
2686 Removes and returns the first elements of the list ``name``.
2687
2688 By default, the command pops a single element from the beginning of
2689 the list. When provided with the optional ``count`` argument, the reply
2690 will consist of up to count elements, depending on the list's length.
2691
2692 For more information, see https://redis.io/commands/lpop
2693 """
2694 if count is not None:
2695 return self.execute_command("LPOP", name, count)
2696 else:
2697 return self.execute_command("LPOP", name)
2698
2699 def lpush(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]:
2700 """
2701 Push ``values`` onto the head of the list ``name``
2702
2703 For more information, see https://redis.io/commands/lpush
2704 """
2705 return self.execute_command("LPUSH", name, *values)
2706
2707 def lpushx(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]:
2708 """
2709 Push ``value`` onto the head of the list ``name`` if ``name`` exists
2710
2711 For more information, see https://redis.io/commands/lpushx
2712 """
2713 return self.execute_command("LPUSHX", name, *values)
2714
2715 def lrange(self, name: str, start: int, end: int) -> Union[Awaitable[list], list]:
2716 """
2717 Return a slice of the list ``name`` between
2718 position ``start`` and ``end``
2719
2720 ``start`` and ``end`` can be negative numbers just like
2721 Python slicing notation
2722
2723 For more information, see https://redis.io/commands/lrange
2724 """
2725 return self.execute_command("LRANGE", name, start, end, keys=[name])
2726
2727 def lrem(self, name: str, count: int, value: str) -> Union[Awaitable[int], int]:
2728 """
2729 Remove the first ``count`` occurrences of elements equal to ``value``
2730 from the list stored at ``name``.
2731
2732 The count argument influences the operation in the following ways:
2733 count > 0: Remove elements equal to value moving from head to tail.
2734 count < 0: Remove elements equal to value moving from tail to head.
2735 count = 0: Remove all elements equal to value.
2736
2737 For more information, see https://redis.io/commands/lrem
2738 """
2739 return self.execute_command("LREM", name, count, value)
2740
2741 def lset(self, name: str, index: int, value: str) -> Union[Awaitable[str], str]:
2742 """
2743 Set element at ``index`` of list ``name`` to ``value``
2744
2745 For more information, see https://redis.io/commands/lset
2746 """
2747 return self.execute_command("LSET", name, index, value)
2748
2749 def ltrim(self, name: str, start: int, end: int) -> Union[Awaitable[str], str]:
2750 """
2751 Trim the list ``name``, removing all values not within the slice
2752 between ``start`` and ``end``
2753
2754 ``start`` and ``end`` can be negative numbers just like
2755 Python slicing notation
2756
2757 For more information, see https://redis.io/commands/ltrim
2758 """
2759 return self.execute_command("LTRIM", name, start, end)
2760
2761 def rpop(
2762 self,
2763 name: str,
2764 count: Optional[int] = None,
2765 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]:
2766 """
2767 Removes and returns the last elements of the list ``name``.
2768
2769 By default, the command pops a single element from the end of the list.
2770 When provided with the optional ``count`` argument, the reply will
2771 consist of up to count elements, depending on the list's length.
2772
2773 For more information, see https://redis.io/commands/rpop
2774 """
2775 if count is not None:
2776 return self.execute_command("RPOP", name, count)
2777 else:
2778 return self.execute_command("RPOP", name)
2779
2780 def rpoplpush(self, src: str, dst: str) -> Union[Awaitable[str], str]:
2781 """
2782 RPOP a value off of the ``src`` list and atomically LPUSH it
2783 on to the ``dst`` list. Returns the value.
2784
2785 For more information, see https://redis.io/commands/rpoplpush
2786 """
2787 return self.execute_command("RPOPLPUSH", src, dst)
2788
2789 def rpush(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]:
2790 """
2791 Push ``values`` onto the tail of the list ``name``
2792
2793 For more information, see https://redis.io/commands/rpush
2794 """
2795 return self.execute_command("RPUSH", name, *values)
2796
2797 def rpushx(self, name: str, *values: str) -> Union[Awaitable[int], int]:
2798 """
2799 Push ``value`` onto the tail of the list ``name`` if ``name`` exists
2800
2801 For more information, see https://redis.io/commands/rpushx
2802 """
2803 return self.execute_command("RPUSHX", name, *values)
2804
2805 def lpos(
2806 self,
2807 name: str,
2808 value: str,
2809 rank: Optional[int] = None,
2810 count: Optional[int] = None,
2811 maxlen: Optional[int] = None,
2812 ) -> Union[str, List, None]:
2813 """
2814 Get position of ``value`` within the list ``name``
2815
2816 If specified, ``rank`` indicates the "rank" of the first element to
2817 return in case there are multiple copies of ``value`` in the list.
2818 By default, LPOS returns the position of the first occurrence of
2819 ``value`` in the list. When ``rank`` 2, LPOS returns the position of
2820 the second ``value`` in the list. If ``rank`` is negative, LPOS
2821 searches the list in reverse. For example, -1 would return the
2822 position of the last occurrence of ``value`` and -2 would return the
2823 position of the next to last occurrence of ``value``.
2824
2825 If specified, ``count`` indicates that LPOS should return a list of
2826 up to ``count`` positions. A ``count`` of 2 would return a list of
2827 up to 2 positions. A ``count`` of 0 returns a list of all positions
2828 matching ``value``. When ``count`` is specified and but ``value``
2829 does not exist in the list, an empty list is returned.
2830
2831 If specified, ``maxlen`` indicates the maximum number of list
2832 elements to scan. A ``maxlen`` of 1000 will only return the
2833 position(s) of items within the first 1000 entries in the list.
2834 A ``maxlen`` of 0 (the default) will scan the entire list.
2835
2836 For more information, see https://redis.io/commands/lpos
2837 """
2838 pieces: list[EncodableT] = [name, value]
2839 if rank is not None:
2840 pieces.extend(["RANK", rank])
2841
2842 if count is not None:
2843 pieces.extend(["COUNT", count])
2844
2845 if maxlen is not None:
2846 pieces.extend(["MAXLEN", maxlen])
2847
2848 return self.execute_command("LPOS", *pieces, keys=[name])
2849
2850 def sort(
2851 self,
2852 name: str,
2853 start: Optional[int] = None,
2854 num: Optional[int] = None,
2855 by: Optional[str] = None,
2856 get: Optional[List[str]] = None,
2857 desc: bool = False,
2858 alpha: bool = False,
2859 store: Optional[str] = None,
2860 groups: Optional[bool] = False,
2861 ) -> Union[List, int]:
2862 """
2863 Sort and return the list, set or sorted set at ``name``.
2864
2865 ``start`` and ``num`` allow for paging through the sorted data
2866
2867 ``by`` allows using an external key to weight and sort the items.
2868 Use an "*" to indicate where in the key the item value is located
2869
2870 ``get`` allows for returning items from external keys rather than the
2871 sorted data itself. Use an "*" to indicate where in the key
2872 the item value is located
2873
2874 ``desc`` allows for reversing the sort
2875
2876 ``alpha`` allows for sorting lexicographically rather than numerically
2877
2878 ``store`` allows for storing the result of the sort into
2879 the key ``store``
2880
2881 ``groups`` if set to True and if ``get`` contains at least two
2882 elements, sort will return a list of tuples, each containing the
2883 values fetched from the arguments to ``get``.
2884
2885 For more information, see https://redis.io/commands/sort
2886 """
2887 if (start is not None and num is None) or (num is not None and start is None):
2888 raise DataError("``start`` and ``num`` must both be specified")
2889
2890 pieces: list[EncodableT] = [name]
2891 if by is not None:
2892 pieces.extend([b"BY", by])
2893 if start is not None and num is not None:
2894 pieces.extend([b"LIMIT", start, num])
2895 if get is not None:
2896 # If get is a string assume we want to get a single value.
2897 # Otherwise assume it's an interable and we want to get multiple
2898 # values. We can't just iterate blindly because strings are
2899 # iterable.
2900 if isinstance(get, (bytes, str)):
2901 pieces.extend([b"GET", get])
2902 else:
2903 for g in get:
2904 pieces.extend([b"GET", g])
2905 if desc:
2906 pieces.append(b"DESC")
2907 if alpha:
2908 pieces.append(b"ALPHA")
2909 if store is not None:
2910 pieces.extend([b"STORE", store])
2911 if groups:
2912 if not get or isinstance(get, (bytes, str)) or len(get) < 2:
2913 raise DataError(
2914 'when using "groups" the "get" argument '
2915 "must be specified and contain at least "
2916 "two keys"
2917 )
2918
2919 options = {"groups": len(get) if groups else None}
2920 options["keys"] = [name]
2921 return self.execute_command("SORT", *pieces, **options)
2922
2923 def sort_ro(
2924 self,
2925 key: str,
2926 start: Optional[int] = None,
2927 num: Optional[int] = None,
2928 by: Optional[str] = None,
2929 get: Optional[List[str]] = None,
2930 desc: bool = False,
2931 alpha: bool = False,
2932 ) -> list:
2933 """
2934 Returns the elements contained in the list, set or sorted set at key.
2935 (read-only variant of the SORT command)
2936
2937 ``start`` and ``num`` allow for paging through the sorted data
2938
2939 ``by`` allows using an external key to weight and sort the items.
2940 Use an "*" to indicate where in the key the item value is located
2941
2942 ``get`` allows for returning items from external keys rather than the
2943 sorted data itself. Use an "*" to indicate where in the key
2944 the item value is located
2945
2946 ``desc`` allows for reversing the sort
2947
2948 ``alpha`` allows for sorting lexicographically rather than numerically
2949
2950 For more information, see https://redis.io/commands/sort_ro
2951 """
2952 return self.sort(
2953 key, start=start, num=num, by=by, get=get, desc=desc, alpha=alpha
2954 )
2955
2956
2957AsyncListCommands = ListCommands
2958
2959
2960class ScanCommands(CommandsProtocol):
2961 """
2962 Redis SCAN commands.
2963 see: https://redis.io/commands/scan
2964 """
2965
2966 def scan(
2967 self,
2968 cursor: int = 0,
2969 match: Union[PatternT, None] = None,
2970 count: Optional[int] = None,
2971 _type: Optional[str] = None,
2972 **kwargs,
2973 ) -> ResponseT:
2974 """
2975 Incrementally return lists of key names. Also return a cursor
2976 indicating the scan position.
2977
2978 ``match`` allows for filtering the keys by pattern
2979
2980 ``count`` provides a hint to Redis about the number of keys to
2981 return per batch.
2982
2983 ``_type`` filters the returned values by a particular Redis type.
2984 Stock Redis instances allow for the following types:
2985 HASH, LIST, SET, STREAM, STRING, ZSET
2986 Additionally, Redis modules can expose other types as well.
2987
2988 For more information, see https://redis.io/commands/scan
2989 """
2990 pieces: list[EncodableT] = [cursor]
2991 if match is not None:
2992 pieces.extend([b"MATCH", match])
2993 if count is not None:
2994 pieces.extend([b"COUNT", count])
2995 if _type is not None:
2996 pieces.extend([b"TYPE", _type])
2997 return self.execute_command("SCAN", *pieces, **kwargs)
2998
2999 def scan_iter(
3000 self,
3001 match: Union[PatternT, None] = None,
3002 count: Optional[int] = None,
3003 _type: Optional[str] = None,
3004 **kwargs,
3005 ) -> Iterator:
3006 """
3007 Make an iterator using the SCAN command so that the client doesn't
3008 need to remember the cursor position.
3009
3010 ``match`` allows for filtering the keys by pattern
3011
3012 ``count`` provides a hint to Redis about the number of keys to
3013 return per batch.
3014
3015 ``_type`` filters the returned values by a particular Redis type.
3016 Stock Redis instances allow for the following types:
3017 HASH, LIST, SET, STREAM, STRING, ZSET
3018 Additionally, Redis modules can expose other types as well.
3019 """
3020 cursor = "0"
3021 while cursor != 0:
3022 cursor, data = self.scan(
3023 cursor=cursor, match=match, count=count, _type=_type, **kwargs
3024 )
3025 yield from data
3026
3027 def sscan(
3028 self,
3029 name: KeyT,
3030 cursor: int = 0,
3031 match: Union[PatternT, None] = None,
3032 count: Optional[int] = None,
3033 ) -> ResponseT:
3034 """
3035 Incrementally return lists of elements in a set. Also return a cursor
3036 indicating the scan position.
3037
3038 ``match`` allows for filtering the keys by pattern
3039
3040 ``count`` allows for hint the minimum number of returns
3041
3042 For more information, see https://redis.io/commands/sscan
3043 """
3044 pieces: list[EncodableT] = [name, cursor]
3045 if match is not None:
3046 pieces.extend([b"MATCH", match])
3047 if count is not None:
3048 pieces.extend([b"COUNT", count])
3049 return self.execute_command("SSCAN", *pieces)
3050
3051 def sscan_iter(
3052 self,
3053 name: KeyT,
3054 match: Union[PatternT, None] = None,
3055 count: Optional[int] = None,
3056 ) -> Iterator:
3057 """
3058 Make an iterator using the SSCAN command so that the client doesn't
3059 need to remember the cursor position.
3060
3061 ``match`` allows for filtering the keys by pattern
3062
3063 ``count`` allows for hint the minimum number of returns
3064 """
3065 cursor = "0"
3066 while cursor != 0:
3067 cursor, data = self.sscan(name, cursor=cursor, match=match, count=count)
3068 yield from data
3069
3070 def hscan(
3071 self,
3072 name: KeyT,
3073 cursor: int = 0,
3074 match: Union[PatternT, None] = None,
3075 count: Optional[int] = None,
3076 no_values: Union[bool, None] = None,
3077 ) -> ResponseT:
3078 """
3079 Incrementally return key/value slices in a hash. Also return a cursor
3080 indicating the scan position.
3081
3082 ``match`` allows for filtering the keys by pattern
3083
3084 ``count`` allows for hint the minimum number of returns
3085
3086 ``no_values`` indicates to return only the keys, without values.
3087
3088 For more information, see https://redis.io/commands/hscan
3089 """
3090 pieces: list[EncodableT] = [name, cursor]
3091 if match is not None:
3092 pieces.extend([b"MATCH", match])
3093 if count is not None:
3094 pieces.extend([b"COUNT", count])
3095 if no_values is not None:
3096 pieces.extend([b"NOVALUES"])
3097 return self.execute_command("HSCAN", *pieces, no_values=no_values)
3098
3099 def hscan_iter(
3100 self,
3101 name: str,
3102 match: Union[PatternT, None] = None,
3103 count: Optional[int] = None,
3104 no_values: Union[bool, None] = None,
3105 ) -> Iterator:
3106 """
3107 Make an iterator using the HSCAN command so that the client doesn't
3108 need to remember the cursor position.
3109
3110 ``match`` allows for filtering the keys by pattern
3111
3112 ``count`` allows for hint the minimum number of returns
3113
3114 ``no_values`` indicates to return only the keys, without values
3115 """
3116 cursor = "0"
3117 while cursor != 0:
3118 cursor, data = self.hscan(
3119 name, cursor=cursor, match=match, count=count, no_values=no_values
3120 )
3121 if no_values:
3122 yield from data
3123 else:
3124 yield from data.items()
3125
3126 def zscan(
3127 self,
3128 name: KeyT,
3129 cursor: int = 0,
3130 match: Union[PatternT, None] = None,
3131 count: Optional[int] = None,
3132 score_cast_func: Union[type, Callable] = float,
3133 ) -> ResponseT:
3134 """
3135 Incrementally return lists of elements in a sorted set. Also return a
3136 cursor indicating the scan position.
3137
3138 ``match`` allows for filtering the keys by pattern
3139
3140 ``count`` allows for hint the minimum number of returns
3141
3142 ``score_cast_func`` a callable used to cast the score return value
3143
3144 For more information, see https://redis.io/commands/zscan
3145 """
3146 pieces = [name, cursor]
3147 if match is not None:
3148 pieces.extend([b"MATCH", match])
3149 if count is not None:
3150 pieces.extend([b"COUNT", count])
3151 options = {"score_cast_func": score_cast_func}
3152 return self.execute_command("ZSCAN", *pieces, **options)
3153
3154 def zscan_iter(
3155 self,
3156 name: KeyT,
3157 match: Union[PatternT, None] = None,
3158 count: Optional[int] = None,
3159 score_cast_func: Union[type, Callable] = float,
3160 ) -> Iterator:
3161 """
3162 Make an iterator using the ZSCAN command so that the client doesn't
3163 need to remember the cursor position.
3164
3165 ``match`` allows for filtering the keys by pattern
3166
3167 ``count`` allows for hint the minimum number of returns
3168
3169 ``score_cast_func`` a callable used to cast the score return value
3170 """
3171 cursor = "0"
3172 while cursor != 0:
3173 cursor, data = self.zscan(
3174 name,
3175 cursor=cursor,
3176 match=match,
3177 count=count,
3178 score_cast_func=score_cast_func,
3179 )
3180 yield from data
3181
3182
3183class AsyncScanCommands(ScanCommands):
3184 async def scan_iter(
3185 self,
3186 match: Union[PatternT, None] = None,
3187 count: Optional[int] = None,
3188 _type: Optional[str] = None,
3189 **kwargs,
3190 ) -> AsyncIterator:
3191 """
3192 Make an iterator using the SCAN command so that the client doesn't
3193 need to remember the cursor position.
3194
3195 ``match`` allows for filtering the keys by pattern
3196
3197 ``count`` provides a hint to Redis about the number of keys to
3198 return per batch.
3199
3200 ``_type`` filters the returned values by a particular Redis type.
3201 Stock Redis instances allow for the following types:
3202 HASH, LIST, SET, STREAM, STRING, ZSET
3203 Additionally, Redis modules can expose other types as well.
3204 """
3205 cursor = "0"
3206 while cursor != 0:
3207 cursor, data = await self.scan(
3208 cursor=cursor, match=match, count=count, _type=_type, **kwargs
3209 )
3210 for d in data:
3211 yield d
3212
3213 async def sscan_iter(
3214 self,
3215 name: KeyT,
3216 match: Union[PatternT, None] = None,
3217 count: Optional[int] = None,
3218 ) -> AsyncIterator:
3219 """
3220 Make an iterator using the SSCAN command so that the client doesn't
3221 need to remember the cursor position.
3222
3223 ``match`` allows for filtering the keys by pattern
3224
3225 ``count`` allows for hint the minimum number of returns
3226 """
3227 cursor = "0"
3228 while cursor != 0:
3229 cursor, data = await self.sscan(
3230 name, cursor=cursor, match=match, count=count
3231 )
3232 for d in data:
3233 yield d
3234
3235 async def hscan_iter(
3236 self,
3237 name: str,
3238 match: Union[PatternT, None] = None,
3239 count: Optional[int] = None,
3240 no_values: Union[bool, None] = None,
3241 ) -> AsyncIterator:
3242 """
3243 Make an iterator using the HSCAN command so that the client doesn't
3244 need to remember the cursor position.
3245
3246 ``match`` allows for filtering the keys by pattern
3247
3248 ``count`` allows for hint the minimum number of returns
3249
3250 ``no_values`` indicates to return only the keys, without values
3251 """
3252 cursor = "0"
3253 while cursor != 0:
3254 cursor, data = await self.hscan(
3255 name, cursor=cursor, match=match, count=count, no_values=no_values
3256 )
3257 if no_values:
3258 for it in data:
3259 yield it
3260 else:
3261 for it in data.items():
3262 yield it
3263
3264 async def zscan_iter(
3265 self,
3266 name: KeyT,
3267 match: Union[PatternT, None] = None,
3268 count: Optional[int] = None,
3269 score_cast_func: Union[type, Callable] = float,
3270 ) -> AsyncIterator:
3271 """
3272 Make an iterator using the ZSCAN command so that the client doesn't
3273 need to remember the cursor position.
3274
3275 ``match`` allows for filtering the keys by pattern
3276
3277 ``count`` allows for hint the minimum number of returns
3278
3279 ``score_cast_func`` a callable used to cast the score return value
3280 """
3281 cursor = "0"
3282 while cursor != 0:
3283 cursor, data = await self.zscan(
3284 name,
3285 cursor=cursor,
3286 match=match,
3287 count=count,
3288 score_cast_func=score_cast_func,
3289 )
3290 for d in data:
3291 yield d
3292
3293
3294class SetCommands(CommandsProtocol):
3295 """
3296 Redis commands for Set data type.
3297 see: https://redis.io/topics/data-types#sets
3298 """
3299
3300 def sadd(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
3301 """
3302 Add ``value(s)`` to set ``name``
3303
3304 For more information, see https://redis.io/commands/sadd
3305 """
3306 return self.execute_command("SADD", name, *values)
3307
3308 def scard(self, name: KeyT) -> Union[Awaitable[int], int]:
3309 """
3310 Return the number of elements in set ``name``
3311
3312 For more information, see https://redis.io/commands/scard
3313 """
3314 return self.execute_command("SCARD", name, keys=[name])
3315
3316 def sdiff(self, keys: List, *args: List) -> Union[Awaitable[list], list]:
3317 """
3318 Return the difference of sets specified by ``keys``
3319
3320 For more information, see https://redis.io/commands/sdiff
3321 """
3322 args = list_or_args(keys, args)
3323 return self.execute_command("SDIFF", *args, keys=args)
3324
3325 def sdiffstore(
3326 self, dest: str, keys: List, *args: List
3327 ) -> Union[Awaitable[int], int]:
3328 """
3329 Store the difference of sets specified by ``keys`` into a new
3330 set named ``dest``. Returns the number of keys in the new set.
3331
3332 For more information, see https://redis.io/commands/sdiffstore
3333 """
3334 args = list_or_args(keys, args)
3335 return self.execute_command("SDIFFSTORE", dest, *args)
3336
3337 def sinter(self, keys: List, *args: List) -> Union[Awaitable[list], list]:
3338 """
3339 Return the intersection of sets specified by ``keys``
3340
3341 For more information, see https://redis.io/commands/sinter
3342 """
3343 args = list_or_args(keys, args)
3344 return self.execute_command("SINTER", *args, keys=args)
3345
3346 def sintercard(
3347 self, numkeys: int, keys: List[KeyT], limit: int = 0
3348 ) -> Union[Awaitable[int], int]:
3349 """
3350 Return the cardinality of the intersect of multiple sets specified by ``keys``.
3351
3352 When LIMIT provided (defaults to 0 and means unlimited), if the intersection
3353 cardinality reaches limit partway through the computation, the algorithm will
3354 exit and yield limit as the cardinality
3355
3356 For more information, see https://redis.io/commands/sintercard
3357 """
3358 args = [numkeys, *keys, "LIMIT", limit]
3359 return self.execute_command("SINTERCARD", *args, keys=keys)
3360
3361 def sinterstore(
3362 self, dest: KeyT, keys: List, *args: List
3363 ) -> Union[Awaitable[int], int]:
3364 """
3365 Store the intersection of sets specified by ``keys`` into a new
3366 set named ``dest``. Returns the number of keys in the new set.
3367
3368 For more information, see https://redis.io/commands/sinterstore
3369 """
3370 args = list_or_args(keys, args)
3371 return self.execute_command("SINTERSTORE", dest, *args)
3372
3373 def sismember(
3374 self, name: KeyT, value: str
3375 ) -> Union[Awaitable[Union[Literal[0], Literal[1]]], Union[Literal[0], Literal[1]]]:
3376 """
3377 Return whether ``value`` is a member of set ``name``:
3378 - 1 if the value is a member of the set.
3379 - 0 if the value is not a member of the set or if key does not exist.
3380
3381 For more information, see https://redis.io/commands/sismember
3382 """
3383 return self.execute_command("SISMEMBER", name, value, keys=[name])
3384
3385 def smembers(self, name: KeyT) -> Union[Awaitable[Set], Set]:
3386 """
3387 Return all members of the set ``name``
3388
3389 For more information, see https://redis.io/commands/smembers
3390 """
3391 return self.execute_command("SMEMBERS", name, keys=[name])
3392
3393 def smismember(
3394 self, name: KeyT, values: List, *args: List
3395 ) -> Union[
3396 Awaitable[List[Union[Literal[0], Literal[1]]]],
3397 List[Union[Literal[0], Literal[1]]],
3398 ]:
3399 """
3400 Return whether each value in ``values`` is a member of the set ``name``
3401 as a list of ``int`` in the order of ``values``:
3402 - 1 if the value is a member of the set.
3403 - 0 if the value is not a member of the set or if key does not exist.
3404
3405 For more information, see https://redis.io/commands/smismember
3406 """
3407 args = list_or_args(values, args)
3408 return self.execute_command("SMISMEMBER", name, *args, keys=[name])
3409
3410 def smove(self, src: KeyT, dst: KeyT, value: str) -> Union[Awaitable[bool], bool]:
3411 """
3412 Move ``value`` from set ``src`` to set ``dst`` atomically
3413
3414 For more information, see https://redis.io/commands/smove
3415 """
3416 return self.execute_command("SMOVE", src, dst, value)
3417
3418 def spop(self, name: KeyT, count: Optional[int] = None) -> Union[str, List, None]:
3419 """
3420 Remove and return a random member of set ``name``
3421
3422 For more information, see https://redis.io/commands/spop
3423 """
3424 args = (count is not None) and [count] or []
3425 return self.execute_command("SPOP", name, *args)
3426
3427 def srandmember(
3428 self, name: KeyT, number: Optional[int] = None
3429 ) -> Union[str, List, None]:
3430 """
3431 If ``number`` is None, returns a random member of set ``name``.
3432
3433 If ``number`` is supplied, returns a list of ``number`` random
3434 members of set ``name``. Note this is only available when running
3435 Redis 2.6+.
3436
3437 For more information, see https://redis.io/commands/srandmember
3438 """
3439 args = (number is not None) and [number] or []
3440 return self.execute_command("SRANDMEMBER", name, *args)
3441
3442 def srem(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
3443 """
3444 Remove ``values`` from set ``name``
3445
3446 For more information, see https://redis.io/commands/srem
3447 """
3448 return self.execute_command("SREM", name, *values)
3449
3450 def sunion(self, keys: List, *args: List) -> Union[Awaitable[List], List]:
3451 """
3452 Return the union of sets specified by ``keys``
3453
3454 For more information, see https://redis.io/commands/sunion
3455 """
3456 args = list_or_args(keys, args)
3457 return self.execute_command("SUNION", *args, keys=args)
3458
3459 def sunionstore(
3460 self, dest: KeyT, keys: List, *args: List
3461 ) -> Union[Awaitable[int], int]:
3462 """
3463 Store the union of sets specified by ``keys`` into a new
3464 set named ``dest``. Returns the number of keys in the new set.
3465
3466 For more information, see https://redis.io/commands/sunionstore
3467 """
3468 args = list_or_args(keys, args)
3469 return self.execute_command("SUNIONSTORE", dest, *args)
3470
3471
3472AsyncSetCommands = SetCommands
3473
3474
3475class StreamCommands(CommandsProtocol):
3476 """
3477 Redis commands for Stream data type.
3478 see: https://redis.io/topics/streams-intro
3479 """
3480
3481 def xack(self, name: KeyT, groupname: GroupT, *ids: StreamIdT) -> ResponseT:
3482 """
3483 Acknowledges the successful processing of one or more messages.
3484
3485 Args:
3486 name: name of the stream.
3487 groupname: name of the consumer group.
3488 *ids: message ids to acknowledge.
3489
3490 For more information, see https://redis.io/commands/xack
3491 """
3492 return self.execute_command("XACK", name, groupname, *ids)
3493
3494 def xackdel(
3495 self,
3496 name: KeyT,
3497 groupname: GroupT,
3498 *ids: StreamIdT,
3499 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF",
3500 ) -> ResponseT:
3501 """
3502 Combines the functionality of XACK and XDEL. Acknowledges the specified
3503 message IDs in the given consumer group and simultaneously attempts to
3504 delete the corresponding entries from the stream.
3505 """
3506 if not ids:
3507 raise DataError("XACKDEL requires at least one message ID")
3508
3509 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
3510 raise DataError("XACKDEL ref_policy must be one of: KEEPREF, DELREF, ACKED")
3511
3512 pieces = [name, groupname, ref_policy, "IDS", len(ids)]
3513 pieces.extend(ids)
3514 return self.execute_command("XACKDEL", *pieces)
3515
3516 def xadd(
3517 self,
3518 name: KeyT,
3519 fields: Dict[FieldT, EncodableT],
3520 id: StreamIdT = "*",
3521 maxlen: Optional[int] = None,
3522 approximate: bool = True,
3523 nomkstream: bool = False,
3524 minid: Union[StreamIdT, None] = None,
3525 limit: Optional[int] = None,
3526 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None,
3527 ) -> ResponseT:
3528 """
3529 Add to a stream.
3530 name: name of the stream
3531 fields: dict of field/value pairs to insert into the stream
3532 id: Location to insert this record. By default it is appended.
3533 maxlen: truncate old stream members beyond this size.
3534 Can't be specified with minid.
3535 approximate: actual stream length may be slightly more than maxlen
3536 nomkstream: When set to true, do not make a stream
3537 minid: the minimum id in the stream to query.
3538 Can't be specified with maxlen.
3539 limit: specifies the maximum number of entries to retrieve
3540 ref_policy: optional reference policy for consumer groups when trimming:
3541 - KEEPREF (default): When trimming, preserves references in consumer groups' PEL
3542 - DELREF: When trimming, removes all references from consumer groups' PEL
3543 - ACKED: When trimming, only removes entries acknowledged by all consumer groups
3544
3545 For more information, see https://redis.io/commands/xadd
3546 """
3547 pieces: list[EncodableT] = []
3548 if maxlen is not None and minid is not None:
3549 raise DataError("Only one of ```maxlen``` or ```minid``` may be specified")
3550
3551 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
3552 raise DataError("XADD ref_policy must be one of: KEEPREF, DELREF, ACKED")
3553
3554 if maxlen is not None:
3555 if not isinstance(maxlen, int) or maxlen < 0:
3556 raise DataError("XADD maxlen must be non-negative integer")
3557 pieces.append(b"MAXLEN")
3558 if approximate:
3559 pieces.append(b"~")
3560 pieces.append(str(maxlen))
3561 if minid is not None:
3562 pieces.append(b"MINID")
3563 if approximate:
3564 pieces.append(b"~")
3565 pieces.append(minid)
3566 if limit is not None:
3567 pieces.extend([b"LIMIT", limit])
3568 if nomkstream:
3569 pieces.append(b"NOMKSTREAM")
3570 if ref_policy is not None:
3571 pieces.append(ref_policy)
3572 pieces.append(id)
3573 if not isinstance(fields, dict) or len(fields) == 0:
3574 raise DataError("XADD fields must be a non-empty dict")
3575 for pair in fields.items():
3576 pieces.extend(pair)
3577 return self.execute_command("XADD", name, *pieces)
3578
3579 def xautoclaim(
3580 self,
3581 name: KeyT,
3582 groupname: GroupT,
3583 consumername: ConsumerT,
3584 min_idle_time: int,
3585 start_id: StreamIdT = "0-0",
3586 count: Optional[int] = None,
3587 justid: bool = False,
3588 ) -> ResponseT:
3589 """
3590 Transfers ownership of pending stream entries that match the specified
3591 criteria. Conceptually, equivalent to calling XPENDING and then XCLAIM,
3592 but provides a more straightforward way to deal with message delivery
3593 failures via SCAN-like semantics.
3594 name: name of the stream.
3595 groupname: name of the consumer group.
3596 consumername: name of a consumer that claims the message.
3597 min_idle_time: filter messages that were idle less than this amount of
3598 milliseconds.
3599 start_id: filter messages with equal or greater ID.
3600 count: optional integer, upper limit of the number of entries that the
3601 command attempts to claim. Set to 100 by default.
3602 justid: optional boolean, false by default. Return just an array of IDs
3603 of messages successfully claimed, without returning the actual message
3604
3605 For more information, see https://redis.io/commands/xautoclaim
3606 """
3607 try:
3608 if int(min_idle_time) < 0:
3609 raise DataError(
3610 "XAUTOCLAIM min_idle_time must be a nonnegative integer"
3611 )
3612 except TypeError:
3613 pass
3614
3615 kwargs = {}
3616 pieces = [name, groupname, consumername, min_idle_time, start_id]
3617
3618 try:
3619 if int(count) < 0:
3620 raise DataError("XPENDING count must be a integer >= 0")
3621 pieces.extend([b"COUNT", count])
3622 except TypeError:
3623 pass
3624 if justid:
3625 pieces.append(b"JUSTID")
3626 kwargs["parse_justid"] = True
3627
3628 return self.execute_command("XAUTOCLAIM", *pieces, **kwargs)
3629
3630 def xclaim(
3631 self,
3632 name: KeyT,
3633 groupname: GroupT,
3634 consumername: ConsumerT,
3635 min_idle_time: int,
3636 message_ids: Union[List[StreamIdT], Tuple[StreamIdT]],
3637 idle: Optional[int] = None,
3638 time: Optional[int] = None,
3639 retrycount: Optional[int] = None,
3640 force: bool = False,
3641 justid: bool = False,
3642 ) -> ResponseT:
3643 """
3644 Changes the ownership of a pending message.
3645
3646 name: name of the stream.
3647
3648 groupname: name of the consumer group.
3649
3650 consumername: name of a consumer that claims the message.
3651
3652 min_idle_time: filter messages that were idle less than this amount of
3653 milliseconds
3654
3655 message_ids: non-empty list or tuple of message IDs to claim
3656
3657 idle: optional. Set the idle time (last time it was delivered) of the
3658 message in ms
3659
3660 time: optional integer. This is the same as idle but instead of a
3661 relative amount of milliseconds, it sets the idle time to a specific
3662 Unix time (in milliseconds).
3663
3664 retrycount: optional integer. set the retry counter to the specified
3665 value. This counter is incremented every time a message is delivered
3666 again.
3667
3668 force: optional boolean, false by default. Creates the pending message
3669 entry in the PEL even if certain specified IDs are not already in the
3670 PEL assigned to a different client.
3671
3672 justid: optional boolean, false by default. Return just an array of IDs
3673 of messages successfully claimed, without returning the actual message
3674
3675 For more information, see https://redis.io/commands/xclaim
3676 """
3677 if not isinstance(min_idle_time, int) or min_idle_time < 0:
3678 raise DataError("XCLAIM min_idle_time must be a non negative integer")
3679 if not isinstance(message_ids, (list, tuple)) or not message_ids:
3680 raise DataError(
3681 "XCLAIM message_ids must be a non empty list or "
3682 "tuple of message IDs to claim"
3683 )
3684
3685 kwargs = {}
3686 pieces: list[EncodableT] = [name, groupname, consumername, str(min_idle_time)]
3687 pieces.extend(list(message_ids))
3688
3689 if idle is not None:
3690 if not isinstance(idle, int):
3691 raise DataError("XCLAIM idle must be an integer")
3692 pieces.extend((b"IDLE", str(idle)))
3693 if time is not None:
3694 if not isinstance(time, int):
3695 raise DataError("XCLAIM time must be an integer")
3696 pieces.extend((b"TIME", str(time)))
3697 if retrycount is not None:
3698 if not isinstance(retrycount, int):
3699 raise DataError("XCLAIM retrycount must be an integer")
3700 pieces.extend((b"RETRYCOUNT", str(retrycount)))
3701
3702 if force:
3703 if not isinstance(force, bool):
3704 raise DataError("XCLAIM force must be a boolean")
3705 pieces.append(b"FORCE")
3706 if justid:
3707 if not isinstance(justid, bool):
3708 raise DataError("XCLAIM justid must be a boolean")
3709 pieces.append(b"JUSTID")
3710 kwargs["parse_justid"] = True
3711 return self.execute_command("XCLAIM", *pieces, **kwargs)
3712
3713 def xdel(self, name: KeyT, *ids: StreamIdT) -> ResponseT:
3714 """
3715 Deletes one or more messages from a stream.
3716
3717 Args:
3718 name: name of the stream.
3719 *ids: message ids to delete.
3720
3721 For more information, see https://redis.io/commands/xdel
3722 """
3723 return self.execute_command("XDEL", name, *ids)
3724
3725 def xdelex(
3726 self,
3727 name: KeyT,
3728 *ids: StreamIdT,
3729 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF",
3730 ) -> ResponseT:
3731 """
3732 Extended version of XDEL that provides more control over how message entries
3733 are deleted concerning consumer groups.
3734 """
3735 if not ids:
3736 raise DataError("XDELEX requires at least one message ID")
3737
3738 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
3739 raise DataError("XDELEX ref_policy must be one of: KEEPREF, DELREF, ACKED")
3740
3741 pieces = [name, ref_policy, "IDS", len(ids)]
3742 pieces.extend(ids)
3743 return self.execute_command("XDELEX", *pieces)
3744
3745 def xgroup_create(
3746 self,
3747 name: KeyT,
3748 groupname: GroupT,
3749 id: StreamIdT = "$",
3750 mkstream: bool = False,
3751 entries_read: Optional[int] = None,
3752 ) -> ResponseT:
3753 """
3754 Create a new consumer group associated with a stream.
3755 name: name of the stream.
3756 groupname: name of the consumer group.
3757 id: ID of the last item in the stream to consider already delivered.
3758
3759 For more information, see https://redis.io/commands/xgroup-create
3760 """
3761 pieces: list[EncodableT] = ["XGROUP CREATE", name, groupname, id]
3762 if mkstream:
3763 pieces.append(b"MKSTREAM")
3764 if entries_read is not None:
3765 pieces.extend(["ENTRIESREAD", entries_read])
3766
3767 return self.execute_command(*pieces)
3768
3769 def xgroup_delconsumer(
3770 self, name: KeyT, groupname: GroupT, consumername: ConsumerT
3771 ) -> ResponseT:
3772 """
3773 Remove a specific consumer from a consumer group.
3774 Returns the number of pending messages that the consumer had before it
3775 was deleted.
3776 name: name of the stream.
3777 groupname: name of the consumer group.
3778 consumername: name of consumer to delete
3779
3780 For more information, see https://redis.io/commands/xgroup-delconsumer
3781 """
3782 return self.execute_command("XGROUP DELCONSUMER", name, groupname, consumername)
3783
3784 def xgroup_destroy(self, name: KeyT, groupname: GroupT) -> ResponseT:
3785 """
3786 Destroy a consumer group.
3787 name: name of the stream.
3788 groupname: name of the consumer group.
3789
3790 For more information, see https://redis.io/commands/xgroup-destroy
3791 """
3792 return self.execute_command("XGROUP DESTROY", name, groupname)
3793
3794 def xgroup_createconsumer(
3795 self, name: KeyT, groupname: GroupT, consumername: ConsumerT
3796 ) -> ResponseT:
3797 """
3798 Consumers in a consumer group are auto-created every time a new
3799 consumer name is mentioned by some command.
3800 They can be explicitly created by using this command.
3801 name: name of the stream.
3802 groupname: name of the consumer group.
3803 consumername: name of consumer to create.
3804
3805 See: https://redis.io/commands/xgroup-createconsumer
3806 """
3807 return self.execute_command(
3808 "XGROUP CREATECONSUMER", name, groupname, consumername
3809 )
3810
3811 def xgroup_setid(
3812 self,
3813 name: KeyT,
3814 groupname: GroupT,
3815 id: StreamIdT,
3816 entries_read: Optional[int] = None,
3817 ) -> ResponseT:
3818 """
3819 Set the consumer group last delivered ID to something else.
3820 name: name of the stream.
3821 groupname: name of the consumer group.
3822 id: ID of the last item in the stream to consider already delivered.
3823
3824 For more information, see https://redis.io/commands/xgroup-setid
3825 """
3826 pieces = [name, groupname, id]
3827 if entries_read is not None:
3828 pieces.extend(["ENTRIESREAD", entries_read])
3829 return self.execute_command("XGROUP SETID", *pieces)
3830
3831 def xinfo_consumers(self, name: KeyT, groupname: GroupT) -> ResponseT:
3832 """
3833 Returns general information about the consumers in the group.
3834 name: name of the stream.
3835 groupname: name of the consumer group.
3836
3837 For more information, see https://redis.io/commands/xinfo-consumers
3838 """
3839 return self.execute_command("XINFO CONSUMERS", name, groupname)
3840
3841 def xinfo_groups(self, name: KeyT) -> ResponseT:
3842 """
3843 Returns general information about the consumer groups of the stream.
3844 name: name of the stream.
3845
3846 For more information, see https://redis.io/commands/xinfo-groups
3847 """
3848 return self.execute_command("XINFO GROUPS", name)
3849
3850 def xinfo_stream(self, name: KeyT, full: bool = False) -> ResponseT:
3851 """
3852 Returns general information about the stream.
3853 name: name of the stream.
3854 full: optional boolean, false by default. Return full summary
3855
3856 For more information, see https://redis.io/commands/xinfo-stream
3857 """
3858 pieces = [name]
3859 options = {}
3860 if full:
3861 pieces.append(b"FULL")
3862 options = {"full": full}
3863 return self.execute_command("XINFO STREAM", *pieces, **options)
3864
3865 def xlen(self, name: KeyT) -> ResponseT:
3866 """
3867 Returns the number of elements in a given stream.
3868
3869 For more information, see https://redis.io/commands/xlen
3870 """
3871 return self.execute_command("XLEN", name, keys=[name])
3872
3873 def xpending(self, name: KeyT, groupname: GroupT) -> ResponseT:
3874 """
3875 Returns information about pending messages of a group.
3876 name: name of the stream.
3877 groupname: name of the consumer group.
3878
3879 For more information, see https://redis.io/commands/xpending
3880 """
3881 return self.execute_command("XPENDING", name, groupname, keys=[name])
3882
3883 def xpending_range(
3884 self,
3885 name: KeyT,
3886 groupname: GroupT,
3887 min: StreamIdT,
3888 max: StreamIdT,
3889 count: int,
3890 consumername: Union[ConsumerT, None] = None,
3891 idle: Optional[int] = None,
3892 ) -> ResponseT:
3893 """
3894 Returns information about pending messages, in a range.
3895
3896 name: name of the stream.
3897 groupname: name of the consumer group.
3898 idle: available from version 6.2. filter entries by their
3899 idle-time, given in milliseconds (optional).
3900 min: minimum stream ID.
3901 max: maximum stream ID.
3902 count: number of messages to return
3903 consumername: name of a consumer to filter by (optional).
3904 """
3905 if {min, max, count} == {None}:
3906 if idle is not None or consumername is not None:
3907 raise DataError(
3908 "if XPENDING is provided with idle time"
3909 " or consumername, it must be provided"
3910 " with min, max and count parameters"
3911 )
3912 return self.xpending(name, groupname)
3913
3914 pieces = [name, groupname]
3915 if min is None or max is None or count is None:
3916 raise DataError(
3917 "XPENDING must be provided with min, max "
3918 "and count parameters, or none of them."
3919 )
3920 # idle
3921 try:
3922 if int(idle) < 0:
3923 raise DataError("XPENDING idle must be a integer >= 0")
3924 pieces.extend(["IDLE", idle])
3925 except TypeError:
3926 pass
3927 # count
3928 try:
3929 if int(count) < 0:
3930 raise DataError("XPENDING count must be a integer >= 0")
3931 pieces.extend([min, max, count])
3932 except TypeError:
3933 pass
3934 # consumername
3935 if consumername:
3936 pieces.append(consumername)
3937
3938 return self.execute_command("XPENDING", *pieces, parse_detail=True)
3939
3940 def xrange(
3941 self,
3942 name: KeyT,
3943 min: StreamIdT = "-",
3944 max: StreamIdT = "+",
3945 count: Optional[int] = None,
3946 ) -> ResponseT:
3947 """
3948 Read stream values within an interval.
3949
3950 name: name of the stream.
3951
3952 start: first stream ID. defaults to '-',
3953 meaning the earliest available.
3954
3955 finish: last stream ID. defaults to '+',
3956 meaning the latest available.
3957
3958 count: if set, only return this many items, beginning with the
3959 earliest available.
3960
3961 For more information, see https://redis.io/commands/xrange
3962 """
3963 pieces = [min, max]
3964 if count is not None:
3965 if not isinstance(count, int) or count < 1:
3966 raise DataError("XRANGE count must be a positive integer")
3967 pieces.append(b"COUNT")
3968 pieces.append(str(count))
3969
3970 return self.execute_command("XRANGE", name, *pieces, keys=[name])
3971
3972 def xread(
3973 self,
3974 streams: Dict[KeyT, StreamIdT],
3975 count: Optional[int] = None,
3976 block: Optional[int] = None,
3977 ) -> ResponseT:
3978 """
3979 Block and monitor multiple streams for new data.
3980
3981 streams: a dict of stream names to stream IDs, where
3982 IDs indicate the last ID already seen.
3983
3984 count: if set, only return this many items, beginning with the
3985 earliest available.
3986
3987 block: number of milliseconds to wait, if nothing already present.
3988
3989 For more information, see https://redis.io/commands/xread
3990 """
3991 pieces = []
3992 if block is not None:
3993 if not isinstance(block, int) or block < 0:
3994 raise DataError("XREAD block must be a non-negative integer")
3995 pieces.append(b"BLOCK")
3996 pieces.append(str(block))
3997 if count is not None:
3998 if not isinstance(count, int) or count < 1:
3999 raise DataError("XREAD count must be a positive integer")
4000 pieces.append(b"COUNT")
4001 pieces.append(str(count))
4002 if not isinstance(streams, dict) or len(streams) == 0:
4003 raise DataError("XREAD streams must be a non empty dict")
4004 pieces.append(b"STREAMS")
4005 keys, values = zip(*streams.items())
4006 pieces.extend(keys)
4007 pieces.extend(values)
4008 return self.execute_command("XREAD", *pieces, keys=keys)
4009
4010 def xreadgroup(
4011 self,
4012 groupname: str,
4013 consumername: str,
4014 streams: Dict[KeyT, StreamIdT],
4015 count: Optional[int] = None,
4016 block: Optional[int] = None,
4017 noack: bool = False,
4018 ) -> ResponseT:
4019 """
4020 Read from a stream via a consumer group.
4021
4022 groupname: name of the consumer group.
4023
4024 consumername: name of the requesting consumer.
4025
4026 streams: a dict of stream names to stream IDs, where
4027 IDs indicate the last ID already seen.
4028
4029 count: if set, only return this many items, beginning with the
4030 earliest available.
4031
4032 block: number of milliseconds to wait, if nothing already present.
4033 noack: do not add messages to the PEL
4034
4035 For more information, see https://redis.io/commands/xreadgroup
4036 """
4037 pieces: list[EncodableT] = [b"GROUP", groupname, consumername]
4038 if count is not None:
4039 if not isinstance(count, int) or count < 1:
4040 raise DataError("XREADGROUP count must be a positive integer")
4041 pieces.append(b"COUNT")
4042 pieces.append(str(count))
4043 if block is not None:
4044 if not isinstance(block, int) or block < 0:
4045 raise DataError("XREADGROUP block must be a non-negative integer")
4046 pieces.append(b"BLOCK")
4047 pieces.append(str(block))
4048 if noack:
4049 pieces.append(b"NOACK")
4050 if not isinstance(streams, dict) or len(streams) == 0:
4051 raise DataError("XREADGROUP streams must be a non empty dict")
4052 pieces.append(b"STREAMS")
4053 pieces.extend(streams.keys())
4054 pieces.extend(streams.values())
4055 return self.execute_command("XREADGROUP", *pieces)
4056
4057 def xrevrange(
4058 self,
4059 name: KeyT,
4060 max: StreamIdT = "+",
4061 min: StreamIdT = "-",
4062 count: Optional[int] = None,
4063 ) -> ResponseT:
4064 """
4065 Read stream values within an interval, in reverse order.
4066
4067 name: name of the stream
4068
4069 start: first stream ID. defaults to '+',
4070 meaning the latest available.
4071
4072 finish: last stream ID. defaults to '-',
4073 meaning the earliest available.
4074
4075 count: if set, only return this many items, beginning with the
4076 latest available.
4077
4078 For more information, see https://redis.io/commands/xrevrange
4079 """
4080 pieces: list[EncodableT] = [max, min]
4081 if count is not None:
4082 if not isinstance(count, int) or count < 1:
4083 raise DataError("XREVRANGE count must be a positive integer")
4084 pieces.append(b"COUNT")
4085 pieces.append(str(count))
4086
4087 return self.execute_command("XREVRANGE", name, *pieces, keys=[name])
4088
4089 def xtrim(
4090 self,
4091 name: KeyT,
4092 maxlen: Optional[int] = None,
4093 approximate: bool = True,
4094 minid: Union[StreamIdT, None] = None,
4095 limit: Optional[int] = None,
4096 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None,
4097 ) -> ResponseT:
4098 """
4099 Trims old messages from a stream.
4100 name: name of the stream.
4101 maxlen: truncate old stream messages beyond this size
4102 Can't be specified with minid.
4103 approximate: actual stream length may be slightly more than maxlen
4104 minid: the minimum id in the stream to query
4105 Can't be specified with maxlen.
4106 limit: specifies the maximum number of entries to retrieve
4107 ref_policy: optional reference policy for consumer groups:
4108 - KEEPREF (default): Trims entries but preserves references in consumer groups' PEL
4109 - DELREF: Trims entries and removes all references from consumer groups' PEL
4110 - ACKED: Only trims entries that were read and acknowledged by all consumer groups
4111
4112 For more information, see https://redis.io/commands/xtrim
4113 """
4114 pieces: list[EncodableT] = []
4115 if maxlen is not None and minid is not None:
4116 raise DataError("Only one of ``maxlen`` or ``minid`` may be specified")
4117
4118 if maxlen is None and minid is None:
4119 raise DataError("One of ``maxlen`` or ``minid`` must be specified")
4120
4121 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
4122 raise DataError("XTRIM ref_policy must be one of: KEEPREF, DELREF, ACKED")
4123
4124 if maxlen is not None:
4125 pieces.append(b"MAXLEN")
4126 if minid is not None:
4127 pieces.append(b"MINID")
4128 if approximate:
4129 pieces.append(b"~")
4130 if maxlen is not None:
4131 pieces.append(maxlen)
4132 if minid is not None:
4133 pieces.append(minid)
4134 if limit is not None:
4135 pieces.append(b"LIMIT")
4136 pieces.append(limit)
4137 if ref_policy is not None:
4138 pieces.append(ref_policy)
4139
4140 return self.execute_command("XTRIM", name, *pieces)
4141
4142
4143AsyncStreamCommands = StreamCommands
4144
4145
4146class SortedSetCommands(CommandsProtocol):
4147 """
4148 Redis commands for Sorted Sets data type.
4149 see: https://redis.io/topics/data-types-intro#redis-sorted-sets
4150 """
4151
4152 def zadd(
4153 self,
4154 name: KeyT,
4155 mapping: Mapping[AnyKeyT, EncodableT],
4156 nx: bool = False,
4157 xx: bool = False,
4158 ch: bool = False,
4159 incr: bool = False,
4160 gt: bool = False,
4161 lt: bool = False,
4162 ) -> ResponseT:
4163 """
4164 Set any number of element-name, score pairs to the key ``name``. Pairs
4165 are specified as a dict of element-names keys to score values.
4166
4167 ``nx`` forces ZADD to only create new elements and not to update
4168 scores for elements that already exist.
4169
4170 ``xx`` forces ZADD to only update scores of elements that already
4171 exist. New elements will not be added.
4172
4173 ``ch`` modifies the return value to be the numbers of elements changed.
4174 Changed elements include new elements that were added and elements
4175 whose scores changed.
4176
4177 ``incr`` modifies ZADD to behave like ZINCRBY. In this mode only a
4178 single element/score pair can be specified and the score is the amount
4179 the existing score will be incremented by. When using this mode the
4180 return value of ZADD will be the new score of the element.
4181
4182 ``lt`` only updates existing elements if the new score is less than
4183 the current score. This flag doesn't prevent adding new elements.
4184
4185 ``gt`` only updates existing elements if the new score is greater than
4186 the current score. This flag doesn't prevent adding new elements.
4187
4188 The return value of ZADD varies based on the mode specified. With no
4189 options, ZADD returns the number of new elements added to the sorted
4190 set.
4191
4192 ``nx``, ``lt``, and ``gt`` are mutually exclusive options.
4193
4194 See: https://redis.io/commands/ZADD
4195 """
4196 if not mapping:
4197 raise DataError("ZADD requires at least one element/score pair")
4198 if nx and xx:
4199 raise DataError("ZADD allows either 'nx' or 'xx', not both")
4200 if gt and lt:
4201 raise DataError("ZADD allows either 'gt' or 'lt', not both")
4202 if incr and len(mapping) != 1:
4203 raise DataError(
4204 "ZADD option 'incr' only works when passing a single element/score pair"
4205 )
4206 if nx and (gt or lt):
4207 raise DataError("Only one of 'nx', 'lt', or 'gr' may be defined.")
4208
4209 pieces: list[EncodableT] = []
4210 options = {}
4211 if nx:
4212 pieces.append(b"NX")
4213 if xx:
4214 pieces.append(b"XX")
4215 if ch:
4216 pieces.append(b"CH")
4217 if incr:
4218 pieces.append(b"INCR")
4219 options["as_score"] = True
4220 if gt:
4221 pieces.append(b"GT")
4222 if lt:
4223 pieces.append(b"LT")
4224 for pair in mapping.items():
4225 pieces.append(pair[1])
4226 pieces.append(pair[0])
4227 return self.execute_command("ZADD", name, *pieces, **options)
4228
4229 def zcard(self, name: KeyT) -> ResponseT:
4230 """
4231 Return the number of elements in the sorted set ``name``
4232
4233 For more information, see https://redis.io/commands/zcard
4234 """
4235 return self.execute_command("ZCARD", name, keys=[name])
4236
4237 def zcount(self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT) -> ResponseT:
4238 """
4239 Returns the number of elements in the sorted set at key ``name`` with
4240 a score between ``min`` and ``max``.
4241
4242 For more information, see https://redis.io/commands/zcount
4243 """
4244 return self.execute_command("ZCOUNT", name, min, max, keys=[name])
4245
4246 def zdiff(self, keys: KeysT, withscores: bool = False) -> ResponseT:
4247 """
4248 Returns the difference between the first and all successive input
4249 sorted sets provided in ``keys``.
4250
4251 For more information, see https://redis.io/commands/zdiff
4252 """
4253 pieces = [len(keys), *keys]
4254 if withscores:
4255 pieces.append("WITHSCORES")
4256 return self.execute_command("ZDIFF", *pieces, keys=keys)
4257
4258 def zdiffstore(self, dest: KeyT, keys: KeysT) -> ResponseT:
4259 """
4260 Computes the difference between the first and all successive input
4261 sorted sets provided in ``keys`` and stores the result in ``dest``.
4262
4263 For more information, see https://redis.io/commands/zdiffstore
4264 """
4265 pieces = [len(keys), *keys]
4266 return self.execute_command("ZDIFFSTORE", dest, *pieces)
4267
4268 def zincrby(self, name: KeyT, amount: float, value: EncodableT) -> ResponseT:
4269 """
4270 Increment the score of ``value`` in sorted set ``name`` by ``amount``
4271
4272 For more information, see https://redis.io/commands/zincrby
4273 """
4274 return self.execute_command("ZINCRBY", name, amount, value)
4275
4276 def zinter(
4277 self, keys: KeysT, aggregate: Optional[str] = None, withscores: bool = False
4278 ) -> ResponseT:
4279 """
4280 Return the intersect of multiple sorted sets specified by ``keys``.
4281 With the ``aggregate`` option, it is possible to specify how the
4282 results of the union are aggregated. This option defaults to SUM,
4283 where the score of an element is summed across the inputs where it
4284 exists. When this option is set to either MIN or MAX, the resulting
4285 set will contain the minimum or maximum score of an element across
4286 the inputs where it exists.
4287
4288 For more information, see https://redis.io/commands/zinter
4289 """
4290 return self._zaggregate("ZINTER", None, keys, aggregate, withscores=withscores)
4291
4292 def zinterstore(
4293 self,
4294 dest: KeyT,
4295 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
4296 aggregate: Optional[str] = None,
4297 ) -> ResponseT:
4298 """
4299 Intersect multiple sorted sets specified by ``keys`` into a new
4300 sorted set, ``dest``. Scores in the destination will be aggregated
4301 based on the ``aggregate``. This option defaults to SUM, where the
4302 score of an element is summed across the inputs where it exists.
4303 When this option is set to either MIN or MAX, the resulting set will
4304 contain the minimum or maximum score of an element across the inputs
4305 where it exists.
4306
4307 For more information, see https://redis.io/commands/zinterstore
4308 """
4309 return self._zaggregate("ZINTERSTORE", dest, keys, aggregate)
4310
4311 def zintercard(
4312 self, numkeys: int, keys: List[str], limit: int = 0
4313 ) -> Union[Awaitable[int], int]:
4314 """
4315 Return the cardinality of the intersect of multiple sorted sets
4316 specified by ``keys``.
4317 When LIMIT provided (defaults to 0 and means unlimited), if the intersection
4318 cardinality reaches limit partway through the computation, the algorithm will
4319 exit and yield limit as the cardinality
4320
4321 For more information, see https://redis.io/commands/zintercard
4322 """
4323 args = [numkeys, *keys, "LIMIT", limit]
4324 return self.execute_command("ZINTERCARD", *args, keys=keys)
4325
4326 def zlexcount(self, name, min, max):
4327 """
4328 Return the number of items in the sorted set ``name`` between the
4329 lexicographical range ``min`` and ``max``.
4330
4331 For more information, see https://redis.io/commands/zlexcount
4332 """
4333 return self.execute_command("ZLEXCOUNT", name, min, max, keys=[name])
4334
4335 def zpopmax(self, name: KeyT, count: Optional[int] = None) -> ResponseT:
4336 """
4337 Remove and return up to ``count`` members with the highest scores
4338 from the sorted set ``name``.
4339
4340 For more information, see https://redis.io/commands/zpopmax
4341 """
4342 args = (count is not None) and [count] or []
4343 options = {"withscores": True}
4344 return self.execute_command("ZPOPMAX", name, *args, **options)
4345
4346 def zpopmin(self, name: KeyT, count: Optional[int] = None) -> ResponseT:
4347 """
4348 Remove and return up to ``count`` members with the lowest scores
4349 from the sorted set ``name``.
4350
4351 For more information, see https://redis.io/commands/zpopmin
4352 """
4353 args = (count is not None) and [count] or []
4354 options = {"withscores": True}
4355 return self.execute_command("ZPOPMIN", name, *args, **options)
4356
4357 def zrandmember(
4358 self, key: KeyT, count: Optional[int] = None, withscores: bool = False
4359 ) -> ResponseT:
4360 """
4361 Return a random element from the sorted set value stored at key.
4362
4363 ``count`` if the argument is positive, return an array of distinct
4364 fields. If called with a negative count, the behavior changes and
4365 the command is allowed to return the same field multiple times.
4366 In this case, the number of returned fields is the absolute value
4367 of the specified count.
4368
4369 ``withscores`` The optional WITHSCORES modifier changes the reply so it
4370 includes the respective scores of the randomly selected elements from
4371 the sorted set.
4372
4373 For more information, see https://redis.io/commands/zrandmember
4374 """
4375 params = []
4376 if count is not None:
4377 params.append(count)
4378 if withscores:
4379 params.append("WITHSCORES")
4380
4381 return self.execute_command("ZRANDMEMBER", key, *params)
4382
4383 def bzpopmax(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT:
4384 """
4385 ZPOPMAX a value off of the first non-empty sorted set
4386 named in the ``keys`` list.
4387
4388 If none of the sorted sets in ``keys`` has a value to ZPOPMAX,
4389 then block for ``timeout`` seconds, or until a member gets added
4390 to one of the sorted sets.
4391
4392 If timeout is 0, then block indefinitely.
4393
4394 For more information, see https://redis.io/commands/bzpopmax
4395 """
4396 if timeout is None:
4397 timeout = 0
4398 keys = list_or_args(keys, None)
4399 keys.append(timeout)
4400 return self.execute_command("BZPOPMAX", *keys)
4401
4402 def bzpopmin(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT:
4403 """
4404 ZPOPMIN a value off of the first non-empty sorted set
4405 named in the ``keys`` list.
4406
4407 If none of the sorted sets in ``keys`` has a value to ZPOPMIN,
4408 then block for ``timeout`` seconds, or until a member gets added
4409 to one of the sorted sets.
4410
4411 If timeout is 0, then block indefinitely.
4412
4413 For more information, see https://redis.io/commands/bzpopmin
4414 """
4415 if timeout is None:
4416 timeout = 0
4417 keys: list[EncodableT] = list_or_args(keys, None)
4418 keys.append(timeout)
4419 return self.execute_command("BZPOPMIN", *keys)
4420
4421 def zmpop(
4422 self,
4423 num_keys: int,
4424 keys: List[str],
4425 min: Optional[bool] = False,
4426 max: Optional[bool] = False,
4427 count: Optional[int] = 1,
4428 ) -> Union[Awaitable[list], list]:
4429 """
4430 Pop ``count`` values (default 1) off of the first non-empty sorted set
4431 named in the ``keys`` list.
4432 For more information, see https://redis.io/commands/zmpop
4433 """
4434 args = [num_keys] + keys
4435 if (min and max) or (not min and not max):
4436 raise DataError
4437 elif min:
4438 args.append("MIN")
4439 else:
4440 args.append("MAX")
4441 if count != 1:
4442 args.extend(["COUNT", count])
4443
4444 return self.execute_command("ZMPOP", *args)
4445
4446 def bzmpop(
4447 self,
4448 timeout: float,
4449 numkeys: int,
4450 keys: List[str],
4451 min: Optional[bool] = False,
4452 max: Optional[bool] = False,
4453 count: Optional[int] = 1,
4454 ) -> Optional[list]:
4455 """
4456 Pop ``count`` values (default 1) off of the first non-empty sorted set
4457 named in the ``keys`` list.
4458
4459 If none of the sorted sets in ``keys`` has a value to pop,
4460 then block for ``timeout`` seconds, or until a member gets added
4461 to one of the sorted sets.
4462
4463 If timeout is 0, then block indefinitely.
4464
4465 For more information, see https://redis.io/commands/bzmpop
4466 """
4467 args = [timeout, numkeys, *keys]
4468 if (min and max) or (not min and not max):
4469 raise DataError("Either min or max, but not both must be set")
4470 elif min:
4471 args.append("MIN")
4472 else:
4473 args.append("MAX")
4474 args.extend(["COUNT", count])
4475
4476 return self.execute_command("BZMPOP", *args)
4477
4478 def _zrange(
4479 self,
4480 command,
4481 dest: Union[KeyT, None],
4482 name: KeyT,
4483 start: int,
4484 end: int,
4485 desc: bool = False,
4486 byscore: bool = False,
4487 bylex: bool = False,
4488 withscores: bool = False,
4489 score_cast_func: Union[type, Callable, None] = float,
4490 offset: Optional[int] = None,
4491 num: Optional[int] = None,
4492 ) -> ResponseT:
4493 if byscore and bylex:
4494 raise DataError("``byscore`` and ``bylex`` can not be specified together.")
4495 if (offset is not None and num is None) or (num is not None and offset is None):
4496 raise DataError("``offset`` and ``num`` must both be specified.")
4497 if bylex and withscores:
4498 raise DataError(
4499 "``withscores`` not supported in combination with ``bylex``."
4500 )
4501 pieces = [command]
4502 if dest:
4503 pieces.append(dest)
4504 pieces.extend([name, start, end])
4505 if byscore:
4506 pieces.append("BYSCORE")
4507 if bylex:
4508 pieces.append("BYLEX")
4509 if desc:
4510 pieces.append("REV")
4511 if offset is not None and num is not None:
4512 pieces.extend(["LIMIT", offset, num])
4513 if withscores:
4514 pieces.append("WITHSCORES")
4515 options = {"withscores": withscores, "score_cast_func": score_cast_func}
4516 options["keys"] = [name]
4517 return self.execute_command(*pieces, **options)
4518
4519 def zrange(
4520 self,
4521 name: KeyT,
4522 start: int,
4523 end: int,
4524 desc: bool = False,
4525 withscores: bool = False,
4526 score_cast_func: Union[type, Callable] = float,
4527 byscore: bool = False,
4528 bylex: bool = False,
4529 offset: Optional[int] = None,
4530 num: Optional[int] = None,
4531 ) -> ResponseT:
4532 """
4533 Return a range of values from sorted set ``name`` between
4534 ``start`` and ``end`` sorted in ascending order.
4535
4536 ``start`` and ``end`` can be negative, indicating the end of the range.
4537
4538 ``desc`` a boolean indicating whether to sort the results in reversed
4539 order.
4540
4541 ``withscores`` indicates to return the scores along with the values.
4542 The return type is a list of (value, score) pairs.
4543
4544 ``score_cast_func`` a callable used to cast the score return value.
4545
4546 ``byscore`` when set to True, returns the range of elements from the
4547 sorted set having scores equal or between ``start`` and ``end``.
4548
4549 ``bylex`` when set to True, returns the range of elements from the
4550 sorted set between the ``start`` and ``end`` lexicographical closed
4551 range intervals.
4552 Valid ``start`` and ``end`` must start with ( or [, in order to specify
4553 whether the range interval is exclusive or inclusive, respectively.
4554
4555 ``offset`` and ``num`` are specified, then return a slice of the range.
4556 Can't be provided when using ``bylex``.
4557
4558 For more information, see https://redis.io/commands/zrange
4559 """
4560 # Need to support ``desc`` also when using old redis version
4561 # because it was supported in 3.5.3 (of redis-py)
4562 if not byscore and not bylex and (offset is None and num is None) and desc:
4563 return self.zrevrange(name, start, end, withscores, score_cast_func)
4564
4565 return self._zrange(
4566 "ZRANGE",
4567 None,
4568 name,
4569 start,
4570 end,
4571 desc,
4572 byscore,
4573 bylex,
4574 withscores,
4575 score_cast_func,
4576 offset,
4577 num,
4578 )
4579
4580 def zrevrange(
4581 self,
4582 name: KeyT,
4583 start: int,
4584 end: int,
4585 withscores: bool = False,
4586 score_cast_func: Union[type, Callable] = float,
4587 ) -> ResponseT:
4588 """
4589 Return a range of values from sorted set ``name`` between
4590 ``start`` and ``end`` sorted in descending order.
4591
4592 ``start`` and ``end`` can be negative, indicating the end of the range.
4593
4594 ``withscores`` indicates to return the scores along with the values
4595 The return type is a list of (value, score) pairs
4596
4597 ``score_cast_func`` a callable used to cast the score return value
4598
4599 For more information, see https://redis.io/commands/zrevrange
4600 """
4601 pieces = ["ZREVRANGE", name, start, end]
4602 if withscores:
4603 pieces.append(b"WITHSCORES")
4604 options = {"withscores": withscores, "score_cast_func": score_cast_func}
4605 options["keys"] = name
4606 return self.execute_command(*pieces, **options)
4607
4608 def zrangestore(
4609 self,
4610 dest: KeyT,
4611 name: KeyT,
4612 start: int,
4613 end: int,
4614 byscore: bool = False,
4615 bylex: bool = False,
4616 desc: bool = False,
4617 offset: Optional[int] = None,
4618 num: Optional[int] = None,
4619 ) -> ResponseT:
4620 """
4621 Stores in ``dest`` the result of a range of values from sorted set
4622 ``name`` between ``start`` and ``end`` sorted in ascending order.
4623
4624 ``start`` and ``end`` can be negative, indicating the end of the range.
4625
4626 ``byscore`` when set to True, returns the range of elements from the
4627 sorted set having scores equal or between ``start`` and ``end``.
4628
4629 ``bylex`` when set to True, returns the range of elements from the
4630 sorted set between the ``start`` and ``end`` lexicographical closed
4631 range intervals.
4632 Valid ``start`` and ``end`` must start with ( or [, in order to specify
4633 whether the range interval is exclusive or inclusive, respectively.
4634
4635 ``desc`` a boolean indicating whether to sort the results in reversed
4636 order.
4637
4638 ``offset`` and ``num`` are specified, then return a slice of the range.
4639 Can't be provided when using ``bylex``.
4640
4641 For more information, see https://redis.io/commands/zrangestore
4642 """
4643 return self._zrange(
4644 "ZRANGESTORE",
4645 dest,
4646 name,
4647 start,
4648 end,
4649 desc,
4650 byscore,
4651 bylex,
4652 False,
4653 None,
4654 offset,
4655 num,
4656 )
4657
4658 def zrangebylex(
4659 self,
4660 name: KeyT,
4661 min: EncodableT,
4662 max: EncodableT,
4663 start: Optional[int] = None,
4664 num: Optional[int] = None,
4665 ) -> ResponseT:
4666 """
4667 Return the lexicographical range of values from sorted set ``name``
4668 between ``min`` and ``max``.
4669
4670 If ``start`` and ``num`` are specified, then return a slice of the
4671 range.
4672
4673 For more information, see https://redis.io/commands/zrangebylex
4674 """
4675 if (start is not None and num is None) or (num is not None and start is None):
4676 raise DataError("``start`` and ``num`` must both be specified")
4677 pieces = ["ZRANGEBYLEX", name, min, max]
4678 if start is not None and num is not None:
4679 pieces.extend([b"LIMIT", start, num])
4680 return self.execute_command(*pieces, keys=[name])
4681
4682 def zrevrangebylex(
4683 self,
4684 name: KeyT,
4685 max: EncodableT,
4686 min: EncodableT,
4687 start: Optional[int] = None,
4688 num: Optional[int] = None,
4689 ) -> ResponseT:
4690 """
4691 Return the reversed lexicographical range of values from sorted set
4692 ``name`` between ``max`` and ``min``.
4693
4694 If ``start`` and ``num`` are specified, then return a slice of the
4695 range.
4696
4697 For more information, see https://redis.io/commands/zrevrangebylex
4698 """
4699 if (start is not None and num is None) or (num is not None and start is None):
4700 raise DataError("``start`` and ``num`` must both be specified")
4701 pieces = ["ZREVRANGEBYLEX", name, max, min]
4702 if start is not None and num is not None:
4703 pieces.extend(["LIMIT", start, num])
4704 return self.execute_command(*pieces, keys=[name])
4705
4706 def zrangebyscore(
4707 self,
4708 name: KeyT,
4709 min: ZScoreBoundT,
4710 max: ZScoreBoundT,
4711 start: Optional[int] = None,
4712 num: Optional[int] = None,
4713 withscores: bool = False,
4714 score_cast_func: Union[type, Callable] = float,
4715 ) -> ResponseT:
4716 """
4717 Return a range of values from the sorted set ``name`` with scores
4718 between ``min`` and ``max``.
4719
4720 If ``start`` and ``num`` are specified, then return a slice
4721 of the range.
4722
4723 ``withscores`` indicates to return the scores along with the values.
4724 The return type is a list of (value, score) pairs
4725
4726 `score_cast_func`` a callable used to cast the score return value
4727
4728 For more information, see https://redis.io/commands/zrangebyscore
4729 """
4730 if (start is not None and num is None) or (num is not None and start is None):
4731 raise DataError("``start`` and ``num`` must both be specified")
4732 pieces = ["ZRANGEBYSCORE", name, min, max]
4733 if start is not None and num is not None:
4734 pieces.extend(["LIMIT", start, num])
4735 if withscores:
4736 pieces.append("WITHSCORES")
4737 options = {"withscores": withscores, "score_cast_func": score_cast_func}
4738 options["keys"] = [name]
4739 return self.execute_command(*pieces, **options)
4740
4741 def zrevrangebyscore(
4742 self,
4743 name: KeyT,
4744 max: ZScoreBoundT,
4745 min: ZScoreBoundT,
4746 start: Optional[int] = None,
4747 num: Optional[int] = None,
4748 withscores: bool = False,
4749 score_cast_func: Union[type, Callable] = float,
4750 ):
4751 """
4752 Return a range of values from the sorted set ``name`` with scores
4753 between ``min`` and ``max`` in descending order.
4754
4755 If ``start`` and ``num`` are specified, then return a slice
4756 of the range.
4757
4758 ``withscores`` indicates to return the scores along with the values.
4759 The return type is a list of (value, score) pairs
4760
4761 ``score_cast_func`` a callable used to cast the score return value
4762
4763 For more information, see https://redis.io/commands/zrevrangebyscore
4764 """
4765 if (start is not None and num is None) or (num is not None and start is None):
4766 raise DataError("``start`` and ``num`` must both be specified")
4767 pieces = ["ZREVRANGEBYSCORE", name, max, min]
4768 if start is not None and num is not None:
4769 pieces.extend(["LIMIT", start, num])
4770 if withscores:
4771 pieces.append("WITHSCORES")
4772 options = {"withscores": withscores, "score_cast_func": score_cast_func}
4773 options["keys"] = [name]
4774 return self.execute_command(*pieces, **options)
4775
4776 def zrank(
4777 self,
4778 name: KeyT,
4779 value: EncodableT,
4780 withscore: bool = False,
4781 ) -> ResponseT:
4782 """
4783 Returns a 0-based value indicating the rank of ``value`` in sorted set
4784 ``name``.
4785 The optional WITHSCORE argument supplements the command's
4786 reply with the score of the element returned.
4787
4788 For more information, see https://redis.io/commands/zrank
4789 """
4790 if withscore:
4791 return self.execute_command("ZRANK", name, value, "WITHSCORE", keys=[name])
4792 return self.execute_command("ZRANK", name, value, keys=[name])
4793
4794 def zrem(self, name: KeyT, *values: FieldT) -> ResponseT:
4795 """
4796 Remove member ``values`` from sorted set ``name``
4797
4798 For more information, see https://redis.io/commands/zrem
4799 """
4800 return self.execute_command("ZREM", name, *values)
4801
4802 def zremrangebylex(self, name: KeyT, min: EncodableT, max: EncodableT) -> ResponseT:
4803 """
4804 Remove all elements in the sorted set ``name`` between the
4805 lexicographical range specified by ``min`` and ``max``.
4806
4807 Returns the number of elements removed.
4808
4809 For more information, see https://redis.io/commands/zremrangebylex
4810 """
4811 return self.execute_command("ZREMRANGEBYLEX", name, min, max)
4812
4813 def zremrangebyrank(self, name: KeyT, min: int, max: int) -> ResponseT:
4814 """
4815 Remove all elements in the sorted set ``name`` with ranks between
4816 ``min`` and ``max``. Values are 0-based, ordered from smallest score
4817 to largest. Values can be negative indicating the highest scores.
4818 Returns the number of elements removed
4819
4820 For more information, see https://redis.io/commands/zremrangebyrank
4821 """
4822 return self.execute_command("ZREMRANGEBYRANK", name, min, max)
4823
4824 def zremrangebyscore(
4825 self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT
4826 ) -> ResponseT:
4827 """
4828 Remove all elements in the sorted set ``name`` with scores
4829 between ``min`` and ``max``. Returns the number of elements removed.
4830
4831 For more information, see https://redis.io/commands/zremrangebyscore
4832 """
4833 return self.execute_command("ZREMRANGEBYSCORE", name, min, max)
4834
4835 def zrevrank(
4836 self,
4837 name: KeyT,
4838 value: EncodableT,
4839 withscore: bool = False,
4840 ) -> ResponseT:
4841 """
4842 Returns a 0-based value indicating the descending rank of
4843 ``value`` in sorted set ``name``.
4844 The optional ``withscore`` argument supplements the command's
4845 reply with the score of the element returned.
4846
4847 For more information, see https://redis.io/commands/zrevrank
4848 """
4849 if withscore:
4850 return self.execute_command(
4851 "ZREVRANK", name, value, "WITHSCORE", keys=[name]
4852 )
4853 return self.execute_command("ZREVRANK", name, value, keys=[name])
4854
4855 def zscore(self, name: KeyT, value: EncodableT) -> ResponseT:
4856 """
4857 Return the score of element ``value`` in sorted set ``name``
4858
4859 For more information, see https://redis.io/commands/zscore
4860 """
4861 return self.execute_command("ZSCORE", name, value, keys=[name])
4862
4863 def zunion(
4864 self,
4865 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
4866 aggregate: Optional[str] = None,
4867 withscores: bool = False,
4868 ) -> ResponseT:
4869 """
4870 Return the union of multiple sorted sets specified by ``keys``.
4871 ``keys`` can be provided as dictionary of keys and their weights.
4872 Scores will be aggregated based on the ``aggregate``, or SUM if
4873 none is provided.
4874
4875 For more information, see https://redis.io/commands/zunion
4876 """
4877 return self._zaggregate("ZUNION", None, keys, aggregate, withscores=withscores)
4878
4879 def zunionstore(
4880 self,
4881 dest: KeyT,
4882 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
4883 aggregate: Optional[str] = None,
4884 ) -> ResponseT:
4885 """
4886 Union multiple sorted sets specified by ``keys`` into
4887 a new sorted set, ``dest``. Scores in the destination will be
4888 aggregated based on the ``aggregate``, or SUM if none is provided.
4889
4890 For more information, see https://redis.io/commands/zunionstore
4891 """
4892 return self._zaggregate("ZUNIONSTORE", dest, keys, aggregate)
4893
4894 def zmscore(self, key: KeyT, members: List[str]) -> ResponseT:
4895 """
4896 Returns the scores associated with the specified members
4897 in the sorted set stored at key.
4898 ``members`` should be a list of the member name.
4899 Return type is a list of score.
4900 If the member does not exist, a None will be returned
4901 in corresponding position.
4902
4903 For more information, see https://redis.io/commands/zmscore
4904 """
4905 if not members:
4906 raise DataError("ZMSCORE members must be a non-empty list")
4907 pieces = [key] + members
4908 return self.execute_command("ZMSCORE", *pieces, keys=[key])
4909
4910 def _zaggregate(
4911 self,
4912 command: str,
4913 dest: Union[KeyT, None],
4914 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
4915 aggregate: Optional[str] = None,
4916 **options,
4917 ) -> ResponseT:
4918 pieces: list[EncodableT] = [command]
4919 if dest is not None:
4920 pieces.append(dest)
4921 pieces.append(len(keys))
4922 if isinstance(keys, dict):
4923 keys, weights = keys.keys(), keys.values()
4924 else:
4925 weights = None
4926 pieces.extend(keys)
4927 if weights:
4928 pieces.append(b"WEIGHTS")
4929 pieces.extend(weights)
4930 if aggregate:
4931 if aggregate.upper() in ["SUM", "MIN", "MAX"]:
4932 pieces.append(b"AGGREGATE")
4933 pieces.append(aggregate)
4934 else:
4935 raise DataError("aggregate can be sum, min or max.")
4936 if options.get("withscores", False):
4937 pieces.append(b"WITHSCORES")
4938 options["keys"] = keys
4939 return self.execute_command(*pieces, **options)
4940
4941
4942AsyncSortedSetCommands = SortedSetCommands
4943
4944
4945class HyperlogCommands(CommandsProtocol):
4946 """
4947 Redis commands of HyperLogLogs data type.
4948 see: https://redis.io/topics/data-types-intro#hyperloglogs
4949 """
4950
4951 def pfadd(self, name: KeyT, *values: FieldT) -> ResponseT:
4952 """
4953 Adds the specified elements to the specified HyperLogLog.
4954
4955 For more information, see https://redis.io/commands/pfadd
4956 """
4957 return self.execute_command("PFADD", name, *values)
4958
4959 def pfcount(self, *sources: KeyT) -> ResponseT:
4960 """
4961 Return the approximated cardinality of
4962 the set observed by the HyperLogLog at key(s).
4963
4964 For more information, see https://redis.io/commands/pfcount
4965 """
4966 return self.execute_command("PFCOUNT", *sources)
4967
4968 def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT:
4969 """
4970 Merge N different HyperLogLogs into a single one.
4971
4972 For more information, see https://redis.io/commands/pfmerge
4973 """
4974 return self.execute_command("PFMERGE", dest, *sources)
4975
4976
4977AsyncHyperlogCommands = HyperlogCommands
4978
4979
4980class HashDataPersistOptions(Enum):
4981 # set the value for each provided key to each
4982 # provided value only if all do not already exist.
4983 FNX = "FNX"
4984
4985 # set the value for each provided key to each
4986 # provided value only if all already exist.
4987 FXX = "FXX"
4988
4989
4990class HashCommands(CommandsProtocol):
4991 """
4992 Redis commands for Hash data type.
4993 see: https://redis.io/topics/data-types-intro#redis-hashes
4994 """
4995
4996 def hdel(self, name: str, *keys: str) -> Union[Awaitable[int], int]:
4997 """
4998 Delete ``keys`` from hash ``name``
4999
5000 For more information, see https://redis.io/commands/hdel
5001 """
5002 return self.execute_command("HDEL", name, *keys)
5003
5004 def hexists(self, name: str, key: str) -> Union[Awaitable[bool], bool]:
5005 """
5006 Returns a boolean indicating if ``key`` exists within hash ``name``
5007
5008 For more information, see https://redis.io/commands/hexists
5009 """
5010 return self.execute_command("HEXISTS", name, key, keys=[name])
5011
5012 def hget(
5013 self, name: str, key: str
5014 ) -> Union[Awaitable[Optional[str]], Optional[str]]:
5015 """
5016 Return the value of ``key`` within the hash ``name``
5017
5018 For more information, see https://redis.io/commands/hget
5019 """
5020 return self.execute_command("HGET", name, key, keys=[name])
5021
5022 def hgetall(self, name: str) -> Union[Awaitable[dict], dict]:
5023 """
5024 Return a Python dict of the hash's name/value pairs
5025
5026 For more information, see https://redis.io/commands/hgetall
5027 """
5028 return self.execute_command("HGETALL", name, keys=[name])
5029
5030 def hgetdel(
5031 self, name: str, *keys: str
5032 ) -> Union[
5033 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]]
5034 ]:
5035 """
5036 Return the value of ``key`` within the hash ``name`` and
5037 delete the field in the hash.
5038 This command is similar to HGET, except for the fact that it also deletes
5039 the key on success from the hash with the provided ```name```.
5040
5041 Available since Redis 8.0
5042 For more information, see https://redis.io/commands/hgetdel
5043 """
5044 if len(keys) == 0:
5045 raise DataError("'hgetdel' should have at least one key provided")
5046
5047 return self.execute_command("HGETDEL", name, "FIELDS", len(keys), *keys)
5048
5049 def hgetex(
5050 self,
5051 name: KeyT,
5052 *keys: str,
5053 ex: Optional[ExpiryT] = None,
5054 px: Optional[ExpiryT] = None,
5055 exat: Optional[AbsExpiryT] = None,
5056 pxat: Optional[AbsExpiryT] = None,
5057 persist: bool = False,
5058 ) -> Union[
5059 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]]
5060 ]:
5061 """
5062 Return the values of ``key`` and ``keys`` within the hash ``name``
5063 and optionally set their expiration.
5064
5065 ``ex`` sets an expire flag on ``kyes`` for ``ex`` seconds.
5066
5067 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds.
5068
5069 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds,
5070 specified in unix time.
5071
5072 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds,
5073 specified in unix time.
5074
5075 ``persist`` remove the time to live associated with the ``keys``.
5076
5077 Available since Redis 8.0
5078 For more information, see https://redis.io/commands/hgetex
5079 """
5080 if not keys:
5081 raise DataError("'hgetex' should have at least one key provided")
5082
5083 opset = {ex, px, exat, pxat}
5084 if len(opset) > 2 or len(opset) > 1 and persist:
5085 raise DataError(
5086 "``ex``, ``px``, ``exat``, ``pxat``, "
5087 "and ``persist`` are mutually exclusive."
5088 )
5089
5090 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat)
5091
5092 if persist:
5093 exp_options.append("PERSIST")
5094
5095 return self.execute_command(
5096 "HGETEX",
5097 name,
5098 *exp_options,
5099 "FIELDS",
5100 len(keys),
5101 *keys,
5102 )
5103
5104 def hincrby(
5105 self, name: str, key: str, amount: int = 1
5106 ) -> Union[Awaitable[int], int]:
5107 """
5108 Increment the value of ``key`` in hash ``name`` by ``amount``
5109
5110 For more information, see https://redis.io/commands/hincrby
5111 """
5112 return self.execute_command("HINCRBY", name, key, amount)
5113
5114 def hincrbyfloat(
5115 self, name: str, key: str, amount: float = 1.0
5116 ) -> Union[Awaitable[float], float]:
5117 """
5118 Increment the value of ``key`` in hash ``name`` by floating ``amount``
5119
5120 For more information, see https://redis.io/commands/hincrbyfloat
5121 """
5122 return self.execute_command("HINCRBYFLOAT", name, key, amount)
5123
5124 def hkeys(self, name: str) -> Union[Awaitable[List], List]:
5125 """
5126 Return the list of keys within hash ``name``
5127
5128 For more information, see https://redis.io/commands/hkeys
5129 """
5130 return self.execute_command("HKEYS", name, keys=[name])
5131
5132 def hlen(self, name: str) -> Union[Awaitable[int], int]:
5133 """
5134 Return the number of elements in hash ``name``
5135
5136 For more information, see https://redis.io/commands/hlen
5137 """
5138 return self.execute_command("HLEN", name, keys=[name])
5139
5140 def hset(
5141 self,
5142 name: str,
5143 key: Optional[str] = None,
5144 value: Optional[str] = None,
5145 mapping: Optional[dict] = None,
5146 items: Optional[list] = None,
5147 ) -> Union[Awaitable[int], int]:
5148 """
5149 Set ``key`` to ``value`` within hash ``name``,
5150 ``mapping`` accepts a dict of key/value pairs that will be
5151 added to hash ``name``.
5152 ``items`` accepts a list of key/value pairs that will be
5153 added to hash ``name``.
5154 Returns the number of fields that were added.
5155
5156 For more information, see https://redis.io/commands/hset
5157 """
5158
5159 if key is None and not mapping and not items:
5160 raise DataError("'hset' with no key value pairs")
5161
5162 pieces = []
5163 if items:
5164 pieces.extend(items)
5165 if key is not None:
5166 pieces.extend((key, value))
5167 if mapping:
5168 for pair in mapping.items():
5169 pieces.extend(pair)
5170
5171 return self.execute_command("HSET", name, *pieces)
5172
5173 def hsetex(
5174 self,
5175 name: str,
5176 key: Optional[str] = None,
5177 value: Optional[str] = None,
5178 mapping: Optional[dict] = None,
5179 items: Optional[list] = None,
5180 ex: Optional[ExpiryT] = None,
5181 px: Optional[ExpiryT] = None,
5182 exat: Optional[AbsExpiryT] = None,
5183 pxat: Optional[AbsExpiryT] = None,
5184 data_persist_option: Optional[HashDataPersistOptions] = None,
5185 keepttl: bool = False,
5186 ) -> Union[Awaitable[int], int]:
5187 """
5188 Set ``key`` to ``value`` within hash ``name``
5189
5190 ``mapping`` accepts a dict of key/value pairs that will be
5191 added to hash ``name``.
5192
5193 ``items`` accepts a list of key/value pairs that will be
5194 added to hash ``name``.
5195
5196 ``ex`` sets an expire flag on ``keys`` for ``ex`` seconds.
5197
5198 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds.
5199
5200 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds,
5201 specified in unix time.
5202
5203 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds,
5204 specified in unix time.
5205
5206 ``data_persist_option`` can be set to ``FNX`` or ``FXX`` to control the
5207 behavior of the command.
5208 ``FNX`` will set the value for each provided key to each
5209 provided value only if all do not already exist.
5210 ``FXX`` will set the value for each provided key to each
5211 provided value only if all already exist.
5212
5213 ``keepttl`` if True, retain the time to live associated with the keys.
5214
5215 Returns the number of fields that were added.
5216
5217 Available since Redis 8.0
5218 For more information, see https://redis.io/commands/hsetex
5219 """
5220 if key is None and not mapping and not items:
5221 raise DataError("'hsetex' with no key value pairs")
5222
5223 if items and len(items) % 2 != 0:
5224 raise DataError(
5225 "'hsetex' with odd number of items. "
5226 "'items' must contain a list of key/value pairs."
5227 )
5228
5229 opset = {ex, px, exat, pxat}
5230 if len(opset) > 2 or len(opset) > 1 and keepttl:
5231 raise DataError(
5232 "``ex``, ``px``, ``exat``, ``pxat``, "
5233 "and ``keepttl`` are mutually exclusive."
5234 )
5235
5236 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat)
5237 if data_persist_option:
5238 exp_options.append(data_persist_option.value)
5239
5240 if keepttl:
5241 exp_options.append("KEEPTTL")
5242
5243 pieces = []
5244 if items:
5245 pieces.extend(items)
5246 if key is not None:
5247 pieces.extend((key, value))
5248 if mapping:
5249 for pair in mapping.items():
5250 pieces.extend(pair)
5251
5252 return self.execute_command(
5253 "HSETEX", name, *exp_options, "FIELDS", int(len(pieces) / 2), *pieces
5254 )
5255
5256 def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool]:
5257 """
5258 Set ``key`` to ``value`` within hash ``name`` if ``key`` does not
5259 exist. Returns 1 if HSETNX created a field, otherwise 0.
5260
5261 For more information, see https://redis.io/commands/hsetnx
5262 """
5263 return self.execute_command("HSETNX", name, key, value)
5264
5265 @deprecated_function(
5266 version="4.0.0",
5267 reason="Use 'hset' instead.",
5268 name="hmset",
5269 )
5270 def hmset(self, name: str, mapping: dict) -> Union[Awaitable[str], str]:
5271 """
5272 Set key to value within hash ``name`` for each corresponding
5273 key and value from the ``mapping`` dict.
5274
5275 For more information, see https://redis.io/commands/hmset
5276 """
5277 if not mapping:
5278 raise DataError("'hmset' with 'mapping' of length 0")
5279 items = []
5280 for pair in mapping.items():
5281 items.extend(pair)
5282 return self.execute_command("HMSET", name, *items)
5283
5284 def hmget(self, name: str, keys: List, *args: List) -> Union[Awaitable[List], List]:
5285 """
5286 Returns a list of values ordered identically to ``keys``
5287
5288 For more information, see https://redis.io/commands/hmget
5289 """
5290 args = list_or_args(keys, args)
5291 return self.execute_command("HMGET", name, *args, keys=[name])
5292
5293 def hvals(self, name: str) -> Union[Awaitable[List], List]:
5294 """
5295 Return the list of values within hash ``name``
5296
5297 For more information, see https://redis.io/commands/hvals
5298 """
5299 return self.execute_command("HVALS", name, keys=[name])
5300
5301 def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]:
5302 """
5303 Return the number of bytes stored in the value of ``key``
5304 within hash ``name``
5305
5306 For more information, see https://redis.io/commands/hstrlen
5307 """
5308 return self.execute_command("HSTRLEN", name, key, keys=[name])
5309
5310 def hexpire(
5311 self,
5312 name: KeyT,
5313 seconds: ExpiryT,
5314 *fields: str,
5315 nx: bool = False,
5316 xx: bool = False,
5317 gt: bool = False,
5318 lt: bool = False,
5319 ) -> ResponseT:
5320 """
5321 Sets or updates the expiration time for fields within a hash key, using relative
5322 time in seconds.
5323
5324 If a field already has an expiration time, the behavior of the update can be
5325 controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5326
5327 The return value provides detailed information about the outcome for each field.
5328
5329 For more information, see https://redis.io/commands/hexpire
5330
5331 Args:
5332 name: The name of the hash key.
5333 seconds: Expiration time in seconds, relative. Can be an integer, or a
5334 Python `timedelta` object.
5335 fields: List of fields within the hash to apply the expiration time to.
5336 nx: Set expiry only when the field has no expiry.
5337 xx: Set expiry only when the field has an existing expiry.
5338 gt: Set expiry only when the new expiry is greater than the current one.
5339 lt: Set expiry only when the new expiry is less than the current one.
5340
5341 Returns:
5342 Returns a list which contains for each field in the request:
5343 - `-2` if the field does not exist, or if the key does not exist.
5344 - `0` if the specified NX | XX | GT | LT condition was not met.
5345 - `1` if the expiration time was set or updated.
5346 - `2` if the field was deleted because the specified expiration time is
5347 in the past.
5348 """
5349 conditions = [nx, xx, gt, lt]
5350 if sum(conditions) > 1:
5351 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5352
5353 if isinstance(seconds, datetime.timedelta):
5354 seconds = int(seconds.total_seconds())
5355
5356 options = []
5357 if nx:
5358 options.append("NX")
5359 if xx:
5360 options.append("XX")
5361 if gt:
5362 options.append("GT")
5363 if lt:
5364 options.append("LT")
5365
5366 return self.execute_command(
5367 "HEXPIRE", name, seconds, *options, "FIELDS", len(fields), *fields
5368 )
5369
5370 def hpexpire(
5371 self,
5372 name: KeyT,
5373 milliseconds: ExpiryT,
5374 *fields: str,
5375 nx: bool = False,
5376 xx: bool = False,
5377 gt: bool = False,
5378 lt: bool = False,
5379 ) -> ResponseT:
5380 """
5381 Sets or updates the expiration time for fields within a hash key, using relative
5382 time in milliseconds.
5383
5384 If a field already has an expiration time, the behavior of the update can be
5385 controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5386
5387 The return value provides detailed information about the outcome for each field.
5388
5389 For more information, see https://redis.io/commands/hpexpire
5390
5391 Args:
5392 name: The name of the hash key.
5393 milliseconds: Expiration time in milliseconds, relative. Can be an integer,
5394 or a Python `timedelta` object.
5395 fields: List of fields within the hash to apply the expiration time to.
5396 nx: Set expiry only when the field has no expiry.
5397 xx: Set expiry only when the field has an existing expiry.
5398 gt: Set expiry only when the new expiry is greater than the current one.
5399 lt: Set expiry only when the new expiry is less than the current one.
5400
5401 Returns:
5402 Returns a list which contains for each field in the request:
5403 - `-2` if the field does not exist, or if the key does not exist.
5404 - `0` if the specified NX | XX | GT | LT condition was not met.
5405 - `1` if the expiration time was set or updated.
5406 - `2` if the field was deleted because the specified expiration time is
5407 in the past.
5408 """
5409 conditions = [nx, xx, gt, lt]
5410 if sum(conditions) > 1:
5411 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5412
5413 if isinstance(milliseconds, datetime.timedelta):
5414 milliseconds = int(milliseconds.total_seconds() * 1000)
5415
5416 options = []
5417 if nx:
5418 options.append("NX")
5419 if xx:
5420 options.append("XX")
5421 if gt:
5422 options.append("GT")
5423 if lt:
5424 options.append("LT")
5425
5426 return self.execute_command(
5427 "HPEXPIRE", name, milliseconds, *options, "FIELDS", len(fields), *fields
5428 )
5429
5430 def hexpireat(
5431 self,
5432 name: KeyT,
5433 unix_time_seconds: AbsExpiryT,
5434 *fields: str,
5435 nx: bool = False,
5436 xx: bool = False,
5437 gt: bool = False,
5438 lt: bool = False,
5439 ) -> ResponseT:
5440 """
5441 Sets or updates the expiration time for fields within a hash key, using an
5442 absolute Unix timestamp in seconds.
5443
5444 If a field already has an expiration time, the behavior of the update can be
5445 controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5446
5447 The return value provides detailed information about the outcome for each field.
5448
5449 For more information, see https://redis.io/commands/hexpireat
5450
5451 Args:
5452 name: The name of the hash key.
5453 unix_time_seconds: Expiration time as Unix timestamp in seconds. Can be an
5454 integer or a Python `datetime` object.
5455 fields: List of fields within the hash to apply the expiration time to.
5456 nx: Set expiry only when the field has no expiry.
5457 xx: Set expiry only when the field has an existing expiration time.
5458 gt: Set expiry only when the new expiry is greater than the current one.
5459 lt: Set expiry only when the new expiry is less than the current one.
5460
5461 Returns:
5462 Returns a list which contains for each field in the request:
5463 - `-2` if the field does not exist, or if the key does not exist.
5464 - `0` if the specified NX | XX | GT | LT condition was not met.
5465 - `1` if the expiration time was set or updated.
5466 - `2` if the field was deleted because the specified expiration time is
5467 in the past.
5468 """
5469 conditions = [nx, xx, gt, lt]
5470 if sum(conditions) > 1:
5471 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5472
5473 if isinstance(unix_time_seconds, datetime.datetime):
5474 unix_time_seconds = int(unix_time_seconds.timestamp())
5475
5476 options = []
5477 if nx:
5478 options.append("NX")
5479 if xx:
5480 options.append("XX")
5481 if gt:
5482 options.append("GT")
5483 if lt:
5484 options.append("LT")
5485
5486 return self.execute_command(
5487 "HEXPIREAT",
5488 name,
5489 unix_time_seconds,
5490 *options,
5491 "FIELDS",
5492 len(fields),
5493 *fields,
5494 )
5495
5496 def hpexpireat(
5497 self,
5498 name: KeyT,
5499 unix_time_milliseconds: AbsExpiryT,
5500 *fields: str,
5501 nx: bool = False,
5502 xx: bool = False,
5503 gt: bool = False,
5504 lt: bool = False,
5505 ) -> ResponseT:
5506 """
5507 Sets or updates the expiration time for fields within a hash key, using an
5508 absolute Unix timestamp in milliseconds.
5509
5510 If a field already has an expiration time, the behavior of the update can be
5511 controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5512
5513 The return value provides detailed information about the outcome for each field.
5514
5515 For more information, see https://redis.io/commands/hpexpireat
5516
5517 Args:
5518 name: The name of the hash key.
5519 unix_time_milliseconds: Expiration time as Unix timestamp in milliseconds.
5520 Can be an integer or a Python `datetime` object.
5521 fields: List of fields within the hash to apply the expiry.
5522 nx: Set expiry only when the field has no expiry.
5523 xx: Set expiry only when the field has an existing expiry.
5524 gt: Set expiry only when the new expiry is greater than the current one.
5525 lt: Set expiry only when the new expiry is less than the current one.
5526
5527 Returns:
5528 Returns a list which contains for each field in the request:
5529 - `-2` if the field does not exist, or if the key does not exist.
5530 - `0` if the specified NX | XX | GT | LT condition was not met.
5531 - `1` if the expiration time was set or updated.
5532 - `2` if the field was deleted because the specified expiration time is
5533 in the past.
5534 """
5535 conditions = [nx, xx, gt, lt]
5536 if sum(conditions) > 1:
5537 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5538
5539 if isinstance(unix_time_milliseconds, datetime.datetime):
5540 unix_time_milliseconds = int(unix_time_milliseconds.timestamp() * 1000)
5541
5542 options = []
5543 if nx:
5544 options.append("NX")
5545 if xx:
5546 options.append("XX")
5547 if gt:
5548 options.append("GT")
5549 if lt:
5550 options.append("LT")
5551
5552 return self.execute_command(
5553 "HPEXPIREAT",
5554 name,
5555 unix_time_milliseconds,
5556 *options,
5557 "FIELDS",
5558 len(fields),
5559 *fields,
5560 )
5561
5562 def hpersist(self, name: KeyT, *fields: str) -> ResponseT:
5563 """
5564 Removes the expiration time for each specified field in a hash.
5565
5566 For more information, see https://redis.io/commands/hpersist
5567
5568 Args:
5569 name: The name of the hash key.
5570 fields: A list of fields within the hash from which to remove the
5571 expiration time.
5572
5573 Returns:
5574 Returns a list which contains for each field in the request:
5575 - `-2` if the field does not exist, or if the key does not exist.
5576 - `-1` if the field exists but has no associated expiration time.
5577 - `1` if the expiration time was successfully removed from the field.
5578 """
5579 return self.execute_command("HPERSIST", name, "FIELDS", len(fields), *fields)
5580
5581 def hexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
5582 """
5583 Returns the expiration times of hash fields as Unix timestamps in seconds.
5584
5585 For more information, see https://redis.io/commands/hexpiretime
5586
5587 Args:
5588 key: The hash key.
5589 fields: A list of fields within the hash for which to get the expiration
5590 time.
5591
5592 Returns:
5593 Returns a list which contains for each field in the request:
5594 - `-2` if the field does not exist, or if the key does not exist.
5595 - `-1` if the field exists but has no associated expire time.
5596 - A positive integer representing the expiration Unix timestamp in
5597 seconds, if the field has an associated expiration time.
5598 """
5599 return self.execute_command(
5600 "HEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key]
5601 )
5602
5603 def hpexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
5604 """
5605 Returns the expiration times of hash fields as Unix timestamps in milliseconds.
5606
5607 For more information, see https://redis.io/commands/hpexpiretime
5608
5609 Args:
5610 key: The hash key.
5611 fields: A list of fields within the hash for which to get the expiration
5612 time.
5613
5614 Returns:
5615 Returns a list which contains for each field in the request:
5616 - `-2` if the field does not exist, or if the key does not exist.
5617 - `-1` if the field exists but has no associated expire time.
5618 - A positive integer representing the expiration Unix timestamp in
5619 milliseconds, if the field has an associated expiration time.
5620 """
5621 return self.execute_command(
5622 "HPEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key]
5623 )
5624
5625 def httl(self, key: KeyT, *fields: str) -> ResponseT:
5626 """
5627 Returns the TTL (Time To Live) in seconds for each specified field within a hash
5628 key.
5629
5630 For more information, see https://redis.io/commands/httl
5631
5632 Args:
5633 key: The hash key.
5634 fields: A list of fields within the hash for which to get the TTL.
5635
5636 Returns:
5637 Returns a list which contains for each field in the request:
5638 - `-2` if the field does not exist, or if the key does not exist.
5639 - `-1` if the field exists but has no associated expire time.
5640 - A positive integer representing the TTL in seconds if the field has
5641 an associated expiration time.
5642 """
5643 return self.execute_command(
5644 "HTTL", key, "FIELDS", len(fields), *fields, keys=[key]
5645 )
5646
5647 def hpttl(self, key: KeyT, *fields: str) -> ResponseT:
5648 """
5649 Returns the TTL (Time To Live) in milliseconds for each specified field within a
5650 hash key.
5651
5652 For more information, see https://redis.io/commands/hpttl
5653
5654 Args:
5655 key: The hash key.
5656 fields: A list of fields within the hash for which to get the TTL.
5657
5658 Returns:
5659 Returns a list which contains for each field in the request:
5660 - `-2` if the field does not exist, or if the key does not exist.
5661 - `-1` if the field exists but has no associated expire time.
5662 - A positive integer representing the TTL in milliseconds if the field
5663 has an associated expiration time.
5664 """
5665 return self.execute_command(
5666 "HPTTL", key, "FIELDS", len(fields), *fields, keys=[key]
5667 )
5668
5669
5670AsyncHashCommands = HashCommands
5671
5672
5673class Script:
5674 """
5675 An executable Lua script object returned by ``register_script``
5676 """
5677
5678 def __init__(self, registered_client: "redis.client.Redis", script: ScriptTextT):
5679 self.registered_client = registered_client
5680 self.script = script
5681 # Precalculate and store the SHA1 hex digest of the script.
5682
5683 if isinstance(script, str):
5684 # We need the encoding from the client in order to generate an
5685 # accurate byte representation of the script
5686 encoder = self.get_encoder()
5687 script = encoder.encode(script)
5688 self.sha = hashlib.sha1(script).hexdigest()
5689
5690 def __call__(
5691 self,
5692 keys: Union[Sequence[KeyT], None] = None,
5693 args: Union[Iterable[EncodableT], None] = None,
5694 client: Union["redis.client.Redis", None] = None,
5695 ):
5696 """Execute the script, passing any required ``args``"""
5697 keys = keys or []
5698 args = args or []
5699 if client is None:
5700 client = self.registered_client
5701 args = tuple(keys) + tuple(args)
5702 # make sure the Redis server knows about the script
5703 from redis.client import Pipeline
5704
5705 if isinstance(client, Pipeline):
5706 # Make sure the pipeline can register the script before executing.
5707 client.scripts.add(self)
5708 try:
5709 return client.evalsha(self.sha, len(keys), *args)
5710 except NoScriptError:
5711 # Maybe the client is pointed to a different server than the client
5712 # that created this instance?
5713 # Overwrite the sha just in case there was a discrepancy.
5714 self.sha = client.script_load(self.script)
5715 return client.evalsha(self.sha, len(keys), *args)
5716
5717 def get_encoder(self):
5718 """Get the encoder to encode string scripts into bytes."""
5719 try:
5720 return self.registered_client.get_encoder()
5721 except AttributeError:
5722 # DEPRECATED
5723 # In version <=4.1.2, this was the code we used to get the encoder.
5724 # However, after 4.1.2 we added support for scripting in clustered
5725 # redis. ClusteredRedis doesn't have a `.connection_pool` attribute
5726 # so we changed the Script class to use
5727 # `self.registered_client.get_encoder` (see above).
5728 # However, that is technically a breaking change, as consumers who
5729 # use Scripts directly might inject a `registered_client` that
5730 # doesn't have a `.get_encoder` field. This try/except prevents us
5731 # from breaking backward-compatibility. Ideally, it would be
5732 # removed in the next major release.
5733 return self.registered_client.connection_pool.get_encoder()
5734
5735
5736class AsyncScript:
5737 """
5738 An executable Lua script object returned by ``register_script``
5739 """
5740
5741 def __init__(
5742 self,
5743 registered_client: "redis.asyncio.client.Redis",
5744 script: ScriptTextT,
5745 ):
5746 self.registered_client = registered_client
5747 self.script = script
5748 # Precalculate and store the SHA1 hex digest of the script.
5749
5750 if isinstance(script, str):
5751 # We need the encoding from the client in order to generate an
5752 # accurate byte representation of the script
5753 try:
5754 encoder = registered_client.connection_pool.get_encoder()
5755 except AttributeError:
5756 # Cluster
5757 encoder = registered_client.get_encoder()
5758 script = encoder.encode(script)
5759 self.sha = hashlib.sha1(script).hexdigest()
5760
5761 async def __call__(
5762 self,
5763 keys: Union[Sequence[KeyT], None] = None,
5764 args: Union[Iterable[EncodableT], None] = None,
5765 client: Union["redis.asyncio.client.Redis", None] = None,
5766 ):
5767 """Execute the script, passing any required ``args``"""
5768 keys = keys or []
5769 args = args or []
5770 if client is None:
5771 client = self.registered_client
5772 args = tuple(keys) + tuple(args)
5773 # make sure the Redis server knows about the script
5774 from redis.asyncio.client import Pipeline
5775
5776 if isinstance(client, Pipeline):
5777 # Make sure the pipeline can register the script before executing.
5778 client.scripts.add(self)
5779 try:
5780 return await client.evalsha(self.sha, len(keys), *args)
5781 except NoScriptError:
5782 # Maybe the client is pointed to a different server than the client
5783 # that created this instance?
5784 # Overwrite the sha just in case there was a discrepancy.
5785 self.sha = await client.script_load(self.script)
5786 return await client.evalsha(self.sha, len(keys), *args)
5787
5788
5789class PubSubCommands(CommandsProtocol):
5790 """
5791 Redis PubSub commands.
5792 see https://redis.io/topics/pubsub
5793 """
5794
5795 def publish(self, channel: ChannelT, message: EncodableT, **kwargs) -> ResponseT:
5796 """
5797 Publish ``message`` on ``channel``.
5798 Returns the number of subscribers the message was delivered to.
5799
5800 For more information, see https://redis.io/commands/publish
5801 """
5802 return self.execute_command("PUBLISH", channel, message, **kwargs)
5803
5804 def spublish(self, shard_channel: ChannelT, message: EncodableT) -> ResponseT:
5805 """
5806 Posts a message to the given shard channel.
5807 Returns the number of clients that received the message
5808
5809 For more information, see https://redis.io/commands/spublish
5810 """
5811 return self.execute_command("SPUBLISH", shard_channel, message)
5812
5813 def pubsub_channels(self, pattern: PatternT = "*", **kwargs) -> ResponseT:
5814 """
5815 Return a list of channels that have at least one subscriber
5816
5817 For more information, see https://redis.io/commands/pubsub-channels
5818 """
5819 return self.execute_command("PUBSUB CHANNELS", pattern, **kwargs)
5820
5821 def pubsub_shardchannels(self, pattern: PatternT = "*", **kwargs) -> ResponseT:
5822 """
5823 Return a list of shard_channels that have at least one subscriber
5824
5825 For more information, see https://redis.io/commands/pubsub-shardchannels
5826 """
5827 return self.execute_command("PUBSUB SHARDCHANNELS", pattern, **kwargs)
5828
5829 def pubsub_numpat(self, **kwargs) -> ResponseT:
5830 """
5831 Returns the number of subscriptions to patterns
5832
5833 For more information, see https://redis.io/commands/pubsub-numpat
5834 """
5835 return self.execute_command("PUBSUB NUMPAT", **kwargs)
5836
5837 def pubsub_numsub(self, *args: ChannelT, **kwargs) -> ResponseT:
5838 """
5839 Return a list of (channel, number of subscribers) tuples
5840 for each channel given in ``*args``
5841
5842 For more information, see https://redis.io/commands/pubsub-numsub
5843 """
5844 return self.execute_command("PUBSUB NUMSUB", *args, **kwargs)
5845
5846 def pubsub_shardnumsub(self, *args: ChannelT, **kwargs) -> ResponseT:
5847 """
5848 Return a list of (shard_channel, number of subscribers) tuples
5849 for each channel given in ``*args``
5850
5851 For more information, see https://redis.io/commands/pubsub-shardnumsub
5852 """
5853 return self.execute_command("PUBSUB SHARDNUMSUB", *args, **kwargs)
5854
5855
5856AsyncPubSubCommands = PubSubCommands
5857
5858
5859class ScriptCommands(CommandsProtocol):
5860 """
5861 Redis Lua script commands. see:
5862 https://redis.io/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/
5863 """
5864
5865 def _eval(
5866 self, command: str, script: str, numkeys: int, *keys_and_args: str
5867 ) -> Union[Awaitable[str], str]:
5868 return self.execute_command(command, script, numkeys, *keys_and_args)
5869
5870 def eval(
5871 self, script: str, numkeys: int, *keys_and_args: str
5872 ) -> Union[Awaitable[str], str]:
5873 """
5874 Execute the Lua ``script``, specifying the ``numkeys`` the script
5875 will touch and the key names and argument values in ``keys_and_args``.
5876 Returns the result of the script.
5877
5878 In practice, use the object returned by ``register_script``. This
5879 function exists purely for Redis API completion.
5880
5881 For more information, see https://redis.io/commands/eval
5882 """
5883 return self._eval("EVAL", script, numkeys, *keys_and_args)
5884
5885 def eval_ro(
5886 self, script: str, numkeys: int, *keys_and_args: str
5887 ) -> Union[Awaitable[str], str]:
5888 """
5889 The read-only variant of the EVAL command
5890
5891 Execute the read-only Lua ``script`` specifying the ``numkeys`` the script
5892 will touch and the key names and argument values in ``keys_and_args``.
5893 Returns the result of the script.
5894
5895 For more information, see https://redis.io/commands/eval_ro
5896 """
5897 return self._eval("EVAL_RO", script, numkeys, *keys_and_args)
5898
5899 def _evalsha(
5900 self, command: str, sha: str, numkeys: int, *keys_and_args: list
5901 ) -> Union[Awaitable[str], str]:
5902 return self.execute_command(command, sha, numkeys, *keys_and_args)
5903
5904 def evalsha(
5905 self, sha: str, numkeys: int, *keys_and_args: str
5906 ) -> Union[Awaitable[str], str]:
5907 """
5908 Use the ``sha`` to execute a Lua script already registered via EVAL
5909 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the
5910 key names and argument values in ``keys_and_args``. Returns the result
5911 of the script.
5912
5913 In practice, use the object returned by ``register_script``. This
5914 function exists purely for Redis API completion.
5915
5916 For more information, see https://redis.io/commands/evalsha
5917 """
5918 return self._evalsha("EVALSHA", sha, numkeys, *keys_and_args)
5919
5920 def evalsha_ro(
5921 self, sha: str, numkeys: int, *keys_and_args: str
5922 ) -> Union[Awaitable[str], str]:
5923 """
5924 The read-only variant of the EVALSHA command
5925
5926 Use the ``sha`` to execute a read-only Lua script already registered via EVAL
5927 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the
5928 key names and argument values in ``keys_and_args``. Returns the result
5929 of the script.
5930
5931 For more information, see https://redis.io/commands/evalsha_ro
5932 """
5933 return self._evalsha("EVALSHA_RO", sha, numkeys, *keys_and_args)
5934
5935 def script_exists(self, *args: str) -> ResponseT:
5936 """
5937 Check if a script exists in the script cache by specifying the SHAs of
5938 each script as ``args``. Returns a list of boolean values indicating if
5939 if each already script exists in the cache_data.
5940
5941 For more information, see https://redis.io/commands/script-exists
5942 """
5943 return self.execute_command("SCRIPT EXISTS", *args)
5944
5945 def script_debug(self, *args) -> None:
5946 raise NotImplementedError(
5947 "SCRIPT DEBUG is intentionally not implemented in the client."
5948 )
5949
5950 def script_flush(
5951 self, sync_type: Union[Literal["SYNC"], Literal["ASYNC"]] = None
5952 ) -> ResponseT:
5953 """Flush all scripts from the script cache_data.
5954
5955 ``sync_type`` is by default SYNC (synchronous) but it can also be
5956 ASYNC.
5957
5958 For more information, see https://redis.io/commands/script-flush
5959 """
5960
5961 # Redis pre 6 had no sync_type.
5962 if sync_type not in ["SYNC", "ASYNC", None]:
5963 raise DataError(
5964 "SCRIPT FLUSH defaults to SYNC in redis > 6.2, or "
5965 "accepts SYNC/ASYNC. For older versions, "
5966 "of redis leave as None."
5967 )
5968 if sync_type is None:
5969 pieces = []
5970 else:
5971 pieces = [sync_type]
5972 return self.execute_command("SCRIPT FLUSH", *pieces)
5973
5974 def script_kill(self) -> ResponseT:
5975 """
5976 Kill the currently executing Lua script
5977
5978 For more information, see https://redis.io/commands/script-kill
5979 """
5980 return self.execute_command("SCRIPT KILL")
5981
5982 def script_load(self, script: ScriptTextT) -> ResponseT:
5983 """
5984 Load a Lua ``script`` into the script cache_data. Returns the SHA.
5985
5986 For more information, see https://redis.io/commands/script-load
5987 """
5988 return self.execute_command("SCRIPT LOAD", script)
5989
5990 def register_script(self: "redis.client.Redis", script: ScriptTextT) -> Script:
5991 """
5992 Register a Lua ``script`` specifying the ``keys`` it will touch.
5993 Returns a Script object that is callable and hides the complexity of
5994 deal with scripts, keys, and shas. This is the preferred way to work
5995 with Lua scripts.
5996 """
5997 return Script(self, script)
5998
5999
6000class AsyncScriptCommands(ScriptCommands):
6001 async def script_debug(self, *args) -> None:
6002 return super().script_debug()
6003
6004 def register_script(
6005 self: "redis.asyncio.client.Redis",
6006 script: ScriptTextT,
6007 ) -> AsyncScript:
6008 """
6009 Register a Lua ``script`` specifying the ``keys`` it will touch.
6010 Returns a Script object that is callable and hides the complexity of
6011 deal with scripts, keys, and shas. This is the preferred way to work
6012 with Lua scripts.
6013 """
6014 return AsyncScript(self, script)
6015
6016
6017class GeoCommands(CommandsProtocol):
6018 """
6019 Redis Geospatial commands.
6020 see: https://redis.com/redis-best-practices/indexing-patterns/geospatial/
6021 """
6022
6023 def geoadd(
6024 self,
6025 name: KeyT,
6026 values: Sequence[EncodableT],
6027 nx: bool = False,
6028 xx: bool = False,
6029 ch: bool = False,
6030 ) -> ResponseT:
6031 """
6032 Add the specified geospatial items to the specified key identified
6033 by the ``name`` argument. The Geospatial items are given as ordered
6034 members of the ``values`` argument, each item or place is formed by
6035 the triad longitude, latitude and name.
6036
6037 Note: You can use ZREM to remove elements.
6038
6039 ``nx`` forces ZADD to only create new elements and not to update
6040 scores for elements that already exist.
6041
6042 ``xx`` forces ZADD to only update scores of elements that already
6043 exist. New elements will not be added.
6044
6045 ``ch`` modifies the return value to be the numbers of elements changed.
6046 Changed elements include new elements that were added and elements
6047 whose scores changed.
6048
6049 For more information, see https://redis.io/commands/geoadd
6050 """
6051 if nx and xx:
6052 raise DataError("GEOADD allows either 'nx' or 'xx', not both")
6053 if len(values) % 3 != 0:
6054 raise DataError("GEOADD requires places with lon, lat and name values")
6055 pieces = [name]
6056 if nx:
6057 pieces.append("NX")
6058 if xx:
6059 pieces.append("XX")
6060 if ch:
6061 pieces.append("CH")
6062 pieces.extend(values)
6063 return self.execute_command("GEOADD", *pieces)
6064
6065 def geodist(
6066 self, name: KeyT, place1: FieldT, place2: FieldT, unit: Optional[str] = None
6067 ) -> ResponseT:
6068 """
6069 Return the distance between ``place1`` and ``place2`` members of the
6070 ``name`` key.
6071 The units must be one of the following : m, km mi, ft. By default
6072 meters are used.
6073
6074 For more information, see https://redis.io/commands/geodist
6075 """
6076 pieces: list[EncodableT] = [name, place1, place2]
6077 if unit and unit not in ("m", "km", "mi", "ft"):
6078 raise DataError("GEODIST invalid unit")
6079 elif unit:
6080 pieces.append(unit)
6081 return self.execute_command("GEODIST", *pieces, keys=[name])
6082
6083 def geohash(self, name: KeyT, *values: FieldT) -> ResponseT:
6084 """
6085 Return the geo hash string for each item of ``values`` members of
6086 the specified key identified by the ``name`` argument.
6087
6088 For more information, see https://redis.io/commands/geohash
6089 """
6090 return self.execute_command("GEOHASH", name, *values, keys=[name])
6091
6092 def geopos(self, name: KeyT, *values: FieldT) -> ResponseT:
6093 """
6094 Return the positions of each item of ``values`` as members of
6095 the specified key identified by the ``name`` argument. Each position
6096 is represented by the pairs lon and lat.
6097
6098 For more information, see https://redis.io/commands/geopos
6099 """
6100 return self.execute_command("GEOPOS", name, *values, keys=[name])
6101
6102 def georadius(
6103 self,
6104 name: KeyT,
6105 longitude: float,
6106 latitude: float,
6107 radius: float,
6108 unit: Optional[str] = None,
6109 withdist: bool = False,
6110 withcoord: bool = False,
6111 withhash: bool = False,
6112 count: Optional[int] = None,
6113 sort: Optional[str] = None,
6114 store: Optional[KeyT] = None,
6115 store_dist: Optional[KeyT] = None,
6116 any: bool = False,
6117 ) -> ResponseT:
6118 """
6119 Return the members of the specified key identified by the
6120 ``name`` argument which are within the borders of the area specified
6121 with the ``latitude`` and ``longitude`` location and the maximum
6122 distance from the center specified by the ``radius`` value.
6123
6124 The units must be one of the following : m, km mi, ft. By default
6125
6126 ``withdist`` indicates to return the distances of each place.
6127
6128 ``withcoord`` indicates to return the latitude and longitude of
6129 each place.
6130
6131 ``withhash`` indicates to return the geohash string of each place.
6132
6133 ``count`` indicates to return the number of elements up to N.
6134
6135 ``sort`` indicates to return the places in a sorted way, ASC for
6136 nearest to fairest and DESC for fairest to nearest.
6137
6138 ``store`` indicates to save the places names in a sorted set named
6139 with a specific key, each element of the destination sorted set is
6140 populated with the score got from the original geo sorted set.
6141
6142 ``store_dist`` indicates to save the places names in a sorted set
6143 named with a specific key, instead of ``store`` the sorted set
6144 destination score is set with the distance.
6145
6146 For more information, see https://redis.io/commands/georadius
6147 """
6148 return self._georadiusgeneric(
6149 "GEORADIUS",
6150 name,
6151 longitude,
6152 latitude,
6153 radius,
6154 unit=unit,
6155 withdist=withdist,
6156 withcoord=withcoord,
6157 withhash=withhash,
6158 count=count,
6159 sort=sort,
6160 store=store,
6161 store_dist=store_dist,
6162 any=any,
6163 )
6164
6165 def georadiusbymember(
6166 self,
6167 name: KeyT,
6168 member: FieldT,
6169 radius: float,
6170 unit: Optional[str] = None,
6171 withdist: bool = False,
6172 withcoord: bool = False,
6173 withhash: bool = False,
6174 count: Optional[int] = None,
6175 sort: Optional[str] = None,
6176 store: Union[KeyT, None] = None,
6177 store_dist: Union[KeyT, None] = None,
6178 any: bool = False,
6179 ) -> ResponseT:
6180 """
6181 This command is exactly like ``georadius`` with the sole difference
6182 that instead of taking, as the center of the area to query, a longitude
6183 and latitude value, it takes the name of a member already existing
6184 inside the geospatial index represented by the sorted set.
6185
6186 For more information, see https://redis.io/commands/georadiusbymember
6187 """
6188 return self._georadiusgeneric(
6189 "GEORADIUSBYMEMBER",
6190 name,
6191 member,
6192 radius,
6193 unit=unit,
6194 withdist=withdist,
6195 withcoord=withcoord,
6196 withhash=withhash,
6197 count=count,
6198 sort=sort,
6199 store=store,
6200 store_dist=store_dist,
6201 any=any,
6202 )
6203
6204 def _georadiusgeneric(
6205 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None]
6206 ) -> ResponseT:
6207 pieces = list(args)
6208 if kwargs["unit"] and kwargs["unit"] not in ("m", "km", "mi", "ft"):
6209 raise DataError("GEORADIUS invalid unit")
6210 elif kwargs["unit"]:
6211 pieces.append(kwargs["unit"])
6212 else:
6213 pieces.append("m")
6214
6215 if kwargs["any"] and kwargs["count"] is None:
6216 raise DataError("``any`` can't be provided without ``count``")
6217
6218 for arg_name, byte_repr in (
6219 ("withdist", "WITHDIST"),
6220 ("withcoord", "WITHCOORD"),
6221 ("withhash", "WITHHASH"),
6222 ):
6223 if kwargs[arg_name]:
6224 pieces.append(byte_repr)
6225
6226 if kwargs["count"] is not None:
6227 pieces.extend(["COUNT", kwargs["count"]])
6228 if kwargs["any"]:
6229 pieces.append("ANY")
6230
6231 if kwargs["sort"]:
6232 if kwargs["sort"] == "ASC":
6233 pieces.append("ASC")
6234 elif kwargs["sort"] == "DESC":
6235 pieces.append("DESC")
6236 else:
6237 raise DataError("GEORADIUS invalid sort")
6238
6239 if kwargs["store"] and kwargs["store_dist"]:
6240 raise DataError("GEORADIUS store and store_dist cant be set together")
6241
6242 if kwargs["store"]:
6243 pieces.extend([b"STORE", kwargs["store"]])
6244
6245 if kwargs["store_dist"]:
6246 pieces.extend([b"STOREDIST", kwargs["store_dist"]])
6247
6248 return self.execute_command(command, *pieces, **kwargs)
6249
6250 def geosearch(
6251 self,
6252 name: KeyT,
6253 member: Union[FieldT, None] = None,
6254 longitude: Union[float, None] = None,
6255 latitude: Union[float, None] = None,
6256 unit: str = "m",
6257 radius: Union[float, None] = None,
6258 width: Union[float, None] = None,
6259 height: Union[float, None] = None,
6260 sort: Optional[str] = None,
6261 count: Optional[int] = None,
6262 any: bool = False,
6263 withcoord: bool = False,
6264 withdist: bool = False,
6265 withhash: bool = False,
6266 ) -> ResponseT:
6267 """
6268 Return the members of specified key identified by the
6269 ``name`` argument, which are within the borders of the
6270 area specified by a given shape. This command extends the
6271 GEORADIUS command, so in addition to searching within circular
6272 areas, it supports searching within rectangular areas.
6273
6274 This command should be used in place of the deprecated
6275 GEORADIUS and GEORADIUSBYMEMBER commands.
6276
6277 ``member`` Use the position of the given existing
6278 member in the sorted set. Can't be given with ``longitude``
6279 and ``latitude``.
6280
6281 ``longitude`` and ``latitude`` Use the position given by
6282 this coordinates. Can't be given with ``member``
6283 ``radius`` Similar to GEORADIUS, search inside circular
6284 area according the given radius. Can't be given with
6285 ``height`` and ``width``.
6286 ``height`` and ``width`` Search inside an axis-aligned
6287 rectangle, determined by the given height and width.
6288 Can't be given with ``radius``
6289
6290 ``unit`` must be one of the following : m, km, mi, ft.
6291 `m` for meters (the default value), `km` for kilometers,
6292 `mi` for miles and `ft` for feet.
6293
6294 ``sort`` indicates to return the places in a sorted way,
6295 ASC for nearest to furthest and DESC for furthest to nearest.
6296
6297 ``count`` limit the results to the first count matching items.
6298
6299 ``any`` is set to True, the command will return as soon as
6300 enough matches are found. Can't be provided without ``count``
6301
6302 ``withdist`` indicates to return the distances of each place.
6303 ``withcoord`` indicates to return the latitude and longitude of
6304 each place.
6305
6306 ``withhash`` indicates to return the geohash string of each place.
6307
6308 For more information, see https://redis.io/commands/geosearch
6309 """
6310
6311 return self._geosearchgeneric(
6312 "GEOSEARCH",
6313 name,
6314 member=member,
6315 longitude=longitude,
6316 latitude=latitude,
6317 unit=unit,
6318 radius=radius,
6319 width=width,
6320 height=height,
6321 sort=sort,
6322 count=count,
6323 any=any,
6324 withcoord=withcoord,
6325 withdist=withdist,
6326 withhash=withhash,
6327 store=None,
6328 store_dist=None,
6329 )
6330
6331 def geosearchstore(
6332 self,
6333 dest: KeyT,
6334 name: KeyT,
6335 member: Optional[FieldT] = None,
6336 longitude: Optional[float] = None,
6337 latitude: Optional[float] = None,
6338 unit: str = "m",
6339 radius: Optional[float] = None,
6340 width: Optional[float] = None,
6341 height: Optional[float] = None,
6342 sort: Optional[str] = None,
6343 count: Optional[int] = None,
6344 any: bool = False,
6345 storedist: bool = False,
6346 ) -> ResponseT:
6347 """
6348 This command is like GEOSEARCH, but stores the result in
6349 ``dest``. By default, it stores the results in the destination
6350 sorted set with their geospatial information.
6351 if ``store_dist`` set to True, the command will stores the
6352 items in a sorted set populated with their distance from the
6353 center of the circle or box, as a floating-point number.
6354
6355 For more information, see https://redis.io/commands/geosearchstore
6356 """
6357 return self._geosearchgeneric(
6358 "GEOSEARCHSTORE",
6359 dest,
6360 name,
6361 member=member,
6362 longitude=longitude,
6363 latitude=latitude,
6364 unit=unit,
6365 radius=radius,
6366 width=width,
6367 height=height,
6368 sort=sort,
6369 count=count,
6370 any=any,
6371 withcoord=None,
6372 withdist=None,
6373 withhash=None,
6374 store=None,
6375 store_dist=storedist,
6376 )
6377
6378 def _geosearchgeneric(
6379 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None]
6380 ) -> ResponseT:
6381 pieces = list(args)
6382
6383 # FROMMEMBER or FROMLONLAT
6384 if kwargs["member"] is None:
6385 if kwargs["longitude"] is None or kwargs["latitude"] is None:
6386 raise DataError("GEOSEARCH must have member or longitude and latitude")
6387 if kwargs["member"]:
6388 if kwargs["longitude"] or kwargs["latitude"]:
6389 raise DataError(
6390 "GEOSEARCH member and longitude or latitude cant be set together"
6391 )
6392 pieces.extend([b"FROMMEMBER", kwargs["member"]])
6393 if kwargs["longitude"] is not None and kwargs["latitude"] is not None:
6394 pieces.extend([b"FROMLONLAT", kwargs["longitude"], kwargs["latitude"]])
6395
6396 # BYRADIUS or BYBOX
6397 if kwargs["radius"] is None:
6398 if kwargs["width"] is None or kwargs["height"] is None:
6399 raise DataError("GEOSEARCH must have radius or width and height")
6400 if kwargs["unit"] is None:
6401 raise DataError("GEOSEARCH must have unit")
6402 if kwargs["unit"].lower() not in ("m", "km", "mi", "ft"):
6403 raise DataError("GEOSEARCH invalid unit")
6404 if kwargs["radius"]:
6405 if kwargs["width"] or kwargs["height"]:
6406 raise DataError(
6407 "GEOSEARCH radius and width or height cant be set together"
6408 )
6409 pieces.extend([b"BYRADIUS", kwargs["radius"], kwargs["unit"]])
6410 if kwargs["width"] and kwargs["height"]:
6411 pieces.extend([b"BYBOX", kwargs["width"], kwargs["height"], kwargs["unit"]])
6412
6413 # sort
6414 if kwargs["sort"]:
6415 if kwargs["sort"].upper() == "ASC":
6416 pieces.append(b"ASC")
6417 elif kwargs["sort"].upper() == "DESC":
6418 pieces.append(b"DESC")
6419 else:
6420 raise DataError("GEOSEARCH invalid sort")
6421
6422 # count any
6423 if kwargs["count"]:
6424 pieces.extend([b"COUNT", kwargs["count"]])
6425 if kwargs["any"]:
6426 pieces.append(b"ANY")
6427 elif kwargs["any"]:
6428 raise DataError("GEOSEARCH ``any`` can't be provided without count")
6429
6430 # other properties
6431 for arg_name, byte_repr in (
6432 ("withdist", b"WITHDIST"),
6433 ("withcoord", b"WITHCOORD"),
6434 ("withhash", b"WITHHASH"),
6435 ("store_dist", b"STOREDIST"),
6436 ):
6437 if kwargs[arg_name]:
6438 pieces.append(byte_repr)
6439
6440 kwargs["keys"] = [args[0] if command == "GEOSEARCH" else args[1]]
6441
6442 return self.execute_command(command, *pieces, **kwargs)
6443
6444
6445AsyncGeoCommands = GeoCommands
6446
6447
6448class ModuleCommands(CommandsProtocol):
6449 """
6450 Redis Module commands.
6451 see: https://redis.io/topics/modules-intro
6452 """
6453
6454 def module_load(self, path, *args) -> ResponseT:
6455 """
6456 Loads the module from ``path``.
6457 Passes all ``*args`` to the module, during loading.
6458 Raises ``ModuleError`` if a module is not found at ``path``.
6459
6460 For more information, see https://redis.io/commands/module-load
6461 """
6462 return self.execute_command("MODULE LOAD", path, *args)
6463
6464 def module_loadex(
6465 self,
6466 path: str,
6467 options: Optional[List[str]] = None,
6468 args: Optional[List[str]] = None,
6469 ) -> ResponseT:
6470 """
6471 Loads a module from a dynamic library at runtime with configuration directives.
6472
6473 For more information, see https://redis.io/commands/module-loadex
6474 """
6475 pieces = []
6476 if options is not None:
6477 pieces.append("CONFIG")
6478 pieces.extend(options)
6479 if args is not None:
6480 pieces.append("ARGS")
6481 pieces.extend(args)
6482
6483 return self.execute_command("MODULE LOADEX", path, *pieces)
6484
6485 def module_unload(self, name) -> ResponseT:
6486 """
6487 Unloads the module ``name``.
6488 Raises ``ModuleError`` if ``name`` is not in loaded modules.
6489
6490 For more information, see https://redis.io/commands/module-unload
6491 """
6492 return self.execute_command("MODULE UNLOAD", name)
6493
6494 def module_list(self) -> ResponseT:
6495 """
6496 Returns a list of dictionaries containing the name and version of
6497 all loaded modules.
6498
6499 For more information, see https://redis.io/commands/module-list
6500 """
6501 return self.execute_command("MODULE LIST")
6502
6503 def command_info(self) -> None:
6504 raise NotImplementedError(
6505 "COMMAND INFO is intentionally not implemented in the client."
6506 )
6507
6508 def command_count(self) -> ResponseT:
6509 return self.execute_command("COMMAND COUNT")
6510
6511 def command_getkeys(self, *args) -> ResponseT:
6512 return self.execute_command("COMMAND GETKEYS", *args)
6513
6514 def command(self) -> ResponseT:
6515 return self.execute_command("COMMAND")
6516
6517
6518class AsyncModuleCommands(ModuleCommands):
6519 async def command_info(self) -> None:
6520 return super().command_info()
6521
6522
6523class ClusterCommands(CommandsProtocol):
6524 """
6525 Class for Redis Cluster commands
6526 """
6527
6528 def cluster(self, cluster_arg, *args, **kwargs) -> ResponseT:
6529 return self.execute_command(f"CLUSTER {cluster_arg.upper()}", *args, **kwargs)
6530
6531 def readwrite(self, **kwargs) -> ResponseT:
6532 """
6533 Disables read queries for a connection to a Redis Cluster slave node.
6534
6535 For more information, see https://redis.io/commands/readwrite
6536 """
6537 return self.execute_command("READWRITE", **kwargs)
6538
6539 def readonly(self, **kwargs) -> ResponseT:
6540 """
6541 Enables read queries for a connection to a Redis Cluster replica node.
6542
6543 For more information, see https://redis.io/commands/readonly
6544 """
6545 return self.execute_command("READONLY", **kwargs)
6546
6547
6548AsyncClusterCommands = ClusterCommands
6549
6550
6551class FunctionCommands:
6552 """
6553 Redis Function commands
6554 """
6555
6556 def function_load(
6557 self, code: str, replace: Optional[bool] = False
6558 ) -> Union[Awaitable[str], str]:
6559 """
6560 Load a library to Redis.
6561 :param code: the source code (must start with
6562 Shebang statement that provides a metadata about the library)
6563 :param replace: changes the behavior to overwrite the existing library
6564 with the new contents.
6565 Return the library name that was loaded.
6566
6567 For more information, see https://redis.io/commands/function-load
6568 """
6569 pieces = ["REPLACE"] if replace else []
6570 pieces.append(code)
6571 return self.execute_command("FUNCTION LOAD", *pieces)
6572
6573 def function_delete(self, library: str) -> Union[Awaitable[str], str]:
6574 """
6575 Delete the library called ``library`` and all its functions.
6576
6577 For more information, see https://redis.io/commands/function-delete
6578 """
6579 return self.execute_command("FUNCTION DELETE", library)
6580
6581 def function_flush(self, mode: str = "SYNC") -> Union[Awaitable[str], str]:
6582 """
6583 Deletes all the libraries.
6584
6585 For more information, see https://redis.io/commands/function-flush
6586 """
6587 return self.execute_command("FUNCTION FLUSH", mode)
6588
6589 def function_list(
6590 self, library: Optional[str] = "*", withcode: Optional[bool] = False
6591 ) -> Union[Awaitable[List], List]:
6592 """
6593 Return information about the functions and libraries.
6594
6595 Args:
6596
6597 library: specify a pattern for matching library names
6598 withcode: cause the server to include the libraries source implementation
6599 in the reply
6600 """
6601 args = ["LIBRARYNAME", library]
6602 if withcode:
6603 args.append("WITHCODE")
6604 return self.execute_command("FUNCTION LIST", *args)
6605
6606 def _fcall(
6607 self, command: str, function, numkeys: int, *keys_and_args: Any
6608 ) -> Union[Awaitable[str], str]:
6609 return self.execute_command(command, function, numkeys, *keys_and_args)
6610
6611 def fcall(
6612 self, function, numkeys: int, *keys_and_args: Any
6613 ) -> Union[Awaitable[str], str]:
6614 """
6615 Invoke a function.
6616
6617 For more information, see https://redis.io/commands/fcall
6618 """
6619 return self._fcall("FCALL", function, numkeys, *keys_and_args)
6620
6621 def fcall_ro(
6622 self, function, numkeys: int, *keys_and_args: Any
6623 ) -> Union[Awaitable[str], str]:
6624 """
6625 This is a read-only variant of the FCALL command that cannot
6626 execute commands that modify data.
6627
6628 For more information, see https://redis.io/commands/fcall_ro
6629 """
6630 return self._fcall("FCALL_RO", function, numkeys, *keys_and_args)
6631
6632 def function_dump(self) -> Union[Awaitable[str], str]:
6633 """
6634 Return the serialized payload of loaded libraries.
6635
6636 For more information, see https://redis.io/commands/function-dump
6637 """
6638 from redis.client import NEVER_DECODE
6639
6640 options = {}
6641 options[NEVER_DECODE] = []
6642
6643 return self.execute_command("FUNCTION DUMP", **options)
6644
6645 def function_restore(
6646 self, payload: str, policy: Optional[str] = "APPEND"
6647 ) -> Union[Awaitable[str], str]:
6648 """
6649 Restore libraries from the serialized ``payload``.
6650 You can use the optional policy argument to provide a policy
6651 for handling existing libraries.
6652
6653 For more information, see https://redis.io/commands/function-restore
6654 """
6655 return self.execute_command("FUNCTION RESTORE", payload, policy)
6656
6657 def function_kill(self) -> Union[Awaitable[str], str]:
6658 """
6659 Kill a function that is currently executing.
6660
6661 For more information, see https://redis.io/commands/function-kill
6662 """
6663 return self.execute_command("FUNCTION KILL")
6664
6665 def function_stats(self) -> Union[Awaitable[List], List]:
6666 """
6667 Return information about the function that's currently running
6668 and information about the available execution engines.
6669
6670 For more information, see https://redis.io/commands/function-stats
6671 """
6672 return self.execute_command("FUNCTION STATS")
6673
6674
6675AsyncFunctionCommands = FunctionCommands
6676
6677
6678class DataAccessCommands(
6679 BasicKeyCommands,
6680 HyperlogCommands,
6681 HashCommands,
6682 GeoCommands,
6683 ListCommands,
6684 ScanCommands,
6685 SetCommands,
6686 StreamCommands,
6687 SortedSetCommands,
6688):
6689 """
6690 A class containing all of the implemented data access redis commands.
6691 This class is to be used as a mixin for synchronous Redis clients.
6692 """
6693
6694
6695class AsyncDataAccessCommands(
6696 AsyncBasicKeyCommands,
6697 AsyncHyperlogCommands,
6698 AsyncHashCommands,
6699 AsyncGeoCommands,
6700 AsyncListCommands,
6701 AsyncScanCommands,
6702 AsyncSetCommands,
6703 AsyncStreamCommands,
6704 AsyncSortedSetCommands,
6705):
6706 """
6707 A class containing all of the implemented data access redis commands.
6708 This class is to be used as a mixin for asynchronous Redis clients.
6709 """
6710
6711
6712class CoreCommands(
6713 ACLCommands,
6714 ClusterCommands,
6715 DataAccessCommands,
6716 ManagementCommands,
6717 ModuleCommands,
6718 PubSubCommands,
6719 ScriptCommands,
6720 FunctionCommands,
6721):
6722 """
6723 A class containing all of the implemented redis commands. This class is
6724 to be used as a mixin for synchronous Redis clients.
6725 """
6726
6727
6728class AsyncCoreCommands(
6729 AsyncACLCommands,
6730 AsyncClusterCommands,
6731 AsyncDataAccessCommands,
6732 AsyncManagementCommands,
6733 AsyncModuleCommands,
6734 AsyncPubSubCommands,
6735 AsyncScriptCommands,
6736 AsyncFunctionCommands,
6737):
6738 """
6739 A class containing all of the implemented redis commands. This class is
6740 to be used as a mixin for asynchronous Redis clients.
6741 """