Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/redis/commands/core.py: 22%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# from __future__ import annotations
3import datetime
4import hashlib
6# Try to import the xxhash library as an optional dependency
7try:
8 import xxhash
10 HAS_XXHASH = True
11except ImportError:
12 HAS_XXHASH = False
14import warnings
15from enum import Enum
16from typing import (
17 TYPE_CHECKING,
18 Any,
19 AsyncIterator,
20 Awaitable,
21 Callable,
22 Dict,
23 Iterable,
24 Iterator,
25 List,
26 Literal,
27 Mapping,
28 Optional,
29 Sequence,
30 Set,
31 Tuple,
32 Union,
33)
35from redis.exceptions import ConnectionError, DataError, NoScriptError, RedisError
36from redis.typing import (
37 AbsExpiryT,
38 AnyKeyT,
39 BitfieldOffsetT,
40 ChannelT,
41 CommandsProtocol,
42 ConsumerT,
43 EncodableT,
44 ExpiryT,
45 FieldT,
46 GroupT,
47 KeysT,
48 KeyT,
49 Number,
50 PatternT,
51 ResponseT,
52 ScriptTextT,
53 StreamIdT,
54 TimeoutSecT,
55 ZScoreBoundT,
56)
57from redis.utils import (
58 deprecated_function,
59 experimental_args,
60 experimental_method,
61 extract_expire_flags,
62)
64from .helpers import at_most_one_value_set, list_or_args
66if TYPE_CHECKING:
67 import redis.asyncio.client
68 import redis.client
71class ACLCommands(CommandsProtocol):
72 """
73 Redis Access Control List (ACL) commands.
74 see: https://redis.io/topics/acl
75 """
77 def acl_cat(self, category: Optional[str] = None, **kwargs) -> ResponseT:
78 """
79 Returns a list of categories or commands within a category.
81 If ``category`` is not supplied, returns a list of all categories.
82 If ``category`` is supplied, returns a list of all commands within
83 that category.
85 For more information, see https://redis.io/commands/acl-cat
86 """
87 pieces: list[EncodableT] = [category] if category else []
88 return self.execute_command("ACL CAT", *pieces, **kwargs)
90 def acl_dryrun(self, username, *args, **kwargs):
91 """
92 Simulate the execution of a given command by a given ``username``.
94 For more information, see https://redis.io/commands/acl-dryrun
95 """
96 return self.execute_command("ACL DRYRUN", username, *args, **kwargs)
98 def acl_deluser(self, *username: str, **kwargs) -> ResponseT:
99 """
100 Delete the ACL for the specified ``username``\\s
102 For more information, see https://redis.io/commands/acl-deluser
103 """
104 return self.execute_command("ACL DELUSER", *username, **kwargs)
106 def acl_genpass(self, bits: Optional[int] = None, **kwargs) -> ResponseT:
107 """Generate a random password value.
108 If ``bits`` is supplied then use this number of bits, rounded to
109 the next multiple of 4.
110 See: https://redis.io/commands/acl-genpass
111 """
112 pieces = []
113 if bits is not None:
114 try:
115 b = int(bits)
116 if b < 0 or b > 4096:
117 raise ValueError
118 pieces.append(b)
119 except ValueError:
120 raise DataError(
121 "genpass optionally accepts a bits argument, between 0 and 4096."
122 )
123 return self.execute_command("ACL GENPASS", *pieces, **kwargs)
125 def acl_getuser(self, username: str, **kwargs) -> ResponseT:
126 """
127 Get the ACL details for the specified ``username``.
129 If ``username`` does not exist, return None
131 For more information, see https://redis.io/commands/acl-getuser
132 """
133 return self.execute_command("ACL GETUSER", username, **kwargs)
135 def acl_help(self, **kwargs) -> ResponseT:
136 """The ACL HELP command returns helpful text describing
137 the different subcommands.
139 For more information, see https://redis.io/commands/acl-help
140 """
141 return self.execute_command("ACL HELP", **kwargs)
143 def acl_list(self, **kwargs) -> ResponseT:
144 """
145 Return a list of all ACLs on the server
147 For more information, see https://redis.io/commands/acl-list
148 """
149 return self.execute_command("ACL LIST", **kwargs)
151 def acl_log(self, count: Optional[int] = None, **kwargs) -> ResponseT:
152 """
153 Get ACL logs as a list.
154 :param int count: Get logs[0:count].
155 :rtype: List.
157 For more information, see https://redis.io/commands/acl-log
158 """
159 args = []
160 if count is not None:
161 if not isinstance(count, int):
162 raise DataError("ACL LOG count must be an integer")
163 args.append(count)
165 return self.execute_command("ACL LOG", *args, **kwargs)
167 def acl_log_reset(self, **kwargs) -> ResponseT:
168 """
169 Reset ACL logs.
170 :rtype: Boolean.
172 For more information, see https://redis.io/commands/acl-log
173 """
174 args = [b"RESET"]
175 return self.execute_command("ACL LOG", *args, **kwargs)
177 def acl_load(self, **kwargs) -> ResponseT:
178 """
179 Load ACL rules from the configured ``aclfile``.
181 Note that the server must be configured with the ``aclfile``
182 directive to be able to load ACL rules from an aclfile.
184 For more information, see https://redis.io/commands/acl-load
185 """
186 return self.execute_command("ACL LOAD", **kwargs)
188 def acl_save(self, **kwargs) -> ResponseT:
189 """
190 Save ACL rules to the configured ``aclfile``.
192 Note that the server must be configured with the ``aclfile``
193 directive to be able to save ACL rules to an aclfile.
195 For more information, see https://redis.io/commands/acl-save
196 """
197 return self.execute_command("ACL SAVE", **kwargs)
199 def acl_setuser(
200 self,
201 username: str,
202 enabled: bool = False,
203 nopass: bool = False,
204 passwords: Optional[Union[str, Iterable[str]]] = None,
205 hashed_passwords: Optional[Union[str, Iterable[str]]] = None,
206 categories: Optional[Iterable[str]] = None,
207 commands: Optional[Iterable[str]] = None,
208 keys: Optional[Iterable[KeyT]] = None,
209 channels: Optional[Iterable[ChannelT]] = None,
210 selectors: Optional[Iterable[Tuple[str, KeyT]]] = None,
211 reset: bool = False,
212 reset_keys: bool = False,
213 reset_channels: bool = False,
214 reset_passwords: bool = False,
215 **kwargs,
216 ) -> ResponseT:
217 """
218 Create or update an ACL user.
220 Create or update the ACL for `username`. If the user already exists,
221 the existing ACL is completely overwritten and replaced with the
222 specified values.
224 For more information, see https://redis.io/commands/acl-setuser
226 Args:
227 username: The name of the user whose ACL is to be created or updated.
228 enabled: Indicates whether the user should be allowed to authenticate.
229 Defaults to `False`.
230 nopass: Indicates whether the user can authenticate without a password.
231 This cannot be `True` if `passwords` are also specified.
232 passwords: A list of plain text passwords to add to or remove from the user.
233 Each password must be prefixed with a '+' to add or a '-' to
234 remove. For convenience, a single prefixed string can be used
235 when adding or removing a single password.
236 hashed_passwords: A list of SHA-256 hashed passwords to add to or remove
237 from the user. Each hashed password must be prefixed with
238 a '+' to add or a '-' to remove. For convenience, a single
239 prefixed string can be used when adding or removing a
240 single password.
241 categories: A list of strings representing category permissions. Each string
242 must be prefixed with either a '+' to add the category
243 permission or a '-' to remove the category permission.
244 commands: A list of strings representing command permissions. Each string
245 must be prefixed with either a '+' to add the command permission
246 or a '-' to remove the command permission.
247 keys: A list of key patterns to grant the user access to. Key patterns allow
248 ``'*'`` to support wildcard matching. For example, ``'*'`` grants
249 access to all keys while ``'cache:*'`` grants access to all keys that
250 are prefixed with ``cache:``.
251 `keys` should not be prefixed with a ``'~'``.
252 reset: Indicates whether the user should be fully reset prior to applying
253 the new ACL. Setting this to `True` will remove all existing
254 passwords, flags, and privileges from the user and then apply the
255 specified rules. If `False`, the user's existing passwords, flags,
256 and privileges will be kept and any new specified rules will be
257 applied on top.
258 reset_keys: Indicates whether the user's key permissions should be reset
259 prior to applying any new key permissions specified in `keys`.
260 If `False`, the user's existing key permissions will be kept and
261 any new specified key permissions will be applied on top.
262 reset_channels: Indicates whether the user's channel permissions should be
263 reset prior to applying any new channel permissions
264 specified in `channels`. If `False`, the user's existing
265 channel permissions will be kept and any new specified
266 channel permissions will be applied on top.
267 reset_passwords: Indicates whether to remove all existing passwords and the
268 `nopass` flag from the user prior to applying any new
269 passwords specified in `passwords` or `hashed_passwords`.
270 If `False`, the user's existing passwords and `nopass`
271 status will be kept and any new specified passwords or
272 hashed passwords will be applied on top.
273 """
274 encoder = self.get_encoder()
275 pieces: List[EncodableT] = [username]
277 if reset:
278 pieces.append(b"reset")
280 if reset_keys:
281 pieces.append(b"resetkeys")
283 if reset_channels:
284 pieces.append(b"resetchannels")
286 if reset_passwords:
287 pieces.append(b"resetpass")
289 if enabled:
290 pieces.append(b"on")
291 else:
292 pieces.append(b"off")
294 if (passwords or hashed_passwords) and nopass:
295 raise DataError(
296 "Cannot set 'nopass' and supply 'passwords' or 'hashed_passwords'"
297 )
299 if passwords:
300 # as most users will have only one password, allow remove_passwords
301 # to be specified as a simple string or a list
302 passwords = list_or_args(passwords, [])
303 for i, password in enumerate(passwords):
304 password = encoder.encode(password)
305 if password.startswith(b"+"):
306 pieces.append(b">%s" % password[1:])
307 elif password.startswith(b"-"):
308 pieces.append(b"<%s" % password[1:])
309 else:
310 raise DataError(
311 f"Password {i} must be prefixed with a "
312 f'"+" to add or a "-" to remove'
313 )
315 if hashed_passwords:
316 # as most users will have only one password, allow remove_passwords
317 # to be specified as a simple string or a list
318 hashed_passwords = list_or_args(hashed_passwords, [])
319 for i, hashed_password in enumerate(hashed_passwords):
320 hashed_password = encoder.encode(hashed_password)
321 if hashed_password.startswith(b"+"):
322 pieces.append(b"#%s" % hashed_password[1:])
323 elif hashed_password.startswith(b"-"):
324 pieces.append(b"!%s" % hashed_password[1:])
325 else:
326 raise DataError(
327 f"Hashed password {i} must be prefixed with a "
328 f'"+" to add or a "-" to remove'
329 )
331 if nopass:
332 pieces.append(b"nopass")
334 if categories:
335 for category in categories:
336 category = encoder.encode(category)
337 # categories can be prefixed with one of (+@, +, -@, -)
338 if category.startswith(b"+@"):
339 pieces.append(category)
340 elif category.startswith(b"+"):
341 pieces.append(b"+@%s" % category[1:])
342 elif category.startswith(b"-@"):
343 pieces.append(category)
344 elif category.startswith(b"-"):
345 pieces.append(b"-@%s" % category[1:])
346 else:
347 raise DataError(
348 f'Category "{encoder.decode(category, force=True)}" '
349 'must be prefixed with "+" or "-"'
350 )
351 if commands:
352 for cmd in commands:
353 cmd = encoder.encode(cmd)
354 if not cmd.startswith(b"+") and not cmd.startswith(b"-"):
355 raise DataError(
356 f'Command "{encoder.decode(cmd, force=True)}" '
357 'must be prefixed with "+" or "-"'
358 )
359 pieces.append(cmd)
361 if keys:
362 for key in keys:
363 key = encoder.encode(key)
364 if not key.startswith(b"%") and not key.startswith(b"~"):
365 key = b"~%s" % key
366 pieces.append(key)
368 if channels:
369 for channel in channels:
370 channel = encoder.encode(channel)
371 pieces.append(b"&%s" % channel)
373 if selectors:
374 for cmd, key in selectors:
375 cmd = encoder.encode(cmd)
376 if not cmd.startswith(b"+") and not cmd.startswith(b"-"):
377 raise DataError(
378 f'Command "{encoder.decode(cmd, force=True)}" '
379 'must be prefixed with "+" or "-"'
380 )
382 key = encoder.encode(key)
383 if not key.startswith(b"%") and not key.startswith(b"~"):
384 key = b"~%s" % key
386 pieces.append(b"(%s %s)" % (cmd, key))
388 return self.execute_command("ACL SETUSER", *pieces, **kwargs)
390 def acl_users(self, **kwargs) -> ResponseT:
391 """Returns a list of all registered users on the server.
393 For more information, see https://redis.io/commands/acl-users
394 """
395 return self.execute_command("ACL USERS", **kwargs)
397 def acl_whoami(self, **kwargs) -> ResponseT:
398 """Get the username for the current connection
400 For more information, see https://redis.io/commands/acl-whoami
401 """
402 return self.execute_command("ACL WHOAMI", **kwargs)
405AsyncACLCommands = ACLCommands
408class HotkeysMetricsTypes(Enum):
409 CPU = "CPU"
410 NET = "NET"
413class ManagementCommands(CommandsProtocol):
414 """
415 Redis management commands
416 """
418 def auth(self, password: str, username: Optional[str] = None, **kwargs):
419 """
420 Authenticates the user. If you do not pass username, Redis will try to
421 authenticate for the "default" user. If you do pass username, it will
422 authenticate for the given user.
423 For more information, see https://redis.io/commands/auth
424 """
425 pieces = []
426 if username is not None:
427 pieces.append(username)
428 pieces.append(password)
429 return self.execute_command("AUTH", *pieces, **kwargs)
431 def bgrewriteaof(self, **kwargs):
432 """Tell the Redis server to rewrite the AOF file from data in memory.
434 For more information, see https://redis.io/commands/bgrewriteaof
435 """
436 return self.execute_command("BGREWRITEAOF", **kwargs)
438 def bgsave(self, schedule: bool = True, **kwargs) -> ResponseT:
439 """
440 Tell the Redis server to save its data to disk. Unlike save(),
441 this method is asynchronous and returns immediately.
443 For more information, see https://redis.io/commands/bgsave
444 """
445 pieces = []
446 if schedule:
447 pieces.append("SCHEDULE")
448 return self.execute_command("BGSAVE", *pieces, **kwargs)
450 def role(self) -> ResponseT:
451 """
452 Provide information on the role of a Redis instance in
453 the context of replication, by returning if the instance
454 is currently a master, slave, or sentinel.
456 For more information, see https://redis.io/commands/role
457 """
458 return self.execute_command("ROLE")
460 def client_kill(self, address: str, **kwargs) -> ResponseT:
461 """Disconnects the client at ``address`` (ip:port)
463 For more information, see https://redis.io/commands/client-kill
464 """
465 return self.execute_command("CLIENT KILL", address, **kwargs)
467 def client_kill_filter(
468 self,
469 _id: Optional[str] = None,
470 _type: Optional[str] = None,
471 addr: Optional[str] = None,
472 skipme: Optional[bool] = None,
473 laddr: Optional[bool] = None,
474 user: Optional[str] = None,
475 maxage: Optional[int] = None,
476 **kwargs,
477 ) -> ResponseT:
478 """
479 Disconnects client(s) using a variety of filter options
480 :param _id: Kills a client by its unique ID field
481 :param _type: Kills a client by type where type is one of 'normal',
482 'master', 'slave' or 'pubsub'
483 :param addr: Kills a client by its 'address:port'
484 :param skipme: If True, then the client calling the command
485 will not get killed even if it is identified by one of the filter
486 options. If skipme is not provided, the server defaults to skipme=True
487 :param laddr: Kills a client by its 'local (bind) address:port'
488 :param user: Kills a client for a specific user name
489 :param maxage: Kills clients that are older than the specified age in seconds
490 """
491 args = []
492 if _type is not None:
493 client_types = ("normal", "master", "slave", "pubsub")
494 if str(_type).lower() not in client_types:
495 raise DataError(f"CLIENT KILL type must be one of {client_types!r}")
496 args.extend((b"TYPE", _type))
497 if skipme is not None:
498 if not isinstance(skipme, bool):
499 raise DataError("CLIENT KILL skipme must be a bool")
500 if skipme:
501 args.extend((b"SKIPME", b"YES"))
502 else:
503 args.extend((b"SKIPME", b"NO"))
504 if _id is not None:
505 args.extend((b"ID", _id))
506 if addr is not None:
507 args.extend((b"ADDR", addr))
508 if laddr is not None:
509 args.extend((b"LADDR", laddr))
510 if user is not None:
511 args.extend((b"USER", user))
512 if maxage is not None:
513 args.extend((b"MAXAGE", maxage))
514 if not args:
515 raise DataError(
516 "CLIENT KILL <filter> <value> ... ... <filter> "
517 "<value> must specify at least one filter"
518 )
519 return self.execute_command("CLIENT KILL", *args, **kwargs)
521 def client_info(self, **kwargs) -> ResponseT:
522 """
523 Returns information and statistics about the current
524 client connection.
526 For more information, see https://redis.io/commands/client-info
527 """
528 return self.execute_command("CLIENT INFO", **kwargs)
530 def client_list(
531 self, _type: Optional[str] = None, client_id: List[EncodableT] = [], **kwargs
532 ) -> ResponseT:
533 """
534 Returns a list of currently connected clients.
535 If type of client specified, only that type will be returned.
537 :param _type: optional. one of the client types (normal, master,
538 replica, pubsub)
539 :param client_id: optional. a list of client ids
541 For more information, see https://redis.io/commands/client-list
542 """
543 args = []
544 if _type is not None:
545 client_types = ("normal", "master", "replica", "pubsub")
546 if str(_type).lower() not in client_types:
547 raise DataError(f"CLIENT LIST _type must be one of {client_types!r}")
548 args.append(b"TYPE")
549 args.append(_type)
550 if not isinstance(client_id, list):
551 raise DataError("client_id must be a list")
552 if client_id:
553 args.append(b"ID")
554 args += client_id
555 return self.execute_command("CLIENT LIST", *args, **kwargs)
557 def client_getname(self, **kwargs) -> ResponseT:
558 """
559 Returns the current connection name
561 For more information, see https://redis.io/commands/client-getname
562 """
563 return self.execute_command("CLIENT GETNAME", **kwargs)
565 def client_getredir(self, **kwargs) -> ResponseT:
566 """
567 Returns the ID (an integer) of the client to whom we are
568 redirecting tracking notifications.
570 see: https://redis.io/commands/client-getredir
571 """
572 return self.execute_command("CLIENT GETREDIR", **kwargs)
574 def client_reply(
575 self, reply: Union[Literal["ON"], Literal["OFF"], Literal["SKIP"]], **kwargs
576 ) -> ResponseT:
577 """
578 Enable and disable redis server replies.
580 ``reply`` Must be ON OFF or SKIP,
581 ON - The default most with server replies to commands
582 OFF - Disable server responses to commands
583 SKIP - Skip the response of the immediately following command.
585 Note: When setting OFF or SKIP replies, you will need a client object
586 with a timeout specified in seconds, and will need to catch the
587 TimeoutError.
588 The test_client_reply unit test illustrates this, and
589 conftest.py has a client with a timeout.
591 See https://redis.io/commands/client-reply
592 """
593 replies = ["ON", "OFF", "SKIP"]
594 if reply not in replies:
595 raise DataError(f"CLIENT REPLY must be one of {replies!r}")
596 return self.execute_command("CLIENT REPLY", reply, **kwargs)
598 def client_id(self, **kwargs) -> ResponseT:
599 """
600 Returns the current connection id
602 For more information, see https://redis.io/commands/client-id
603 """
604 return self.execute_command("CLIENT ID", **kwargs)
606 def client_tracking_on(
607 self,
608 clientid: Optional[int] = None,
609 prefix: Sequence[KeyT] = [],
610 bcast: bool = False,
611 optin: bool = False,
612 optout: bool = False,
613 noloop: bool = False,
614 ) -> ResponseT:
615 """
616 Turn on the tracking mode.
617 For more information, about the options look at client_tracking func.
619 See https://redis.io/commands/client-tracking
620 """
621 return self.client_tracking(
622 True, clientid, prefix, bcast, optin, optout, noloop
623 )
625 def client_tracking_off(
626 self,
627 clientid: Optional[int] = None,
628 prefix: Sequence[KeyT] = [],
629 bcast: bool = False,
630 optin: bool = False,
631 optout: bool = False,
632 noloop: bool = False,
633 ) -> ResponseT:
634 """
635 Turn off the tracking mode.
636 For more information, about the options look at client_tracking func.
638 See https://redis.io/commands/client-tracking
639 """
640 return self.client_tracking(
641 False, clientid, prefix, bcast, optin, optout, noloop
642 )
644 def client_tracking(
645 self,
646 on: bool = True,
647 clientid: Optional[int] = None,
648 prefix: Sequence[KeyT] = [],
649 bcast: bool = False,
650 optin: bool = False,
651 optout: bool = False,
652 noloop: bool = False,
653 **kwargs,
654 ) -> ResponseT:
655 """
656 Enables the tracking feature of the Redis server, that is used
657 for server assisted client side caching.
659 ``on`` indicate for tracking on or tracking off. The default is on.
661 ``clientid`` send invalidation messages to the connection with
662 the specified ID.
664 ``bcast`` enable tracking in broadcasting mode. In this mode
665 invalidation messages are reported for all the prefixes
666 specified, regardless of the keys requested by the connection.
668 ``optin`` when broadcasting is NOT active, normally don't track
669 keys in read only commands, unless they are called immediately
670 after a CLIENT CACHING yes command.
672 ``optout`` when broadcasting is NOT active, normally track keys in
673 read only commands, unless they are called immediately after a
674 CLIENT CACHING no command.
676 ``noloop`` don't send notifications about keys modified by this
677 connection itself.
679 ``prefix`` for broadcasting, register a given key prefix, so that
680 notifications will be provided only for keys starting with this string.
682 See https://redis.io/commands/client-tracking
683 """
685 if len(prefix) != 0 and bcast is False:
686 raise DataError("Prefix can only be used with bcast")
688 pieces = ["ON"] if on else ["OFF"]
689 if clientid is not None:
690 pieces.extend(["REDIRECT", clientid])
691 for p in prefix:
692 pieces.extend(["PREFIX", p])
693 if bcast:
694 pieces.append("BCAST")
695 if optin:
696 pieces.append("OPTIN")
697 if optout:
698 pieces.append("OPTOUT")
699 if noloop:
700 pieces.append("NOLOOP")
702 return self.execute_command("CLIENT TRACKING", *pieces, **kwargs)
704 def client_trackinginfo(self, **kwargs) -> ResponseT:
705 """
706 Returns the information about the current client connection's
707 use of the server assisted client side cache.
709 See https://redis.io/commands/client-trackinginfo
710 """
711 return self.execute_command("CLIENT TRACKINGINFO", **kwargs)
713 def client_setname(self, name: str, **kwargs) -> ResponseT:
714 """
715 Sets the current connection name
717 For more information, see https://redis.io/commands/client-setname
719 .. note::
720 This method sets client name only for **current** connection.
722 If you want to set a common name for all connections managed
723 by this client, use ``client_name`` constructor argument.
724 """
725 return self.execute_command("CLIENT SETNAME", name, **kwargs)
727 def client_setinfo(self, attr: str, value: str, **kwargs) -> ResponseT:
728 """
729 Sets the current connection library name or version
730 For mor information see https://redis.io/commands/client-setinfo
731 """
732 return self.execute_command("CLIENT SETINFO", attr, value, **kwargs)
734 def client_unblock(
735 self, client_id: int, error: bool = False, **kwargs
736 ) -> ResponseT:
737 """
738 Unblocks a connection by its client id.
739 If ``error`` is True, unblocks the client with a special error message.
740 If ``error`` is False (default), the client is unblocked using the
741 regular timeout mechanism.
743 For more information, see https://redis.io/commands/client-unblock
744 """
745 args = ["CLIENT UNBLOCK", int(client_id)]
746 if error:
747 args.append(b"ERROR")
748 return self.execute_command(*args, **kwargs)
750 def client_pause(self, timeout: int, all: bool = True, **kwargs) -> ResponseT:
751 """
752 Suspend all the Redis clients for the specified amount of time.
755 For more information, see https://redis.io/commands/client-pause
757 Args:
758 timeout: milliseconds to pause clients
759 all: If true (default) all client commands are blocked.
760 otherwise, clients are only blocked if they attempt to execute
761 a write command.
763 For the WRITE mode, some commands have special behavior:
765 * EVAL/EVALSHA: Will block client for all scripts.
766 * PUBLISH: Will block client.
767 * PFCOUNT: Will block client.
768 * WAIT: Acknowledgments will be delayed, so this command will
769 appear blocked.
770 """
771 args = ["CLIENT PAUSE", str(timeout)]
772 if not isinstance(timeout, int):
773 raise DataError("CLIENT PAUSE timeout must be an integer")
774 if not all:
775 args.append("WRITE")
776 return self.execute_command(*args, **kwargs)
778 def client_unpause(self, **kwargs) -> ResponseT:
779 """
780 Unpause all redis clients
782 For more information, see https://redis.io/commands/client-unpause
783 """
784 return self.execute_command("CLIENT UNPAUSE", **kwargs)
786 def client_no_evict(self, mode: str) -> Union[Awaitable[str], str]:
787 """
788 Sets the client eviction mode for the current connection.
790 For more information, see https://redis.io/commands/client-no-evict
791 """
792 return self.execute_command("CLIENT NO-EVICT", mode)
794 def client_no_touch(self, mode: str) -> Union[Awaitable[str], str]:
795 """
796 # The command controls whether commands sent by the client will alter
797 # the LRU/LFU of the keys they access.
798 # When turned on, the current client will not change LFU/LRU stats,
799 # unless it sends the TOUCH command.
801 For more information, see https://redis.io/commands/client-no-touch
802 """
803 return self.execute_command("CLIENT NO-TOUCH", mode)
805 def command(self, **kwargs):
806 """
807 Returns dict reply of details about all Redis commands.
809 For more information, see https://redis.io/commands/command
810 """
811 return self.execute_command("COMMAND", **kwargs)
813 def command_info(self, **kwargs) -> None:
814 raise NotImplementedError(
815 "COMMAND INFO is intentionally not implemented in the client."
816 )
818 def command_count(self, **kwargs) -> ResponseT:
819 return self.execute_command("COMMAND COUNT", **kwargs)
821 def command_list(
822 self,
823 module: Optional[str] = None,
824 category: Optional[str] = None,
825 pattern: Optional[str] = None,
826 ) -> ResponseT:
827 """
828 Return an array of the server's command names.
829 You can use one of the following filters:
830 ``module``: get the commands that belong to the module
831 ``category``: get the commands in the ACL category
832 ``pattern``: get the commands that match the given pattern
834 For more information, see https://redis.io/commands/command-list/
835 """
836 pieces = []
837 if module is not None:
838 pieces.extend(["MODULE", module])
839 if category is not None:
840 pieces.extend(["ACLCAT", category])
841 if pattern is not None:
842 pieces.extend(["PATTERN", pattern])
844 if pieces:
845 pieces.insert(0, "FILTERBY")
847 return self.execute_command("COMMAND LIST", *pieces)
849 def command_getkeysandflags(self, *args: str) -> List[Union[str, List[str]]]:
850 """
851 Returns array of keys from a full Redis command and their usage flags.
853 For more information, see https://redis.io/commands/command-getkeysandflags
854 """
855 return self.execute_command("COMMAND GETKEYSANDFLAGS", *args)
857 def command_docs(self, *args):
858 """
859 This function throws a NotImplementedError since it is intentionally
860 not supported.
861 """
862 raise NotImplementedError(
863 "COMMAND DOCS is intentionally not implemented in the client."
864 )
866 def config_get(
867 self, pattern: PatternT = "*", *args: PatternT, **kwargs
868 ) -> ResponseT:
869 """
870 Return a dictionary of configuration based on the ``pattern``
872 For more information, see https://redis.io/commands/config-get
873 """
874 return self.execute_command("CONFIG GET", pattern, *args, **kwargs)
876 def config_set(
877 self,
878 name: KeyT,
879 value: EncodableT,
880 *args: Union[KeyT, EncodableT],
881 **kwargs,
882 ) -> ResponseT:
883 """Set config item ``name`` with ``value``
885 For more information, see https://redis.io/commands/config-set
886 """
887 return self.execute_command("CONFIG SET", name, value, *args, **kwargs)
889 def config_resetstat(self, **kwargs) -> ResponseT:
890 """
891 Reset runtime statistics
893 For more information, see https://redis.io/commands/config-resetstat
894 """
895 return self.execute_command("CONFIG RESETSTAT", **kwargs)
897 def config_rewrite(self, **kwargs) -> ResponseT:
898 """
899 Rewrite config file with the minimal change to reflect running config.
901 For more information, see https://redis.io/commands/config-rewrite
902 """
903 return self.execute_command("CONFIG REWRITE", **kwargs)
905 def dbsize(self, **kwargs) -> ResponseT:
906 """
907 Returns the number of keys in the current database
909 For more information, see https://redis.io/commands/dbsize
910 """
911 return self.execute_command("DBSIZE", **kwargs)
913 def debug_object(self, key: KeyT, **kwargs) -> ResponseT:
914 """
915 Returns version specific meta information about a given key
917 For more information, see https://redis.io/commands/debug-object
918 """
919 return self.execute_command("DEBUG OBJECT", key, **kwargs)
921 def debug_segfault(self, **kwargs) -> None:
922 raise NotImplementedError(
923 """
924 DEBUG SEGFAULT is intentionally not implemented in the client.
926 For more information, see https://redis.io/commands/debug-segfault
927 """
928 )
930 def echo(self, value: EncodableT, **kwargs) -> ResponseT:
931 """
932 Echo the string back from the server
934 For more information, see https://redis.io/commands/echo
935 """
936 return self.execute_command("ECHO", value, **kwargs)
938 def flushall(self, asynchronous: bool = False, **kwargs) -> ResponseT:
939 """
940 Delete all keys in all databases on the current host.
942 ``asynchronous`` indicates whether the operation is
943 executed asynchronously by the server.
945 For more information, see https://redis.io/commands/flushall
946 """
947 args = []
948 if asynchronous:
949 args.append(b"ASYNC")
950 return self.execute_command("FLUSHALL", *args, **kwargs)
952 def flushdb(self, asynchronous: bool = False, **kwargs) -> ResponseT:
953 """
954 Delete all keys in the current database.
956 ``asynchronous`` indicates whether the operation is
957 executed asynchronously by the server.
959 For more information, see https://redis.io/commands/flushdb
960 """
961 args = []
962 if asynchronous:
963 args.append(b"ASYNC")
964 return self.execute_command("FLUSHDB", *args, **kwargs)
966 def sync(self) -> ResponseT:
967 """
968 Initiates a replication stream from the master.
970 For more information, see https://redis.io/commands/sync
971 """
972 from redis.client import NEVER_DECODE
974 options = {}
975 options[NEVER_DECODE] = []
976 return self.execute_command("SYNC", **options)
978 def psync(self, replicationid: str, offset: int):
979 """
980 Initiates a replication stream from the master.
981 Newer version for `sync`.
983 For more information, see https://redis.io/commands/sync
984 """
985 from redis.client import NEVER_DECODE
987 options = {}
988 options[NEVER_DECODE] = []
989 return self.execute_command("PSYNC", replicationid, offset, **options)
991 def swapdb(self, first: int, second: int, **kwargs) -> ResponseT:
992 """
993 Swap two databases
995 For more information, see https://redis.io/commands/swapdb
996 """
997 return self.execute_command("SWAPDB", first, second, **kwargs)
999 def select(self, index: int, **kwargs) -> ResponseT:
1000 """Select the Redis logical database at index.
1002 See: https://redis.io/commands/select
1003 """
1004 return self.execute_command("SELECT", index, **kwargs)
1006 def info(self, section: Optional[str] = None, *args: str, **kwargs) -> ResponseT:
1007 """
1008 Returns a dictionary containing information about the Redis server
1010 The ``section`` option can be used to select a specific section
1011 of information
1013 The section option is not supported by older versions of Redis Server,
1014 and will generate ResponseError
1016 For more information, see https://redis.io/commands/info
1017 """
1018 if section is None:
1019 return self.execute_command("INFO", **kwargs)
1020 else:
1021 return self.execute_command("INFO", section, *args, **kwargs)
1023 def lastsave(self, **kwargs) -> ResponseT:
1024 """
1025 Return a Python datetime object representing the last time the
1026 Redis database was saved to disk
1028 For more information, see https://redis.io/commands/lastsave
1029 """
1030 return self.execute_command("LASTSAVE", **kwargs)
1032 def latency_doctor(self):
1033 """Raise a NotImplementedError, as the client will not support LATENCY DOCTOR.
1034 This function is best used within the redis-cli.
1036 For more information, see https://redis.io/commands/latency-doctor
1037 """
1038 raise NotImplementedError(
1039 """
1040 LATENCY DOCTOR is intentionally not implemented in the client.
1042 For more information, see https://redis.io/commands/latency-doctor
1043 """
1044 )
1046 def latency_graph(self):
1047 """Raise a NotImplementedError, as the client will not support LATENCY GRAPH.
1048 This function is best used within the redis-cli.
1050 For more information, see https://redis.io/commands/latency-graph.
1051 """
1052 raise NotImplementedError(
1053 """
1054 LATENCY GRAPH is intentionally not implemented in the client.
1056 For more information, see https://redis.io/commands/latency-graph
1057 """
1058 )
1060 def lolwut(self, *version_numbers: Union[str, float], **kwargs) -> ResponseT:
1061 """
1062 Get the Redis version and a piece of generative computer art
1064 See: https://redis.io/commands/lolwut
1065 """
1066 if version_numbers:
1067 return self.execute_command("LOLWUT VERSION", *version_numbers, **kwargs)
1068 else:
1069 return self.execute_command("LOLWUT", **kwargs)
1071 def reset(self) -> ResponseT:
1072 """Perform a full reset on the connection's server-side context.
1074 See: https://redis.io/commands/reset
1075 """
1076 return self.execute_command("RESET")
1078 def migrate(
1079 self,
1080 host: str,
1081 port: int,
1082 keys: KeysT,
1083 destination_db: int,
1084 timeout: int,
1085 copy: bool = False,
1086 replace: bool = False,
1087 auth: Optional[str] = None,
1088 **kwargs,
1089 ) -> ResponseT:
1090 """
1091 Migrate 1 or more keys from the current Redis server to a different
1092 server specified by the ``host``, ``port`` and ``destination_db``.
1094 The ``timeout``, specified in milliseconds, indicates the maximum
1095 time the connection between the two servers can be idle before the
1096 command is interrupted.
1098 If ``copy`` is True, the specified ``keys`` are NOT deleted from
1099 the source server.
1101 If ``replace`` is True, this operation will overwrite the keys
1102 on the destination server if they exist.
1104 If ``auth`` is specified, authenticate to the destination server with
1105 the password provided.
1107 For more information, see https://redis.io/commands/migrate
1108 """
1109 keys = list_or_args(keys, [])
1110 if not keys:
1111 raise DataError("MIGRATE requires at least one key")
1112 pieces = []
1113 if copy:
1114 pieces.append(b"COPY")
1115 if replace:
1116 pieces.append(b"REPLACE")
1117 if auth:
1118 pieces.append(b"AUTH")
1119 pieces.append(auth)
1120 pieces.append(b"KEYS")
1121 pieces.extend(keys)
1122 return self.execute_command(
1123 "MIGRATE", host, port, "", destination_db, timeout, *pieces, **kwargs
1124 )
1126 def object(self, infotype: str, key: KeyT, **kwargs) -> ResponseT:
1127 """
1128 Return the encoding, idletime, or refcount about the key
1129 """
1130 return self.execute_command(
1131 "OBJECT", infotype, key, infotype=infotype, **kwargs
1132 )
1134 def memory_doctor(self, **kwargs) -> None:
1135 raise NotImplementedError(
1136 """
1137 MEMORY DOCTOR is intentionally not implemented in the client.
1139 For more information, see https://redis.io/commands/memory-doctor
1140 """
1141 )
1143 def memory_help(self, **kwargs) -> None:
1144 raise NotImplementedError(
1145 """
1146 MEMORY HELP is intentionally not implemented in the client.
1148 For more information, see https://redis.io/commands/memory-help
1149 """
1150 )
1152 def memory_stats(self, **kwargs) -> ResponseT:
1153 """
1154 Return a dictionary of memory stats
1156 For more information, see https://redis.io/commands/memory-stats
1157 """
1158 return self.execute_command("MEMORY STATS", **kwargs)
1160 def memory_malloc_stats(self, **kwargs) -> ResponseT:
1161 """
1162 Return an internal statistics report from the memory allocator.
1164 See: https://redis.io/commands/memory-malloc-stats
1165 """
1166 return self.execute_command("MEMORY MALLOC-STATS", **kwargs)
1168 def memory_usage(
1169 self, key: KeyT, samples: Optional[int] = None, **kwargs
1170 ) -> ResponseT:
1171 """
1172 Return the total memory usage for key, its value and associated
1173 administrative overheads.
1175 For nested data structures, ``samples`` is the number of elements to
1176 sample. If left unspecified, the server's default is 5. Use 0 to sample
1177 all elements.
1179 For more information, see https://redis.io/commands/memory-usage
1180 """
1181 args = []
1182 if isinstance(samples, int):
1183 args.extend([b"SAMPLES", samples])
1184 return self.execute_command("MEMORY USAGE", key, *args, **kwargs)
1186 def memory_purge(self, **kwargs) -> ResponseT:
1187 """
1188 Attempts to purge dirty pages for reclamation by allocator
1190 For more information, see https://redis.io/commands/memory-purge
1191 """
1192 return self.execute_command("MEMORY PURGE", **kwargs)
1194 def latency_histogram(self, *args):
1195 """
1196 This function throws a NotImplementedError since it is intentionally
1197 not supported.
1198 """
1199 raise NotImplementedError(
1200 "LATENCY HISTOGRAM is intentionally not implemented in the client."
1201 )
1203 def latency_history(self, event: str) -> ResponseT:
1204 """
1205 Returns the raw data of the ``event``'s latency spikes time series.
1207 For more information, see https://redis.io/commands/latency-history
1208 """
1209 return self.execute_command("LATENCY HISTORY", event)
1211 def latency_latest(self) -> ResponseT:
1212 """
1213 Reports the latest latency events logged.
1215 For more information, see https://redis.io/commands/latency-latest
1216 """
1217 return self.execute_command("LATENCY LATEST")
1219 def latency_reset(self, *events: str) -> ResponseT:
1220 """
1221 Resets the latency spikes time series of all, or only some, events.
1223 For more information, see https://redis.io/commands/latency-reset
1224 """
1225 return self.execute_command("LATENCY RESET", *events)
1227 def ping(self, **kwargs) -> Union[Awaitable[bool], bool]:
1228 """
1229 Ping the Redis server to test connectivity.
1231 Sends a PING command to the Redis server and returns True if the server
1232 responds with "PONG".
1234 This command is useful for:
1235 - Testing whether a connection is still alive
1236 - Verifying the server's ability to serve data
1238 For more information on the underlying ping command see https://redis.io/commands/ping
1239 """
1240 return self.execute_command("PING", **kwargs)
1242 def quit(self, **kwargs) -> ResponseT:
1243 """
1244 Ask the server to close the connection.
1246 For more information, see https://redis.io/commands/quit
1247 """
1248 return self.execute_command("QUIT", **kwargs)
1250 def replicaof(self, *args, **kwargs) -> ResponseT:
1251 """
1252 Update the replication settings of a redis replica, on the fly.
1254 Examples of valid arguments include:
1256 NO ONE (set no replication)
1257 host port (set to the host and port of a redis server)
1259 For more information, see https://redis.io/commands/replicaof
1260 """
1261 return self.execute_command("REPLICAOF", *args, **kwargs)
1263 def save(self, **kwargs) -> ResponseT:
1264 """
1265 Tell the Redis server to save its data to disk,
1266 blocking until the save is complete
1268 For more information, see https://redis.io/commands/save
1269 """
1270 return self.execute_command("SAVE", **kwargs)
1272 def shutdown(
1273 self,
1274 save: bool = False,
1275 nosave: bool = False,
1276 now: bool = False,
1277 force: bool = False,
1278 abort: bool = False,
1279 **kwargs,
1280 ) -> None:
1281 """Shutdown the Redis server. If Redis has persistence configured,
1282 data will be flushed before shutdown.
1283 It is possible to specify modifiers to alter the behavior of the command:
1284 ``save`` will force a DB saving operation even if no save points are configured.
1285 ``nosave`` will prevent a DB saving operation even if one or more save points
1286 are configured.
1287 ``now`` skips waiting for lagging replicas, i.e. it bypasses the first step in
1288 the shutdown sequence.
1289 ``force`` ignores any errors that would normally prevent the server from exiting
1290 ``abort`` cancels an ongoing shutdown and cannot be combined with other flags.
1292 For more information, see https://redis.io/commands/shutdown
1293 """
1294 if save and nosave:
1295 raise DataError("SHUTDOWN save and nosave cannot both be set")
1296 args = ["SHUTDOWN"]
1297 if save:
1298 args.append("SAVE")
1299 if nosave:
1300 args.append("NOSAVE")
1301 if now:
1302 args.append("NOW")
1303 if force:
1304 args.append("FORCE")
1305 if abort:
1306 args.append("ABORT")
1307 try:
1308 self.execute_command(*args, **kwargs)
1309 except ConnectionError:
1310 # a ConnectionError here is expected
1311 return
1312 raise RedisError("SHUTDOWN seems to have failed.")
1314 def slaveof(
1315 self, host: Optional[str] = None, port: Optional[int] = None, **kwargs
1316 ) -> ResponseT:
1317 """
1318 Set the server to be a replicated slave of the instance identified
1319 by the ``host`` and ``port``. If called without arguments, the
1320 instance is promoted to a master instead.
1322 For more information, see https://redis.io/commands/slaveof
1323 """
1324 if host is None and port is None:
1325 return self.execute_command("SLAVEOF", b"NO", b"ONE", **kwargs)
1326 return self.execute_command("SLAVEOF", host, port, **kwargs)
1328 def slowlog_get(self, num: Optional[int] = None, **kwargs) -> ResponseT:
1329 """
1330 Get the entries from the slowlog. If ``num`` is specified, get the
1331 most recent ``num`` items.
1333 For more information, see https://redis.io/commands/slowlog-get
1334 """
1335 from redis.client import NEVER_DECODE
1337 args = ["SLOWLOG GET"]
1338 if num is not None:
1339 args.append(num)
1340 decode_responses = self.get_connection_kwargs().get("decode_responses", False)
1341 if decode_responses is True:
1342 kwargs[NEVER_DECODE] = []
1343 return self.execute_command(*args, **kwargs)
1345 def slowlog_len(self, **kwargs) -> ResponseT:
1346 """
1347 Get the number of items in the slowlog
1349 For more information, see https://redis.io/commands/slowlog-len
1350 """
1351 return self.execute_command("SLOWLOG LEN", **kwargs)
1353 def slowlog_reset(self, **kwargs) -> ResponseT:
1354 """
1355 Remove all items in the slowlog
1357 For more information, see https://redis.io/commands/slowlog-reset
1358 """
1359 return self.execute_command("SLOWLOG RESET", **kwargs)
1361 def time(self, **kwargs) -> ResponseT:
1362 """
1363 Returns the server time as a 2-item tuple of ints:
1364 (seconds since epoch, microseconds into this second).
1366 For more information, see https://redis.io/commands/time
1367 """
1368 return self.execute_command("TIME", **kwargs)
1370 def wait(self, num_replicas: int, timeout: int, **kwargs) -> ResponseT:
1371 """
1372 Redis synchronous replication
1373 That returns the number of replicas that processed the query when
1374 we finally have at least ``num_replicas``, or when the ``timeout`` was
1375 reached.
1377 For more information, see https://redis.io/commands/wait
1378 """
1379 return self.execute_command("WAIT", num_replicas, timeout, **kwargs)
1381 def waitaof(
1382 self, num_local: int, num_replicas: int, timeout: int, **kwargs
1383 ) -> ResponseT:
1384 """
1385 This command blocks the current client until all previous write
1386 commands by that client are acknowledged as having been fsynced
1387 to the AOF of the local Redis and/or at least the specified number
1388 of replicas.
1390 For more information, see https://redis.io/commands/waitaof
1391 """
1392 return self.execute_command(
1393 "WAITAOF", num_local, num_replicas, timeout, **kwargs
1394 )
1396 def hello(self):
1397 """
1398 This function throws a NotImplementedError since it is intentionally
1399 not supported.
1400 """
1401 raise NotImplementedError(
1402 "HELLO is intentionally not implemented in the client."
1403 )
1405 def failover(self):
1406 """
1407 This function throws a NotImplementedError since it is intentionally
1408 not supported.
1409 """
1410 raise NotImplementedError(
1411 "FAILOVER is intentionally not implemented in the client."
1412 )
1414 def hotkeys_start(
1415 self,
1416 metrics: List[HotkeysMetricsTypes],
1417 count: Optional[int] = None,
1418 duration: Optional[int] = None,
1419 sample_ratio: Optional[int] = None,
1420 slots: Optional[List[int]] = None,
1421 **kwargs,
1422 ) -> Union[Awaitable[Union[str, bytes]], Union[str, bytes]]:
1423 """
1424 Start collecting hotkeys data.
1425 Returns an error if there is an ongoing collection session.
1427 Args:
1428 count: The number of keys to collect in each criteria (CPU and network consumption)
1429 metrics: List of metrics to track. Supported values: [HotkeysMetricsTypes.CPU, HotkeysMetricsTypes.NET]
1430 duration: Automatically stop the collection after `duration` seconds
1431 sample_ratio: Commands are sampled with probability 1/ratio (1 means no sampling)
1432 slots: Only track keys on the specified hash slots
1434 For more information, see https://redis.io/commands/hotkeys-start
1435 """
1436 args: List[Union[str, int]] = ["HOTKEYS", "START"]
1438 # Add METRICS
1439 args.extend(["METRICS", len(metrics)])
1440 args.extend([str(m.value) for m in metrics])
1442 # Add COUNT
1443 if count is not None:
1444 args.extend(["COUNT", count])
1446 # Add optional DURATION
1447 if duration is not None:
1448 args.extend(["DURATION", duration])
1450 # Add optional SAMPLE ratio
1451 if sample_ratio is not None:
1452 args.extend(["SAMPLE", sample_ratio])
1454 # Add optional SLOTS
1455 if slots is not None:
1456 args.append("SLOTS")
1457 args.append(len(slots))
1458 args.extend(slots)
1460 return self.execute_command(*args, **kwargs)
1462 def hotkeys_stop(
1463 self, **kwargs
1464 ) -> Union[Awaitable[Union[str, bytes]], Union[str, bytes]]:
1465 """
1466 Stop the ongoing hotkeys collection session (if any).
1467 The results of the last collection session are kept for consumption with HOTKEYS GET.
1469 For more information, see https://redis.io/commands/hotkeys-stop
1470 """
1471 return self.execute_command("HOTKEYS STOP", **kwargs)
1473 def hotkeys_reset(
1474 self, **kwargs
1475 ) -> Union[Awaitable[Union[str, bytes]], Union[str, bytes]]:
1476 """
1477 Discard the last hotkeys collection session results (in order to save memory).
1478 Error if there is an ongoing collection session.
1480 For more information, see https://redis.io/commands/hotkeys-reset
1481 """
1482 return self.execute_command("HOTKEYS RESET", **kwargs)
1484 def hotkeys_get(
1485 self, **kwargs
1486 ) -> Union[
1487 Awaitable[list[dict[Union[str, bytes], Any]]],
1488 list[dict[Union[str, bytes], Any]],
1489 ]:
1490 """
1491 Retrieve the result of the ongoing collection session (if any),
1492 or the last collection session (if any).
1494 HOTKEYS GET response is wrapped in an array for aggregation support.
1495 Each node returns a single-element array, allowing multiple node
1496 responses to be concatenated by DMC or other aggregators.
1498 For more information, see https://redis.io/commands/hotkeys-get
1499 """
1500 return self.execute_command("HOTKEYS GET", **kwargs)
1503class AsyncManagementCommands(ManagementCommands):
1504 async def command_info(self, **kwargs) -> None:
1505 return super().command_info(**kwargs)
1507 async def debug_segfault(self, **kwargs) -> None:
1508 return super().debug_segfault(**kwargs)
1510 async def memory_doctor(self, **kwargs) -> None:
1511 return super().memory_doctor(**kwargs)
1513 async def memory_help(self, **kwargs) -> None:
1514 return super().memory_help(**kwargs)
1516 async def shutdown(
1517 self,
1518 save: bool = False,
1519 nosave: bool = False,
1520 now: bool = False,
1521 force: bool = False,
1522 abort: bool = False,
1523 **kwargs,
1524 ) -> None:
1525 """Shutdown the Redis server. If Redis has persistence configured,
1526 data will be flushed before shutdown. If the "save" option is set,
1527 a data flush will be attempted even if there is no persistence
1528 configured. If the "nosave" option is set, no data flush will be
1529 attempted. The "save" and "nosave" options cannot both be set.
1531 For more information, see https://redis.io/commands/shutdown
1532 """
1533 if save and nosave:
1534 raise DataError("SHUTDOWN save and nosave cannot both be set")
1535 args = ["SHUTDOWN"]
1536 if save:
1537 args.append("SAVE")
1538 if nosave:
1539 args.append("NOSAVE")
1540 if now:
1541 args.append("NOW")
1542 if force:
1543 args.append("FORCE")
1544 if abort:
1545 args.append("ABORT")
1546 try:
1547 await self.execute_command(*args, **kwargs)
1548 except ConnectionError:
1549 # a ConnectionError here is expected
1550 return
1551 raise RedisError("SHUTDOWN seems to have failed.")
1554class BitFieldOperation:
1555 """
1556 Command builder for BITFIELD commands.
1557 """
1559 def __init__(
1560 self,
1561 client: Union["redis.client.Redis", "redis.asyncio.client.Redis"],
1562 key: str,
1563 default_overflow: Optional[str] = None,
1564 ):
1565 self.client = client
1566 self.key = key
1567 self._default_overflow = default_overflow
1568 # for typing purposes, run the following in constructor and in reset()
1569 self.operations: list[tuple[EncodableT, ...]] = []
1570 self._last_overflow = "WRAP"
1571 self.reset()
1573 def reset(self):
1574 """
1575 Reset the state of the instance to when it was constructed
1576 """
1577 self.operations = []
1578 self._last_overflow = "WRAP"
1579 self.overflow(self._default_overflow or self._last_overflow)
1581 def overflow(self, overflow: str):
1582 """
1583 Update the overflow algorithm of successive INCRBY operations
1584 :param overflow: Overflow algorithm, one of WRAP, SAT, FAIL. See the
1585 Redis docs for descriptions of these algorithmsself.
1586 :returns: a :py:class:`BitFieldOperation` instance.
1587 """
1588 overflow = overflow.upper()
1589 if overflow != self._last_overflow:
1590 self._last_overflow = overflow
1591 self.operations.append(("OVERFLOW", overflow))
1592 return self
1594 def incrby(
1595 self,
1596 fmt: str,
1597 offset: BitfieldOffsetT,
1598 increment: int,
1599 overflow: Optional[str] = None,
1600 ):
1601 """
1602 Increment a bitfield by a given amount.
1603 :param fmt: format-string for the bitfield being updated, e.g. 'u8'
1604 for an unsigned 8-bit integer.
1605 :param offset: offset (in number of bits). If prefixed with a
1606 '#', this is an offset multiplier, e.g. given the arguments
1607 fmt='u8', offset='#2', the offset will be 16.
1608 :param int increment: value to increment the bitfield by.
1609 :param str overflow: overflow algorithm. Defaults to WRAP, but other
1610 acceptable values are SAT and FAIL. See the Redis docs for
1611 descriptions of these algorithms.
1612 :returns: a :py:class:`BitFieldOperation` instance.
1613 """
1614 if overflow is not None:
1615 self.overflow(overflow)
1617 self.operations.append(("INCRBY", fmt, offset, increment))
1618 return self
1620 def get(self, fmt: str, offset: BitfieldOffsetT):
1621 """
1622 Get the value of a given bitfield.
1623 :param fmt: format-string for the bitfield being read, e.g. 'u8' for
1624 an unsigned 8-bit integer.
1625 :param offset: offset (in number of bits). If prefixed with a
1626 '#', this is an offset multiplier, e.g. given the arguments
1627 fmt='u8', offset='#2', the offset will be 16.
1628 :returns: a :py:class:`BitFieldOperation` instance.
1629 """
1630 self.operations.append(("GET", fmt, offset))
1631 return self
1633 def set(self, fmt: str, offset: BitfieldOffsetT, value: int):
1634 """
1635 Set the value of a given bitfield.
1636 :param fmt: format-string for the bitfield being read, e.g. 'u8' for
1637 an unsigned 8-bit integer.
1638 :param offset: offset (in number of bits). If prefixed with a
1639 '#', this is an offset multiplier, e.g. given the arguments
1640 fmt='u8', offset='#2', the offset will be 16.
1641 :param int value: value to set at the given position.
1642 :returns: a :py:class:`BitFieldOperation` instance.
1643 """
1644 self.operations.append(("SET", fmt, offset, value))
1645 return self
1647 @property
1648 def command(self):
1649 cmd = ["BITFIELD", self.key]
1650 for ops in self.operations:
1651 cmd.extend(ops)
1652 return cmd
1654 def execute(self) -> ResponseT:
1655 """
1656 Execute the operation(s) in a single BITFIELD command. The return value
1657 is a list of values corresponding to each operation. If the client
1658 used to create this instance was a pipeline, the list of values
1659 will be present within the pipeline's execute.
1660 """
1661 command = self.command
1662 self.reset()
1663 return self.client.execute_command(*command)
1666class DataPersistOptions(Enum):
1667 # set the value for each provided key to each
1668 # provided value only if all do not already exist.
1669 NX = "NX"
1671 # set the value for each provided key to each
1672 # provided value only if all already exist.
1673 XX = "XX"
1676class BasicKeyCommands(CommandsProtocol):
1677 """
1678 Redis basic key-based commands
1679 """
1681 def append(self, key: KeyT, value: EncodableT) -> ResponseT:
1682 """
1683 Appends the string ``value`` to the value at ``key``. If ``key``
1684 doesn't already exist, create it with a value of ``value``.
1685 Returns the new length of the value at ``key``.
1687 For more information, see https://redis.io/commands/append
1688 """
1689 return self.execute_command("APPEND", key, value)
1691 def bitcount(
1692 self,
1693 key: KeyT,
1694 start: Optional[int] = None,
1695 end: Optional[int] = None,
1696 mode: Optional[str] = None,
1697 ) -> ResponseT:
1698 """
1699 Returns the count of set bits in the value of ``key``. Optional
1700 ``start`` and ``end`` parameters indicate which bytes to consider
1702 For more information, see https://redis.io/commands/bitcount
1703 """
1704 params = [key]
1705 if start is not None and end is not None:
1706 params.append(start)
1707 params.append(end)
1708 elif (start is not None and end is None) or (end is not None and start is None):
1709 raise DataError("Both start and end must be specified")
1710 if mode is not None:
1711 params.append(mode)
1712 return self.execute_command("BITCOUNT", *params, keys=[key])
1714 def bitfield(
1715 self: Union["redis.client.Redis", "redis.asyncio.client.Redis"],
1716 key: KeyT,
1717 default_overflow: Optional[str] = None,
1718 ) -> BitFieldOperation:
1719 """
1720 Return a BitFieldOperation instance to conveniently construct one or
1721 more bitfield operations on ``key``.
1723 For more information, see https://redis.io/commands/bitfield
1724 """
1725 return BitFieldOperation(self, key, default_overflow=default_overflow)
1727 def bitfield_ro(
1728 self: Union["redis.client.Redis", "redis.asyncio.client.Redis"],
1729 key: KeyT,
1730 encoding: str,
1731 offset: BitfieldOffsetT,
1732 items: Optional[list] = None,
1733 ) -> ResponseT:
1734 """
1735 Return an array of the specified bitfield values
1736 where the first value is found using ``encoding`` and ``offset``
1737 parameters and remaining values are result of corresponding
1738 encoding/offset pairs in optional list ``items``
1739 Read-only variant of the BITFIELD command.
1741 For more information, see https://redis.io/commands/bitfield_ro
1742 """
1743 params = [key, "GET", encoding, offset]
1745 items = items or []
1746 for encoding, offset in items:
1747 params.extend(["GET", encoding, offset])
1748 return self.execute_command("BITFIELD_RO", *params, keys=[key])
1750 def bitop(self, operation: str, dest: KeyT, *keys: KeyT) -> ResponseT:
1751 """
1752 Perform a bitwise operation using ``operation`` between ``keys`` and
1753 store the result in ``dest``.
1755 For more information, see https://redis.io/commands/bitop
1756 """
1757 return self.execute_command("BITOP", operation, dest, *keys)
1759 def bitpos(
1760 self,
1761 key: KeyT,
1762 bit: int,
1763 start: Optional[int] = None,
1764 end: Optional[int] = None,
1765 mode: Optional[str] = None,
1766 ) -> ResponseT:
1767 """
1768 Return the position of the first bit set to 1 or 0 in a string.
1769 ``start`` and ``end`` defines search range. The range is interpreted
1770 as a range of bytes and not a range of bits, so start=0 and end=2
1771 means to look at the first three bytes.
1773 For more information, see https://redis.io/commands/bitpos
1774 """
1775 if bit not in (0, 1):
1776 raise DataError("bit must be 0 or 1")
1777 params = [key, bit]
1779 start is not None and params.append(start)
1781 if start is not None and end is not None:
1782 params.append(end)
1783 elif start is None and end is not None:
1784 raise DataError("start argument is not set, when end is specified")
1786 if mode is not None:
1787 params.append(mode)
1788 return self.execute_command("BITPOS", *params, keys=[key])
1790 def copy(
1791 self,
1792 source: str,
1793 destination: str,
1794 destination_db: Optional[str] = None,
1795 replace: bool = False,
1796 ) -> ResponseT:
1797 """
1798 Copy the value stored in the ``source`` key to the ``destination`` key.
1800 ``destination_db`` an alternative destination database. By default,
1801 the ``destination`` key is created in the source Redis database.
1803 ``replace`` whether the ``destination`` key should be removed before
1804 copying the value to it. By default, the value is not copied if
1805 the ``destination`` key already exists.
1807 For more information, see https://redis.io/commands/copy
1808 """
1809 params = [source, destination]
1810 if destination_db is not None:
1811 params.extend(["DB", destination_db])
1812 if replace:
1813 params.append("REPLACE")
1814 return self.execute_command("COPY", *params)
1816 def decrby(self, name: KeyT, amount: int = 1) -> ResponseT:
1817 """
1818 Decrements the value of ``key`` by ``amount``. If no key exists,
1819 the value will be initialized as 0 - ``amount``
1821 For more information, see https://redis.io/commands/decrby
1822 """
1823 return self.execute_command("DECRBY", name, amount)
1825 decr = decrby
1827 def delete(self, *names: KeyT) -> ResponseT:
1828 """
1829 Delete one or more keys specified by ``names``
1830 """
1831 return self.execute_command("DEL", *names)
1833 def __delitem__(self, name: KeyT):
1834 self.delete(name)
1836 @experimental_method()
1837 def delex(
1838 self,
1839 name: KeyT,
1840 ifeq: Optional[Union[bytes, str]] = None,
1841 ifne: Optional[Union[bytes, str]] = None,
1842 ifdeq: Optional[str] = None, # hex digest
1843 ifdne: Optional[str] = None, # hex digest
1844 ) -> int:
1845 """
1846 Conditionally removes the specified key.
1848 Warning:
1849 **Experimental** since 7.1.
1850 This API may change or be removed without notice.
1851 The API may change based on feedback.
1853 Arguments:
1854 name: KeyT - the key to delete
1855 ifeq match-valu: Optional[Union[bytes, str]] - Delete the key only if its value is equal to match-value
1856 ifne match-value: Optional[Union[bytes, str]] - Delete the key only if its value is not equal to match-value
1857 ifdeq match-digest: Optional[str] - Delete the key only if the digest of its value is equal to match-digest
1858 ifdne match-digest: Optional[str] - Delete the key only if the digest of its value is not equal to match-digest
1860 Returns:
1861 int: 1 if the key was deleted, 0 otherwise.
1862 Raises:
1863 redis.exceptions.ResponseError: if key exists but is not a string
1864 and a condition is specified.
1865 ValueError: if more than one condition is provided.
1868 Requires Redis 8.4 or greater.
1869 For more information, see https://redis.io/commands/delex
1870 """
1871 conds = [x is not None for x in (ifeq, ifne, ifdeq, ifdne)]
1872 if sum(conds) > 1:
1873 raise ValueError("Only one of IFEQ/IFNE/IFDEQ/IFDNE may be specified")
1875 pieces = ["DELEX", name]
1876 if ifeq is not None:
1877 pieces += ["IFEQ", ifeq]
1878 elif ifne is not None:
1879 pieces += ["IFNE", ifne]
1880 elif ifdeq is not None:
1881 pieces += ["IFDEQ", ifdeq]
1882 elif ifdne is not None:
1883 pieces += ["IFDNE", ifdne]
1885 return self.execute_command(*pieces)
1887 def dump(self, name: KeyT) -> ResponseT:
1888 """
1889 Return a serialized version of the value stored at the specified key.
1890 If key does not exist a nil bulk reply is returned.
1892 For more information, see https://redis.io/commands/dump
1893 """
1894 from redis.client import NEVER_DECODE
1896 options = {}
1897 options[NEVER_DECODE] = []
1898 return self.execute_command("DUMP", name, **options)
1900 def exists(self, *names: KeyT) -> ResponseT:
1901 """
1902 Returns the number of ``names`` that exist
1904 For more information, see https://redis.io/commands/exists
1905 """
1906 return self.execute_command("EXISTS", *names, keys=names)
1908 __contains__ = exists
1910 def expire(
1911 self,
1912 name: KeyT,
1913 time: ExpiryT,
1914 nx: bool = False,
1915 xx: bool = False,
1916 gt: bool = False,
1917 lt: bool = False,
1918 ) -> ResponseT:
1919 """
1920 Set an expire flag on key ``name`` for ``time`` seconds with given
1921 ``option``. ``time`` can be represented by an integer or a Python timedelta
1922 object.
1924 Valid options are:
1925 NX -> Set expiry only when the key has no expiry
1926 XX -> Set expiry only when the key has an existing expiry
1927 GT -> Set expiry only when the new expiry is greater than current one
1928 LT -> Set expiry only when the new expiry is less than current one
1930 For more information, see https://redis.io/commands/expire
1931 """
1932 if isinstance(time, datetime.timedelta):
1933 time = int(time.total_seconds())
1935 exp_option = list()
1936 if nx:
1937 exp_option.append("NX")
1938 if xx:
1939 exp_option.append("XX")
1940 if gt:
1941 exp_option.append("GT")
1942 if lt:
1943 exp_option.append("LT")
1945 return self.execute_command("EXPIRE", name, time, *exp_option)
1947 def expireat(
1948 self,
1949 name: KeyT,
1950 when: AbsExpiryT,
1951 nx: bool = False,
1952 xx: bool = False,
1953 gt: bool = False,
1954 lt: bool = False,
1955 ) -> ResponseT:
1956 """
1957 Set an expire flag on key ``name`` with given ``option``. ``when``
1958 can be represented as an integer indicating unix time or a Python
1959 datetime object.
1961 Valid options are:
1962 -> NX -- Set expiry only when the key has no expiry
1963 -> XX -- Set expiry only when the key has an existing expiry
1964 -> GT -- Set expiry only when the new expiry is greater than current one
1965 -> LT -- Set expiry only when the new expiry is less than current one
1967 For more information, see https://redis.io/commands/expireat
1968 """
1969 if isinstance(when, datetime.datetime):
1970 when = int(when.timestamp())
1972 exp_option = list()
1973 if nx:
1974 exp_option.append("NX")
1975 if xx:
1976 exp_option.append("XX")
1977 if gt:
1978 exp_option.append("GT")
1979 if lt:
1980 exp_option.append("LT")
1982 return self.execute_command("EXPIREAT", name, when, *exp_option)
1984 def expiretime(self, key: str) -> int:
1985 """
1986 Returns the absolute Unix timestamp (since January 1, 1970) in seconds
1987 at which the given key will expire.
1989 For more information, see https://redis.io/commands/expiretime
1990 """
1991 return self.execute_command("EXPIRETIME", key)
1993 @experimental_method()
1994 def digest_local(self, value: Union[bytes, str]) -> Union[bytes, str]:
1995 """
1996 Compute the hexadecimal digest of the value locally, without sending it to the server.
1998 This is useful for conditional operations like IFDEQ/IFDNE where you need to
1999 compute the digest client-side before sending a command.
2001 Warning:
2002 **Experimental** - This API may change or be removed without notice.
2004 Arguments:
2005 - value: Union[bytes, str] - the value to compute the digest of.
2006 If a string is provided, it will be encoded using UTF-8 before hashing,
2007 which matches Redis's default encoding behavior.
2009 Returns:
2010 - (str | bytes) the XXH3 digest of the value as a hex string (16 hex characters).
2011 Returns bytes if decode_responses is False, otherwise returns str.
2013 For more information, see https://redis.io/commands/digest
2014 """
2015 if not HAS_XXHASH:
2016 raise NotImplementedError(
2017 "XXHASH support requires the optional 'xxhash' library. "
2018 "Install it with 'pip install xxhash' or use this package's extra with "
2019 "'pip install redis[xxhash]' to enable this feature."
2020 )
2022 local_digest = xxhash.xxh3_64(value).hexdigest()
2024 # To align with digest, we want to return bytes if decode_responses is False.
2025 # The following works because of Python's mixin-based client class hierarchy.
2026 if not self.get_encoder().decode_responses:
2027 local_digest = local_digest.encode()
2029 return local_digest
2031 @experimental_method()
2032 def digest(self, name: KeyT) -> Union[str, bytes, None]:
2033 """
2034 Return the digest of the value stored at the specified key.
2036 Warning:
2037 **Experimental** since 7.1.
2038 This API may change or be removed without notice.
2039 The API may change based on feedback.
2041 Arguments:
2042 - name: KeyT - the key to get the digest of
2044 Returns:
2045 - None if the key does not exist
2046 - (bulk string) the XXH3 digest of the value as a hex string
2047 Raises:
2048 - ResponseError if key exists but is not a string
2051 Requires Redis 8.4 or greater.
2052 For more information, see https://redis.io/commands/digest
2053 """
2054 # Bulk string response is already handled (bytes/str based on decode_responses)
2055 return self.execute_command("DIGEST", name)
2057 def get(self, name: KeyT) -> ResponseT:
2058 """
2059 Return the value at key ``name``, or None if the key doesn't exist
2061 For more information, see https://redis.io/commands/get
2062 """
2063 return self.execute_command("GET", name, keys=[name])
2065 def getdel(self, name: KeyT) -> ResponseT:
2066 """
2067 Get the value at key ``name`` and delete the key. This command
2068 is similar to GET, except for the fact that it also deletes
2069 the key on success (if and only if the key's value type
2070 is a string).
2072 For more information, see https://redis.io/commands/getdel
2073 """
2074 return self.execute_command("GETDEL", name)
2076 def getex(
2077 self,
2078 name: KeyT,
2079 ex: Optional[ExpiryT] = None,
2080 px: Optional[ExpiryT] = None,
2081 exat: Optional[AbsExpiryT] = None,
2082 pxat: Optional[AbsExpiryT] = None,
2083 persist: bool = False,
2084 ) -> ResponseT:
2085 """
2086 Get the value of key and optionally set its expiration.
2087 GETEX is similar to GET, but is a write command with
2088 additional options. All time parameters can be given as
2089 datetime.timedelta or integers.
2091 ``ex`` sets an expire flag on key ``name`` for ``ex`` seconds.
2093 ``px`` sets an expire flag on key ``name`` for ``px`` milliseconds.
2095 ``exat`` sets an expire flag on key ``name`` for ``ex`` seconds,
2096 specified in unix time.
2098 ``pxat`` sets an expire flag on key ``name`` for ``ex`` milliseconds,
2099 specified in unix time.
2101 ``persist`` remove the time to live associated with ``name``.
2103 For more information, see https://redis.io/commands/getex
2104 """
2105 if not at_most_one_value_set((ex, px, exat, pxat, persist)):
2106 raise DataError(
2107 "``ex``, ``px``, ``exat``, ``pxat``, "
2108 "and ``persist`` are mutually exclusive."
2109 )
2111 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat)
2113 if persist:
2114 exp_options.append("PERSIST")
2116 return self.execute_command("GETEX", name, *exp_options)
2118 def __getitem__(self, name: KeyT):
2119 """
2120 Return the value at key ``name``, raises a KeyError if the key
2121 doesn't exist.
2122 """
2123 value = self.get(name)
2124 if value is not None:
2125 return value
2126 raise KeyError(name)
2128 def getbit(self, name: KeyT, offset: int) -> ResponseT:
2129 """
2130 Returns an integer indicating the value of ``offset`` in ``name``
2132 For more information, see https://redis.io/commands/getbit
2133 """
2134 return self.execute_command("GETBIT", name, offset, keys=[name])
2136 def getrange(self, key: KeyT, start: int, end: int) -> ResponseT:
2137 """
2138 Returns the substring of the string value stored at ``key``,
2139 determined by the offsets ``start`` and ``end`` (both are inclusive)
2141 For more information, see https://redis.io/commands/getrange
2142 """
2143 return self.execute_command("GETRANGE", key, start, end, keys=[key])
2145 def getset(self, name: KeyT, value: EncodableT) -> ResponseT:
2146 """
2147 Sets the value at key ``name`` to ``value``
2148 and returns the old value at key ``name`` atomically.
2150 As per Redis 6.2, GETSET is considered deprecated.
2151 Please use SET with GET parameter in new code.
2153 For more information, see https://redis.io/commands/getset
2154 """
2155 return self.execute_command("GETSET", name, value)
2157 def incrby(self, name: KeyT, amount: int = 1) -> ResponseT:
2158 """
2159 Increments the value of ``key`` by ``amount``. If no key exists,
2160 the value will be initialized as ``amount``
2162 For more information, see https://redis.io/commands/incrby
2163 """
2164 return self.execute_command("INCRBY", name, amount)
2166 incr = incrby
2168 def incrbyfloat(self, name: KeyT, amount: float = 1.0) -> ResponseT:
2169 """
2170 Increments the value at key ``name`` by floating ``amount``.
2171 If no key exists, the value will be initialized as ``amount``
2173 For more information, see https://redis.io/commands/incrbyfloat
2174 """
2175 return self.execute_command("INCRBYFLOAT", name, amount)
2177 def keys(self, pattern: PatternT = "*", **kwargs) -> ResponseT:
2178 """
2179 Returns a list of keys matching ``pattern``
2181 For more information, see https://redis.io/commands/keys
2182 """
2183 return self.execute_command("KEYS", pattern, **kwargs)
2185 def lmove(
2186 self, first_list: str, second_list: str, src: str = "LEFT", dest: str = "RIGHT"
2187 ) -> ResponseT:
2188 """
2189 Atomically returns and removes the first/last element of a list,
2190 pushing it as the first/last element on the destination list.
2191 Returns the element being popped and pushed.
2193 For more information, see https://redis.io/commands/lmove
2194 """
2195 params = [first_list, second_list, src, dest]
2196 return self.execute_command("LMOVE", *params)
2198 def blmove(
2199 self,
2200 first_list: str,
2201 second_list: str,
2202 timeout: int,
2203 src: str = "LEFT",
2204 dest: str = "RIGHT",
2205 ) -> ResponseT:
2206 """
2207 Blocking version of lmove.
2209 For more information, see https://redis.io/commands/blmove
2210 """
2211 params = [first_list, second_list, src, dest, timeout]
2212 return self.execute_command("BLMOVE", *params)
2214 def mget(self, keys: KeysT, *args: EncodableT) -> ResponseT:
2215 """
2216 Returns a list of values ordered identically to ``keys``
2218 ** Important ** When this method is used with Cluster clients, all keys
2219 must be in the same hash slot, otherwise a RedisClusterException
2220 will be raised.
2222 For more information, see https://redis.io/commands/mget
2223 """
2224 from redis.client import EMPTY_RESPONSE
2226 args = list_or_args(keys, args)
2227 options = {}
2228 if not args:
2229 options[EMPTY_RESPONSE] = []
2230 options["keys"] = args
2231 return self.execute_command("MGET", *args, **options)
2233 def mset(self, mapping: Mapping[AnyKeyT, EncodableT]) -> ResponseT:
2234 """
2235 Sets key/values based on a mapping. Mapping is a dictionary of
2236 key/value pairs. Both keys and values should be strings or types that
2237 can be cast to a string via str().
2239 ** Important ** When this method is used with Cluster clients, all keys
2240 must be in the same hash slot, otherwise a RedisClusterException
2241 will be raised.
2243 For more information, see https://redis.io/commands/mset
2244 """
2245 items = []
2246 for pair in mapping.items():
2247 items.extend(pair)
2248 return self.execute_command("MSET", *items)
2250 def msetex(
2251 self,
2252 mapping: Mapping[AnyKeyT, EncodableT],
2253 data_persist_option: Optional[DataPersistOptions] = None,
2254 ex: Optional[ExpiryT] = None,
2255 px: Optional[ExpiryT] = None,
2256 exat: Optional[AbsExpiryT] = None,
2257 pxat: Optional[AbsExpiryT] = None,
2258 keepttl: bool = False,
2259 ) -> Union[Awaitable[int], int]:
2260 """
2261 Sets key/values based on the provided ``mapping`` items.
2263 ** Important ** When this method is used with Cluster clients, all keys
2264 must be in the same hash slot, otherwise a RedisClusterException
2265 will be raised.
2267 ``mapping`` accepts a dict of key/value pairs that will be added to the database.
2269 ``data_persist_option`` can be set to ``NX`` or ``XX`` to control the
2270 behavior of the command.
2271 ``NX`` will set the value for each provided key to each
2272 provided value only if all do not already exist.
2273 ``XX`` will set the value for each provided key to each
2274 provided value only if all already exist.
2276 ``ex`` sets an expire flag on the keys in ``mapping`` for ``ex`` seconds.
2278 ``px`` sets an expire flag on the keys in ``mapping`` for ``px`` milliseconds.
2280 ``exat`` sets an expire flag on the keys in ``mapping`` for ``exat`` seconds,
2281 specified in unix time.
2283 ``pxat`` sets an expire flag on the keys in ``mapping`` for ``pxat`` milliseconds,
2284 specified in unix time.
2286 ``keepttl`` if True, retain the time to live associated with the keys.
2288 Returns the number of fields that were added.
2290 Available since Redis 8.4
2291 For more information, see https://redis.io/commands/msetex
2292 """
2293 if not at_most_one_value_set((ex, px, exat, pxat, keepttl)):
2294 raise DataError(
2295 "``ex``, ``px``, ``exat``, ``pxat``, "
2296 "and ``keepttl`` are mutually exclusive."
2297 )
2299 exp_options: list[EncodableT] = []
2300 if data_persist_option:
2301 exp_options.append(data_persist_option.value)
2303 exp_options.extend(extract_expire_flags(ex, px, exat, pxat))
2305 if keepttl:
2306 exp_options.append("KEEPTTL")
2308 pieces = ["MSETEX", len(mapping)]
2310 for pair in mapping.items():
2311 pieces.extend(pair)
2313 return self.execute_command(*pieces, *exp_options)
2315 def msetnx(self, mapping: Mapping[AnyKeyT, EncodableT]) -> ResponseT:
2316 """
2317 Sets key/values based on a mapping if none of the keys are already set.
2318 Mapping is a dictionary of key/value pairs. Both keys and values
2319 should be strings or types that can be cast to a string via str().
2320 Returns a boolean indicating if the operation was successful.
2322 ** Important ** When this method is used with Cluster clients, all keys
2323 must be in the same hash slot, otherwise a RedisClusterException
2324 will be raised.
2326 For more information, see https://redis.io/commands/msetnx
2327 """
2328 items = []
2329 for pair in mapping.items():
2330 items.extend(pair)
2331 return self.execute_command("MSETNX", *items)
2333 def move(self, name: KeyT, db: int) -> ResponseT:
2334 """
2335 Moves the key ``name`` to a different Redis database ``db``
2337 For more information, see https://redis.io/commands/move
2338 """
2339 return self.execute_command("MOVE", name, db)
2341 def persist(self, name: KeyT) -> ResponseT:
2342 """
2343 Removes an expiration on ``name``
2345 For more information, see https://redis.io/commands/persist
2346 """
2347 return self.execute_command("PERSIST", name)
2349 def pexpire(
2350 self,
2351 name: KeyT,
2352 time: ExpiryT,
2353 nx: bool = False,
2354 xx: bool = False,
2355 gt: bool = False,
2356 lt: bool = False,
2357 ) -> ResponseT:
2358 """
2359 Set an expire flag on key ``name`` for ``time`` milliseconds
2360 with given ``option``. ``time`` can be represented by an
2361 integer or a Python timedelta object.
2363 Valid options are:
2364 NX -> Set expiry only when the key has no expiry
2365 XX -> Set expiry only when the key has an existing expiry
2366 GT -> Set expiry only when the new expiry is greater than current one
2367 LT -> Set expiry only when the new expiry is less than current one
2369 For more information, see https://redis.io/commands/pexpire
2370 """
2371 if isinstance(time, datetime.timedelta):
2372 time = int(time.total_seconds() * 1000)
2374 exp_option = list()
2375 if nx:
2376 exp_option.append("NX")
2377 if xx:
2378 exp_option.append("XX")
2379 if gt:
2380 exp_option.append("GT")
2381 if lt:
2382 exp_option.append("LT")
2383 return self.execute_command("PEXPIRE", name, time, *exp_option)
2385 def pexpireat(
2386 self,
2387 name: KeyT,
2388 when: AbsExpiryT,
2389 nx: bool = False,
2390 xx: bool = False,
2391 gt: bool = False,
2392 lt: bool = False,
2393 ) -> ResponseT:
2394 """
2395 Set an expire flag on key ``name`` with given ``option``. ``when``
2396 can be represented as an integer representing unix time in
2397 milliseconds (unix time * 1000) or a Python datetime object.
2399 Valid options are:
2400 NX -> Set expiry only when the key has no expiry
2401 XX -> Set expiry only when the key has an existing expiry
2402 GT -> Set expiry only when the new expiry is greater than current one
2403 LT -> Set expiry only when the new expiry is less than current one
2405 For more information, see https://redis.io/commands/pexpireat
2406 """
2407 if isinstance(when, datetime.datetime):
2408 when = int(when.timestamp() * 1000)
2409 exp_option = list()
2410 if nx:
2411 exp_option.append("NX")
2412 if xx:
2413 exp_option.append("XX")
2414 if gt:
2415 exp_option.append("GT")
2416 if lt:
2417 exp_option.append("LT")
2418 return self.execute_command("PEXPIREAT", name, when, *exp_option)
2420 def pexpiretime(self, key: str) -> int:
2421 """
2422 Returns the absolute Unix timestamp (since January 1, 1970) in milliseconds
2423 at which the given key will expire.
2425 For more information, see https://redis.io/commands/pexpiretime
2426 """
2427 return self.execute_command("PEXPIRETIME", key)
2429 def psetex(self, name: KeyT, time_ms: ExpiryT, value: EncodableT):
2430 """
2431 Set the value of key ``name`` to ``value`` that expires in ``time_ms``
2432 milliseconds. ``time_ms`` can be represented by an integer or a Python
2433 timedelta object
2435 For more information, see https://redis.io/commands/psetex
2436 """
2437 if isinstance(time_ms, datetime.timedelta):
2438 time_ms = int(time_ms.total_seconds() * 1000)
2439 return self.execute_command("PSETEX", name, time_ms, value)
2441 def pttl(self, name: KeyT) -> ResponseT:
2442 """
2443 Returns the number of milliseconds until the key ``name`` will expire
2445 For more information, see https://redis.io/commands/pttl
2446 """
2447 return self.execute_command("PTTL", name)
2449 def hrandfield(
2450 self, key: str, count: Optional[int] = None, withvalues: bool = False
2451 ) -> ResponseT:
2452 """
2453 Return a random field from the hash value stored at key.
2455 count: if the argument is positive, return an array of distinct fields.
2456 If called with a negative count, the behavior changes and the command
2457 is allowed to return the same field multiple times. In this case,
2458 the number of returned fields is the absolute value of the
2459 specified count.
2460 withvalues: The optional WITHVALUES modifier changes the reply so it
2461 includes the respective values of the randomly selected hash fields.
2463 For more information, see https://redis.io/commands/hrandfield
2464 """
2465 params = []
2466 if count is not None:
2467 params.append(count)
2468 if withvalues:
2469 params.append("WITHVALUES")
2471 return self.execute_command("HRANDFIELD", key, *params)
2473 def randomkey(self, **kwargs) -> ResponseT:
2474 """
2475 Returns the name of a random key
2477 For more information, see https://redis.io/commands/randomkey
2478 """
2479 return self.execute_command("RANDOMKEY", **kwargs)
2481 def rename(self, src: KeyT, dst: KeyT) -> ResponseT:
2482 """
2483 Rename key ``src`` to ``dst``
2485 For more information, see https://redis.io/commands/rename
2486 """
2487 return self.execute_command("RENAME", src, dst)
2489 def renamenx(self, src: KeyT, dst: KeyT):
2490 """
2491 Rename key ``src`` to ``dst`` if ``dst`` doesn't already exist
2493 For more information, see https://redis.io/commands/renamenx
2494 """
2495 return self.execute_command("RENAMENX", src, dst)
2497 def restore(
2498 self,
2499 name: KeyT,
2500 ttl: float,
2501 value: EncodableT,
2502 replace: bool = False,
2503 absttl: bool = False,
2504 idletime: Optional[int] = None,
2505 frequency: Optional[int] = None,
2506 ) -> ResponseT:
2507 """
2508 Create a key using the provided serialized value, previously obtained
2509 using DUMP.
2511 ``replace`` allows an existing key on ``name`` to be overridden. If
2512 it's not specified an error is raised on collision.
2514 ``absttl`` if True, specified ``ttl`` should represent an absolute Unix
2515 timestamp in milliseconds in which the key will expire. (Redis 5.0 or
2516 greater).
2518 ``idletime`` Used for eviction, this is the number of seconds the
2519 key must be idle, prior to execution.
2521 ``frequency`` Used for eviction, this is the frequency counter of
2522 the object stored at the key, prior to execution.
2524 For more information, see https://redis.io/commands/restore
2525 """
2526 params = [name, ttl, value]
2527 if replace:
2528 params.append("REPLACE")
2529 if absttl:
2530 params.append("ABSTTL")
2531 if idletime is not None:
2532 params.append("IDLETIME")
2533 try:
2534 params.append(int(idletime))
2535 except ValueError:
2536 raise DataError("idletimemust be an integer")
2538 if frequency is not None:
2539 params.append("FREQ")
2540 try:
2541 params.append(int(frequency))
2542 except ValueError:
2543 raise DataError("frequency must be an integer")
2545 return self.execute_command("RESTORE", *params)
2547 @experimental_args(["ifeq", "ifne", "ifdeq", "ifdne"])
2548 def set(
2549 self,
2550 name: KeyT,
2551 value: EncodableT,
2552 ex: Optional[ExpiryT] = None,
2553 px: Optional[ExpiryT] = None,
2554 nx: bool = False,
2555 xx: bool = False,
2556 keepttl: bool = False,
2557 get: bool = False,
2558 exat: Optional[AbsExpiryT] = None,
2559 pxat: Optional[AbsExpiryT] = None,
2560 ifeq: Optional[Union[bytes, str]] = None,
2561 ifne: Optional[Union[bytes, str]] = None,
2562 ifdeq: Optional[str] = None, # hex digest of current value
2563 ifdne: Optional[str] = None, # hex digest of current value
2564 ) -> ResponseT:
2565 """
2566 Set the value at key ``name`` to ``value``
2568 Warning:
2569 **Experimental** since 7.1.
2570 The usage of the arguments ``ifeq``, ``ifne``, ``ifdeq``, and ``ifdne``
2571 is experimental. The API or returned results when those parameters are used
2572 may change based on feedback.
2574 ``ex`` sets an expire flag on key ``name`` for ``ex`` seconds.
2576 ``px`` sets an expire flag on key ``name`` for ``px`` milliseconds.
2578 ``nx`` if set to True, set the value at key ``name`` to ``value`` only
2579 if it does not exist.
2581 ``xx`` if set to True, set the value at key ``name`` to ``value`` only
2582 if it already exists.
2584 ``keepttl`` if True, retain the time to live associated with the key.
2585 (Available since Redis 6.0)
2587 ``get`` if True, set the value at key ``name`` to ``value`` and return
2588 the old value stored at key, or None if the key did not exist.
2589 (Available since Redis 6.2)
2591 ``exat`` sets an expire flag on key ``name`` for ``ex`` seconds,
2592 specified in unix time.
2594 ``pxat`` sets an expire flag on key ``name`` for ``ex`` milliseconds,
2595 specified in unix time.
2597 ``ifeq`` set the value at key ``name`` to ``value`` only if the current
2598 value exactly matches the argument.
2599 If key doesn’t exist - it won’t be created.
2600 (Requires Redis 8.4 or greater)
2602 ``ifne`` set the value at key ``name`` to ``value`` only if the current
2603 value does not exactly match the argument.
2604 If key doesn’t exist - it will be created.
2605 (Requires Redis 8.4 or greater)
2607 ``ifdeq`` set the value at key ``name`` to ``value`` only if the current
2608 value XXH3 hex digest exactly matches the argument.
2609 If key doesn’t exist - it won’t be created.
2610 (Requires Redis 8.4 or greater)
2612 ``ifdne`` set the value at key ``name`` to ``value`` only if the current
2613 value XXH3 hex digest does not exactly match the argument.
2614 If key doesn’t exist - it will be created.
2615 (Requires Redis 8.4 or greater)
2617 For more information, see https://redis.io/commands/set
2618 """
2620 if not at_most_one_value_set((ex, px, exat, pxat, keepttl)):
2621 raise DataError(
2622 "``ex``, ``px``, ``exat``, ``pxat``, "
2623 "and ``keepttl`` are mutually exclusive."
2624 )
2626 # Enforce mutual exclusivity among all conditional switches.
2627 if not at_most_one_value_set((nx, xx, ifeq, ifne, ifdeq, ifdne)):
2628 raise DataError(
2629 "``nx``, ``xx``, ``ifeq``, ``ifne``, ``ifdeq``, ``ifdne`` are mutually exclusive."
2630 )
2632 pieces: list[EncodableT] = [name, value]
2633 options = {}
2635 # Conditional modifier (exactly one at most)
2636 if nx:
2637 pieces.append("NX")
2638 elif xx:
2639 pieces.append("XX")
2640 elif ifeq is not None:
2641 pieces.extend(("IFEQ", ifeq))
2642 elif ifne is not None:
2643 pieces.extend(("IFNE", ifne))
2644 elif ifdeq is not None:
2645 pieces.extend(("IFDEQ", ifdeq))
2646 elif ifdne is not None:
2647 pieces.extend(("IFDNE", ifdne))
2649 if get:
2650 pieces.append("GET")
2651 options["get"] = True
2653 pieces.extend(extract_expire_flags(ex, px, exat, pxat))
2655 if keepttl:
2656 pieces.append("KEEPTTL")
2658 return self.execute_command("SET", *pieces, **options)
2660 def __setitem__(self, name: KeyT, value: EncodableT):
2661 self.set(name, value)
2663 def setbit(self, name: KeyT, offset: int, value: int) -> ResponseT:
2664 """
2665 Flag the ``offset`` in ``name`` as ``value``. Returns an integer
2666 indicating the previous value of ``offset``.
2668 For more information, see https://redis.io/commands/setbit
2669 """
2670 value = value and 1 or 0
2671 return self.execute_command("SETBIT", name, offset, value)
2673 def setex(self, name: KeyT, time: ExpiryT, value: EncodableT) -> ResponseT:
2674 """
2675 Set the value of key ``name`` to ``value`` that expires in ``time``
2676 seconds. ``time`` can be represented by an integer or a Python
2677 timedelta object.
2679 For more information, see https://redis.io/commands/setex
2680 """
2681 if isinstance(time, datetime.timedelta):
2682 time = int(time.total_seconds())
2683 return self.execute_command("SETEX", name, time, value)
2685 def setnx(self, name: KeyT, value: EncodableT) -> ResponseT:
2686 """
2687 Set the value of key ``name`` to ``value`` if key doesn't exist
2689 For more information, see https://redis.io/commands/setnx
2690 """
2691 return self.execute_command("SETNX", name, value)
2693 def setrange(self, name: KeyT, offset: int, value: EncodableT) -> ResponseT:
2694 """
2695 Overwrite bytes in the value of ``name`` starting at ``offset`` with
2696 ``value``. If ``offset`` plus the length of ``value`` exceeds the
2697 length of the original value, the new value will be larger than before.
2698 If ``offset`` exceeds the length of the original value, null bytes
2699 will be used to pad between the end of the previous value and the start
2700 of what's being injected.
2702 Returns the length of the new string.
2704 For more information, see https://redis.io/commands/setrange
2705 """
2706 return self.execute_command("SETRANGE", name, offset, value)
2708 def stralgo(
2709 self,
2710 algo: Literal["LCS"],
2711 value1: KeyT,
2712 value2: KeyT,
2713 specific_argument: Union[Literal["strings"], Literal["keys"]] = "strings",
2714 len: bool = False,
2715 idx: bool = False,
2716 minmatchlen: Optional[int] = None,
2717 withmatchlen: bool = False,
2718 **kwargs,
2719 ) -> ResponseT:
2720 """
2721 Implements complex algorithms that operate on strings.
2722 Right now the only algorithm implemented is the LCS algorithm
2723 (longest common substring). However new algorithms could be
2724 implemented in the future.
2726 ``algo`` Right now must be LCS
2727 ``value1`` and ``value2`` Can be two strings or two keys
2728 ``specific_argument`` Specifying if the arguments to the algorithm
2729 will be keys or strings. strings is the default.
2730 ``len`` Returns just the len of the match.
2731 ``idx`` Returns the match positions in each string.
2732 ``minmatchlen`` Restrict the list of matches to the ones of a given
2733 minimal length. Can be provided only when ``idx`` set to True.
2734 ``withmatchlen`` Returns the matches with the len of the match.
2735 Can be provided only when ``idx`` set to True.
2737 For more information, see https://redis.io/commands/stralgo
2738 """
2739 # check validity
2740 supported_algo = ["LCS"]
2741 if algo not in supported_algo:
2742 supported_algos_str = ", ".join(supported_algo)
2743 raise DataError(f"The supported algorithms are: {supported_algos_str}")
2744 if specific_argument not in ["keys", "strings"]:
2745 raise DataError("specific_argument can be only keys or strings")
2746 if len and idx:
2747 raise DataError("len and idx cannot be provided together.")
2749 pieces: list[EncodableT] = [algo, specific_argument.upper(), value1, value2]
2750 if len:
2751 pieces.append(b"LEN")
2752 if idx:
2753 pieces.append(b"IDX")
2754 try:
2755 int(minmatchlen)
2756 pieces.extend([b"MINMATCHLEN", minmatchlen])
2757 except TypeError:
2758 pass
2759 if withmatchlen:
2760 pieces.append(b"WITHMATCHLEN")
2762 return self.execute_command(
2763 "STRALGO",
2764 *pieces,
2765 len=len,
2766 idx=idx,
2767 minmatchlen=minmatchlen,
2768 withmatchlen=withmatchlen,
2769 **kwargs,
2770 )
2772 def strlen(self, name: KeyT) -> ResponseT:
2773 """
2774 Return the number of bytes stored in the value of ``name``
2776 For more information, see https://redis.io/commands/strlen
2777 """
2778 return self.execute_command("STRLEN", name, keys=[name])
2780 def substr(self, name: KeyT, start: int, end: int = -1) -> ResponseT:
2781 """
2782 Return a substring of the string at key ``name``. ``start`` and ``end``
2783 are 0-based integers specifying the portion of the string to return.
2784 """
2785 return self.execute_command("SUBSTR", name, start, end, keys=[name])
2787 def touch(self, *args: KeyT) -> ResponseT:
2788 """
2789 Alters the last access time of a key(s) ``*args``. A key is ignored
2790 if it does not exist.
2792 For more information, see https://redis.io/commands/touch
2793 """
2794 return self.execute_command("TOUCH", *args)
2796 def ttl(self, name: KeyT) -> ResponseT:
2797 """
2798 Returns the number of seconds until the key ``name`` will expire
2800 For more information, see https://redis.io/commands/ttl
2801 """
2802 return self.execute_command("TTL", name)
2804 def type(self, name: KeyT) -> ResponseT:
2805 """
2806 Returns the type of key ``name``
2808 For more information, see https://redis.io/commands/type
2809 """
2810 return self.execute_command("TYPE", name, keys=[name])
2812 def watch(self, *names: KeyT) -> None:
2813 """
2814 Watches the values at keys ``names``, or None if the key doesn't exist
2816 For more information, see https://redis.io/commands/watch
2817 """
2818 warnings.warn(DeprecationWarning("Call WATCH from a Pipeline object"))
2820 def unwatch(self) -> None:
2821 """
2822 Unwatches all previously watched keys for a transaction
2824 For more information, see https://redis.io/commands/unwatch
2825 """
2826 warnings.warn(DeprecationWarning("Call UNWATCH from a Pipeline object"))
2828 def unlink(self, *names: KeyT) -> ResponseT:
2829 """
2830 Unlink one or more keys specified by ``names``
2832 For more information, see https://redis.io/commands/unlink
2833 """
2834 return self.execute_command("UNLINK", *names)
2836 def lcs(
2837 self,
2838 key1: str,
2839 key2: str,
2840 len: Optional[bool] = False,
2841 idx: Optional[bool] = False,
2842 minmatchlen: Optional[int] = 0,
2843 withmatchlen: Optional[bool] = False,
2844 ) -> Union[str, int, list]:
2845 """
2846 Find the longest common subsequence between ``key1`` and ``key2``.
2847 If ``len`` is true the length of the match will will be returned.
2848 If ``idx`` is true the match position in each strings will be returned.
2849 ``minmatchlen`` restrict the list of matches to the ones of
2850 the given ``minmatchlen``.
2851 If ``withmatchlen`` the length of the match also will be returned.
2852 For more information, see https://redis.io/commands/lcs
2853 """
2854 pieces = [key1, key2]
2855 if len:
2856 pieces.append("LEN")
2857 if idx:
2858 pieces.append("IDX")
2859 if minmatchlen != 0:
2860 pieces.extend(["MINMATCHLEN", minmatchlen])
2861 if withmatchlen:
2862 pieces.append("WITHMATCHLEN")
2863 return self.execute_command("LCS", *pieces, keys=[key1, key2])
2866class AsyncBasicKeyCommands(BasicKeyCommands):
2867 def __delitem__(self, name: KeyT):
2868 raise TypeError("Async Redis client does not support class deletion")
2870 def __contains__(self, name: KeyT):
2871 raise TypeError("Async Redis client does not support class inclusion")
2873 def __getitem__(self, name: KeyT):
2874 raise TypeError("Async Redis client does not support class retrieval")
2876 def __setitem__(self, name: KeyT, value: EncodableT):
2877 raise TypeError("Async Redis client does not support class assignment")
2879 async def watch(self, *names: KeyT) -> None:
2880 return super().watch(*names)
2882 async def unwatch(self) -> None:
2883 return super().unwatch()
2886class ListCommands(CommandsProtocol):
2887 """
2888 Redis commands for List data type.
2889 see: https://redis.io/topics/data-types#lists
2890 """
2892 def blpop(
2893 self, keys: List, timeout: Optional[Number] = 0
2894 ) -> Union[Awaitable[list], list]:
2895 """
2896 LPOP a value off of the first non-empty list
2897 named in the ``keys`` list.
2899 If none of the lists in ``keys`` has a value to LPOP, then block
2900 for ``timeout`` seconds, or until a value gets pushed on to one
2901 of the lists.
2903 If timeout is 0, then block indefinitely.
2905 For more information, see https://redis.io/commands/blpop
2906 """
2907 if timeout is None:
2908 timeout = 0
2909 keys = list_or_args(keys, None)
2910 keys.append(timeout)
2911 return self.execute_command("BLPOP", *keys)
2913 def brpop(
2914 self, keys: List, timeout: Optional[Number] = 0
2915 ) -> Union[Awaitable[list], list]:
2916 """
2917 RPOP a value off of the first non-empty list
2918 named in the ``keys`` list.
2920 If none of the lists in ``keys`` has a value to RPOP, then block
2921 for ``timeout`` seconds, or until a value gets pushed on to one
2922 of the lists.
2924 If timeout is 0, then block indefinitely.
2926 For more information, see https://redis.io/commands/brpop
2927 """
2928 if timeout is None:
2929 timeout = 0
2930 keys = list_or_args(keys, None)
2931 keys.append(timeout)
2932 return self.execute_command("BRPOP", *keys)
2934 def brpoplpush(
2935 self, src: KeyT, dst: KeyT, timeout: Optional[Number] = 0
2936 ) -> Union[Awaitable[Optional[str]], Optional[str]]:
2937 """
2938 Pop a value off the tail of ``src``, push it on the head of ``dst``
2939 and then return it.
2941 This command blocks until a value is in ``src`` or until ``timeout``
2942 seconds elapse, whichever is first. A ``timeout`` value of 0 blocks
2943 forever.
2945 For more information, see https://redis.io/commands/brpoplpush
2946 """
2947 if timeout is None:
2948 timeout = 0
2949 return self.execute_command("BRPOPLPUSH", src, dst, timeout)
2951 def blmpop(
2952 self,
2953 timeout: float,
2954 numkeys: int,
2955 *args: str,
2956 direction: str,
2957 count: Optional[int] = 1,
2958 ) -> Optional[list]:
2959 """
2960 Pop ``count`` values (default 1) from first non-empty in the list
2961 of provided key names.
2963 When all lists are empty this command blocks the connection until another
2964 client pushes to it or until the timeout, timeout of 0 blocks indefinitely
2966 For more information, see https://redis.io/commands/blmpop
2967 """
2968 cmd_args = [timeout, numkeys, *args, direction, "COUNT", count]
2970 return self.execute_command("BLMPOP", *cmd_args)
2972 def lmpop(
2973 self,
2974 num_keys: int,
2975 *args: str,
2976 direction: str,
2977 count: Optional[int] = 1,
2978 ) -> Union[Awaitable[list], list]:
2979 """
2980 Pop ``count`` values (default 1) first non-empty list key from the list
2981 of args provided key names.
2983 For more information, see https://redis.io/commands/lmpop
2984 """
2985 cmd_args = [num_keys] + list(args) + [direction]
2986 if count != 1:
2987 cmd_args.extend(["COUNT", count])
2989 return self.execute_command("LMPOP", *cmd_args)
2991 def lindex(
2992 self, name: KeyT, index: int
2993 ) -> Union[Awaitable[Optional[str]], Optional[str]]:
2994 """
2995 Return the item from list ``name`` at position ``index``
2997 Negative indexes are supported and will return an item at the
2998 end of the list
3000 For more information, see https://redis.io/commands/lindex
3001 """
3002 return self.execute_command("LINDEX", name, index, keys=[name])
3004 def linsert(
3005 self, name: KeyT, where: str, refvalue: str, value: str
3006 ) -> Union[Awaitable[int], int]:
3007 """
3008 Insert ``value`` in list ``name`` either immediately before or after
3009 [``where``] ``refvalue``
3011 Returns the new length of the list on success or -1 if ``refvalue``
3012 is not in the list.
3014 For more information, see https://redis.io/commands/linsert
3015 """
3016 return self.execute_command("LINSERT", name, where, refvalue, value)
3018 def llen(self, name: KeyT) -> Union[Awaitable[int], int]:
3019 """
3020 Return the length of the list ``name``
3022 For more information, see https://redis.io/commands/llen
3023 """
3024 return self.execute_command("LLEN", name, keys=[name])
3026 def lpop(
3027 self,
3028 name: KeyT,
3029 count: Optional[int] = None,
3030 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]:
3031 """
3032 Removes and returns the first elements of the list ``name``.
3034 By default, the command pops a single element from the beginning of
3035 the list. When provided with the optional ``count`` argument, the reply
3036 will consist of up to count elements, depending on the list's length.
3038 For more information, see https://redis.io/commands/lpop
3039 """
3040 if count is not None:
3041 return self.execute_command("LPOP", name, count)
3042 else:
3043 return self.execute_command("LPOP", name)
3045 def lpush(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
3046 """
3047 Push ``values`` onto the head of the list ``name``
3049 For more information, see https://redis.io/commands/lpush
3050 """
3051 return self.execute_command("LPUSH", name, *values)
3053 def lpushx(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
3054 """
3055 Push ``value`` onto the head of the list ``name`` if ``name`` exists
3057 For more information, see https://redis.io/commands/lpushx
3058 """
3059 return self.execute_command("LPUSHX", name, *values)
3061 def lrange(self, name: KeyT, start: int, end: int) -> Union[Awaitable[list], list]:
3062 """
3063 Return a slice of the list ``name`` between
3064 position ``start`` and ``end``
3066 ``start`` and ``end`` can be negative numbers just like
3067 Python slicing notation
3069 For more information, see https://redis.io/commands/lrange
3070 """
3071 return self.execute_command("LRANGE", name, start, end, keys=[name])
3073 def lrem(self, name: KeyT, count: int, value: str) -> Union[Awaitable[int], int]:
3074 """
3075 Remove the first ``count`` occurrences of elements equal to ``value``
3076 from the list stored at ``name``.
3078 The count argument influences the operation in the following ways:
3079 count > 0: Remove elements equal to value moving from head to tail.
3080 count < 0: Remove elements equal to value moving from tail to head.
3081 count = 0: Remove all elements equal to value.
3083 For more information, see https://redis.io/commands/lrem
3084 """
3085 return self.execute_command("LREM", name, count, value)
3087 def lset(self, name: KeyT, index: int, value: str) -> Union[Awaitable[str], str]:
3088 """
3089 Set element at ``index`` of list ``name`` to ``value``
3091 For more information, see https://redis.io/commands/lset
3092 """
3093 return self.execute_command("LSET", name, index, value)
3095 def ltrim(self, name: KeyT, start: int, end: int) -> Union[Awaitable[str], str]:
3096 """
3097 Trim the list ``name``, removing all values not within the slice
3098 between ``start`` and ``end``
3100 ``start`` and ``end`` can be negative numbers just like
3101 Python slicing notation
3103 For more information, see https://redis.io/commands/ltrim
3104 """
3105 return self.execute_command("LTRIM", name, start, end)
3107 def rpop(
3108 self,
3109 name: KeyT,
3110 count: Optional[int] = None,
3111 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]:
3112 """
3113 Removes and returns the last elements of the list ``name``.
3115 By default, the command pops a single element from the end of the list.
3116 When provided with the optional ``count`` argument, the reply will
3117 consist of up to count elements, depending on the list's length.
3119 For more information, see https://redis.io/commands/rpop
3120 """
3121 if count is not None:
3122 return self.execute_command("RPOP", name, count)
3123 else:
3124 return self.execute_command("RPOP", name)
3126 def rpoplpush(self, src: KeyT, dst: KeyT) -> Union[Awaitable[str], str]:
3127 """
3128 RPOP a value off of the ``src`` list and atomically LPUSH it
3129 on to the ``dst`` list. Returns the value.
3131 For more information, see https://redis.io/commands/rpoplpush
3132 """
3133 return self.execute_command("RPOPLPUSH", src, dst)
3135 def rpush(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
3136 """
3137 Push ``values`` onto the tail of the list ``name``
3139 For more information, see https://redis.io/commands/rpush
3140 """
3141 return self.execute_command("RPUSH", name, *values)
3143 def rpushx(self, name: KeyT, *values: str) -> Union[Awaitable[int], int]:
3144 """
3145 Push ``value`` onto the tail of the list ``name`` if ``name`` exists
3147 For more information, see https://redis.io/commands/rpushx
3148 """
3149 return self.execute_command("RPUSHX", name, *values)
3151 def lpos(
3152 self,
3153 name: KeyT,
3154 value: str,
3155 rank: Optional[int] = None,
3156 count: Optional[int] = None,
3157 maxlen: Optional[int] = None,
3158 ) -> Union[str, List, None]:
3159 """
3160 Get position of ``value`` within the list ``name``
3162 If specified, ``rank`` indicates the "rank" of the first element to
3163 return in case there are multiple copies of ``value`` in the list.
3164 By default, LPOS returns the position of the first occurrence of
3165 ``value`` in the list. When ``rank`` 2, LPOS returns the position of
3166 the second ``value`` in the list. If ``rank`` is negative, LPOS
3167 searches the list in reverse. For example, -1 would return the
3168 position of the last occurrence of ``value`` and -2 would return the
3169 position of the next to last occurrence of ``value``.
3171 If specified, ``count`` indicates that LPOS should return a list of
3172 up to ``count`` positions. A ``count`` of 2 would return a list of
3173 up to 2 positions. A ``count`` of 0 returns a list of all positions
3174 matching ``value``. When ``count`` is specified and but ``value``
3175 does not exist in the list, an empty list is returned.
3177 If specified, ``maxlen`` indicates the maximum number of list
3178 elements to scan. A ``maxlen`` of 1000 will only return the
3179 position(s) of items within the first 1000 entries in the list.
3180 A ``maxlen`` of 0 (the default) will scan the entire list.
3182 For more information, see https://redis.io/commands/lpos
3183 """
3184 pieces: list[EncodableT] = [name, value]
3185 if rank is not None:
3186 pieces.extend(["RANK", rank])
3188 if count is not None:
3189 pieces.extend(["COUNT", count])
3191 if maxlen is not None:
3192 pieces.extend(["MAXLEN", maxlen])
3194 return self.execute_command("LPOS", *pieces, keys=[name])
3196 def sort(
3197 self,
3198 name: KeyT,
3199 start: Optional[int] = None,
3200 num: Optional[int] = None,
3201 by: Optional[str] = None,
3202 get: Optional[List[str]] = None,
3203 desc: bool = False,
3204 alpha: bool = False,
3205 store: Optional[str] = None,
3206 groups: Optional[bool] = False,
3207 ) -> Union[List, int]:
3208 """
3209 Sort and return the list, set or sorted set at ``name``.
3211 ``start`` and ``num`` allow for paging through the sorted data
3213 ``by`` allows using an external key to weight and sort the items.
3214 Use an "*" to indicate where in the key the item value is located
3216 ``get`` allows for returning items from external keys rather than the
3217 sorted data itself. Use an "*" to indicate where in the key
3218 the item value is located
3220 ``desc`` allows for reversing the sort
3222 ``alpha`` allows for sorting lexicographically rather than numerically
3224 ``store`` allows for storing the result of the sort into
3225 the key ``store``
3227 ``groups`` if set to True and if ``get`` contains at least two
3228 elements, sort will return a list of tuples, each containing the
3229 values fetched from the arguments to ``get``.
3231 For more information, see https://redis.io/commands/sort
3232 """
3233 if (start is not None and num is None) or (num is not None and start is None):
3234 raise DataError("``start`` and ``num`` must both be specified")
3236 pieces: list[EncodableT] = [name]
3237 if by is not None:
3238 pieces.extend([b"BY", by])
3239 if start is not None and num is not None:
3240 pieces.extend([b"LIMIT", start, num])
3241 if get is not None:
3242 # If get is a string assume we want to get a single value.
3243 # Otherwise assume it's an interable and we want to get multiple
3244 # values. We can't just iterate blindly because strings are
3245 # iterable.
3246 if isinstance(get, (bytes, str)):
3247 pieces.extend([b"GET", get])
3248 else:
3249 for g in get:
3250 pieces.extend([b"GET", g])
3251 if desc:
3252 pieces.append(b"DESC")
3253 if alpha:
3254 pieces.append(b"ALPHA")
3255 if store is not None:
3256 pieces.extend([b"STORE", store])
3257 if groups:
3258 if not get or isinstance(get, (bytes, str)) or len(get) < 2:
3259 raise DataError(
3260 'when using "groups" the "get" argument '
3261 "must be specified and contain at least "
3262 "two keys"
3263 )
3265 options = {"groups": len(get) if groups else None}
3266 options["keys"] = [name]
3267 return self.execute_command("SORT", *pieces, **options)
3269 def sort_ro(
3270 self,
3271 key: str,
3272 start: Optional[int] = None,
3273 num: Optional[int] = None,
3274 by: Optional[str] = None,
3275 get: Optional[List[str]] = None,
3276 desc: bool = False,
3277 alpha: bool = False,
3278 ) -> list:
3279 """
3280 Returns the elements contained in the list, set or sorted set at key.
3281 (read-only variant of the SORT command)
3283 ``start`` and ``num`` allow for paging through the sorted data
3285 ``by`` allows using an external key to weight and sort the items.
3286 Use an "*" to indicate where in the key the item value is located
3288 ``get`` allows for returning items from external keys rather than the
3289 sorted data itself. Use an "*" to indicate where in the key
3290 the item value is located
3292 ``desc`` allows for reversing the sort
3294 ``alpha`` allows for sorting lexicographically rather than numerically
3296 For more information, see https://redis.io/commands/sort_ro
3297 """
3298 return self.sort(
3299 key, start=start, num=num, by=by, get=get, desc=desc, alpha=alpha
3300 )
3303AsyncListCommands = ListCommands
3306class ScanCommands(CommandsProtocol):
3307 """
3308 Redis SCAN commands.
3309 see: https://redis.io/commands/scan
3310 """
3312 def scan(
3313 self,
3314 cursor: int = 0,
3315 match: Union[PatternT, None] = None,
3316 count: Optional[int] = None,
3317 _type: Optional[str] = None,
3318 **kwargs,
3319 ) -> ResponseT:
3320 """
3321 Incrementally return lists of key names. Also return a cursor
3322 indicating the scan position.
3324 ``match`` allows for filtering the keys by pattern
3326 ``count`` provides a hint to Redis about the number of keys to
3327 return per batch.
3329 ``_type`` filters the returned values by a particular Redis type.
3330 Stock Redis instances allow for the following types:
3331 HASH, LIST, SET, STREAM, STRING, ZSET
3332 Additionally, Redis modules can expose other types as well.
3334 For more information, see https://redis.io/commands/scan
3335 """
3336 pieces: list[EncodableT] = [cursor]
3337 if match is not None:
3338 pieces.extend([b"MATCH", match])
3339 if count is not None:
3340 pieces.extend([b"COUNT", count])
3341 if _type is not None:
3342 pieces.extend([b"TYPE", _type])
3343 return self.execute_command("SCAN", *pieces, **kwargs)
3345 def scan_iter(
3346 self,
3347 match: Union[PatternT, None] = None,
3348 count: Optional[int] = None,
3349 _type: Optional[str] = None,
3350 **kwargs,
3351 ) -> Iterator:
3352 """
3353 Make an iterator using the SCAN command so that the client doesn't
3354 need to remember the cursor position.
3356 ``match`` allows for filtering the keys by pattern
3358 ``count`` provides a hint to Redis about the number of keys to
3359 return per batch.
3361 ``_type`` filters the returned values by a particular Redis type.
3362 Stock Redis instances allow for the following types:
3363 HASH, LIST, SET, STREAM, STRING, ZSET
3364 Additionally, Redis modules can expose other types as well.
3365 """
3366 cursor = "0"
3367 while cursor != 0:
3368 cursor, data = self.scan(
3369 cursor=cursor, match=match, count=count, _type=_type, **kwargs
3370 )
3371 yield from data
3373 def sscan(
3374 self,
3375 name: KeyT,
3376 cursor: int = 0,
3377 match: Union[PatternT, None] = None,
3378 count: Optional[int] = None,
3379 ) -> ResponseT:
3380 """
3381 Incrementally return lists of elements in a set. Also return a cursor
3382 indicating the scan position.
3384 ``match`` allows for filtering the keys by pattern
3386 ``count`` allows for hint the minimum number of returns
3388 For more information, see https://redis.io/commands/sscan
3389 """
3390 pieces: list[EncodableT] = [name, cursor]
3391 if match is not None:
3392 pieces.extend([b"MATCH", match])
3393 if count is not None:
3394 pieces.extend([b"COUNT", count])
3395 return self.execute_command("SSCAN", *pieces)
3397 def sscan_iter(
3398 self,
3399 name: KeyT,
3400 match: Union[PatternT, None] = None,
3401 count: Optional[int] = None,
3402 ) -> Iterator:
3403 """
3404 Make an iterator using the SSCAN command so that the client doesn't
3405 need to remember the cursor position.
3407 ``match`` allows for filtering the keys by pattern
3409 ``count`` allows for hint the minimum number of returns
3410 """
3411 cursor = "0"
3412 while cursor != 0:
3413 cursor, data = self.sscan(name, cursor=cursor, match=match, count=count)
3414 yield from data
3416 def hscan(
3417 self,
3418 name: KeyT,
3419 cursor: int = 0,
3420 match: Union[PatternT, None] = None,
3421 count: Optional[int] = None,
3422 no_values: Union[bool, None] = None,
3423 ) -> ResponseT:
3424 """
3425 Incrementally return key/value slices in a hash. Also return a cursor
3426 indicating the scan position.
3428 ``match`` allows for filtering the keys by pattern
3430 ``count`` allows for hint the minimum number of returns
3432 ``no_values`` indicates to return only the keys, without values.
3434 For more information, see https://redis.io/commands/hscan
3435 """
3436 pieces: list[EncodableT] = [name, cursor]
3437 if match is not None:
3438 pieces.extend([b"MATCH", match])
3439 if count is not None:
3440 pieces.extend([b"COUNT", count])
3441 if no_values is not None:
3442 pieces.extend([b"NOVALUES"])
3443 return self.execute_command("HSCAN", *pieces, no_values=no_values)
3445 def hscan_iter(
3446 self,
3447 name: str,
3448 match: Union[PatternT, None] = None,
3449 count: Optional[int] = None,
3450 no_values: Union[bool, None] = None,
3451 ) -> Iterator:
3452 """
3453 Make an iterator using the HSCAN command so that the client doesn't
3454 need to remember the cursor position.
3456 ``match`` allows for filtering the keys by pattern
3458 ``count`` allows for hint the minimum number of returns
3460 ``no_values`` indicates to return only the keys, without values
3461 """
3462 cursor = "0"
3463 while cursor != 0:
3464 cursor, data = self.hscan(
3465 name, cursor=cursor, match=match, count=count, no_values=no_values
3466 )
3467 if no_values:
3468 yield from data
3469 else:
3470 yield from data.items()
3472 def zscan(
3473 self,
3474 name: KeyT,
3475 cursor: int = 0,
3476 match: Union[PatternT, None] = None,
3477 count: Optional[int] = None,
3478 score_cast_func: Union[type, Callable] = float,
3479 ) -> ResponseT:
3480 """
3481 Incrementally return lists of elements in a sorted set. Also return a
3482 cursor indicating the scan position.
3484 ``match`` allows for filtering the keys by pattern
3486 ``count`` allows for hint the minimum number of returns
3488 ``score_cast_func`` a callable used to cast the score return value
3490 For more information, see https://redis.io/commands/zscan
3491 """
3492 pieces = [name, cursor]
3493 if match is not None:
3494 pieces.extend([b"MATCH", match])
3495 if count is not None:
3496 pieces.extend([b"COUNT", count])
3497 options = {"score_cast_func": score_cast_func}
3498 return self.execute_command("ZSCAN", *pieces, **options)
3500 def zscan_iter(
3501 self,
3502 name: KeyT,
3503 match: Union[PatternT, None] = None,
3504 count: Optional[int] = None,
3505 score_cast_func: Union[type, Callable] = float,
3506 ) -> Iterator:
3507 """
3508 Make an iterator using the ZSCAN command so that the client doesn't
3509 need to remember the cursor position.
3511 ``match`` allows for filtering the keys by pattern
3513 ``count`` allows for hint the minimum number of returns
3515 ``score_cast_func`` a callable used to cast the score return value
3516 """
3517 cursor = "0"
3518 while cursor != 0:
3519 cursor, data = self.zscan(
3520 name,
3521 cursor=cursor,
3522 match=match,
3523 count=count,
3524 score_cast_func=score_cast_func,
3525 )
3526 yield from data
3529class AsyncScanCommands(ScanCommands):
3530 async def scan_iter(
3531 self,
3532 match: Union[PatternT, None] = None,
3533 count: Optional[int] = None,
3534 _type: Optional[str] = None,
3535 **kwargs,
3536 ) -> AsyncIterator:
3537 """
3538 Make an iterator using the SCAN command so that the client doesn't
3539 need to remember the cursor position.
3541 ``match`` allows for filtering the keys by pattern
3543 ``count`` provides a hint to Redis about the number of keys to
3544 return per batch.
3546 ``_type`` filters the returned values by a particular Redis type.
3547 Stock Redis instances allow for the following types:
3548 HASH, LIST, SET, STREAM, STRING, ZSET
3549 Additionally, Redis modules can expose other types as well.
3550 """
3551 cursor = "0"
3552 while cursor != 0:
3553 cursor, data = await self.scan(
3554 cursor=cursor, match=match, count=count, _type=_type, **kwargs
3555 )
3556 for d in data:
3557 yield d
3559 async def sscan_iter(
3560 self,
3561 name: KeyT,
3562 match: Union[PatternT, None] = None,
3563 count: Optional[int] = None,
3564 ) -> AsyncIterator:
3565 """
3566 Make an iterator using the SSCAN command so that the client doesn't
3567 need to remember the cursor position.
3569 ``match`` allows for filtering the keys by pattern
3571 ``count`` allows for hint the minimum number of returns
3572 """
3573 cursor = "0"
3574 while cursor != 0:
3575 cursor, data = await self.sscan(
3576 name, cursor=cursor, match=match, count=count
3577 )
3578 for d in data:
3579 yield d
3581 async def hscan_iter(
3582 self,
3583 name: str,
3584 match: Union[PatternT, None] = None,
3585 count: Optional[int] = None,
3586 no_values: Union[bool, None] = None,
3587 ) -> AsyncIterator:
3588 """
3589 Make an iterator using the HSCAN command so that the client doesn't
3590 need to remember the cursor position.
3592 ``match`` allows for filtering the keys by pattern
3594 ``count`` allows for hint the minimum number of returns
3596 ``no_values`` indicates to return only the keys, without values
3597 """
3598 cursor = "0"
3599 while cursor != 0:
3600 cursor, data = await self.hscan(
3601 name, cursor=cursor, match=match, count=count, no_values=no_values
3602 )
3603 if no_values:
3604 for it in data:
3605 yield it
3606 else:
3607 for it in data.items():
3608 yield it
3610 async def zscan_iter(
3611 self,
3612 name: KeyT,
3613 match: Union[PatternT, None] = None,
3614 count: Optional[int] = None,
3615 score_cast_func: Union[type, Callable] = float,
3616 ) -> AsyncIterator:
3617 """
3618 Make an iterator using the ZSCAN command so that the client doesn't
3619 need to remember the cursor position.
3621 ``match`` allows for filtering the keys by pattern
3623 ``count`` allows for hint the minimum number of returns
3625 ``score_cast_func`` a callable used to cast the score return value
3626 """
3627 cursor = "0"
3628 while cursor != 0:
3629 cursor, data = await self.zscan(
3630 name,
3631 cursor=cursor,
3632 match=match,
3633 count=count,
3634 score_cast_func=score_cast_func,
3635 )
3636 for d in data:
3637 yield d
3640class SetCommands(CommandsProtocol):
3641 """
3642 Redis commands for Set data type.
3643 see: https://redis.io/topics/data-types#sets
3644 """
3646 def sadd(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
3647 """
3648 Add ``value(s)`` to set ``name``
3650 For more information, see https://redis.io/commands/sadd
3651 """
3652 return self.execute_command("SADD", name, *values)
3654 def scard(self, name: KeyT) -> Union[Awaitable[int], int]:
3655 """
3656 Return the number of elements in set ``name``
3658 For more information, see https://redis.io/commands/scard
3659 """
3660 return self.execute_command("SCARD", name, keys=[name])
3662 def sdiff(self, keys: List, *args: List) -> Union[Awaitable[list], list]:
3663 """
3664 Return the difference of sets specified by ``keys``
3666 For more information, see https://redis.io/commands/sdiff
3667 """
3668 args = list_or_args(keys, args)
3669 return self.execute_command("SDIFF", *args, keys=args)
3671 def sdiffstore(
3672 self, dest: str, keys: List, *args: List
3673 ) -> Union[Awaitable[int], int]:
3674 """
3675 Store the difference of sets specified by ``keys`` into a new
3676 set named ``dest``. Returns the number of keys in the new set.
3678 For more information, see https://redis.io/commands/sdiffstore
3679 """
3680 args = list_or_args(keys, args)
3681 return self.execute_command("SDIFFSTORE", dest, *args)
3683 def sinter(self, keys: List, *args: List) -> Union[Awaitable[list], list]:
3684 """
3685 Return the intersection of sets specified by ``keys``
3687 For more information, see https://redis.io/commands/sinter
3688 """
3689 args = list_or_args(keys, args)
3690 return self.execute_command("SINTER", *args, keys=args)
3692 def sintercard(
3693 self, numkeys: int, keys: List[KeyT], limit: int = 0
3694 ) -> Union[Awaitable[int], int]:
3695 """
3696 Return the cardinality of the intersect of multiple sets specified by ``keys``.
3698 When LIMIT provided (defaults to 0 and means unlimited), if the intersection
3699 cardinality reaches limit partway through the computation, the algorithm will
3700 exit and yield limit as the cardinality
3702 For more information, see https://redis.io/commands/sintercard
3703 """
3704 args = [numkeys, *keys, "LIMIT", limit]
3705 return self.execute_command("SINTERCARD", *args, keys=keys)
3707 def sinterstore(
3708 self, dest: KeyT, keys: List, *args: List
3709 ) -> Union[Awaitable[int], int]:
3710 """
3711 Store the intersection of sets specified by ``keys`` into a new
3712 set named ``dest``. Returns the number of keys in the new set.
3714 For more information, see https://redis.io/commands/sinterstore
3715 """
3716 args = list_or_args(keys, args)
3717 return self.execute_command("SINTERSTORE", dest, *args)
3719 def sismember(
3720 self, name: KeyT, value: str
3721 ) -> Union[Awaitable[Union[Literal[0], Literal[1]]], Union[Literal[0], Literal[1]]]:
3722 """
3723 Return whether ``value`` is a member of set ``name``:
3724 - 1 if the value is a member of the set.
3725 - 0 if the value is not a member of the set or if key does not exist.
3727 For more information, see https://redis.io/commands/sismember
3728 """
3729 return self.execute_command("SISMEMBER", name, value, keys=[name])
3731 def smembers(self, name: KeyT) -> Union[Awaitable[Set], Set]:
3732 """
3733 Return all members of the set ``name``
3735 For more information, see https://redis.io/commands/smembers
3736 """
3737 return self.execute_command("SMEMBERS", name, keys=[name])
3739 def smismember(
3740 self, name: KeyT, values: List, *args: List
3741 ) -> Union[
3742 Awaitable[List[Union[Literal[0], Literal[1]]]],
3743 List[Union[Literal[0], Literal[1]]],
3744 ]:
3745 """
3746 Return whether each value in ``values`` is a member of the set ``name``
3747 as a list of ``int`` in the order of ``values``:
3748 - 1 if the value is a member of the set.
3749 - 0 if the value is not a member of the set or if key does not exist.
3751 For more information, see https://redis.io/commands/smismember
3752 """
3753 args = list_or_args(values, args)
3754 return self.execute_command("SMISMEMBER", name, *args, keys=[name])
3756 def smove(self, src: KeyT, dst: KeyT, value: str) -> Union[Awaitable[bool], bool]:
3757 """
3758 Move ``value`` from set ``src`` to set ``dst`` atomically
3760 For more information, see https://redis.io/commands/smove
3761 """
3762 return self.execute_command("SMOVE", src, dst, value)
3764 def spop(self, name: KeyT, count: Optional[int] = None) -> Union[str, List, None]:
3765 """
3766 Remove and return a random member of set ``name``
3768 For more information, see https://redis.io/commands/spop
3769 """
3770 args = (count is not None) and [count] or []
3771 return self.execute_command("SPOP", name, *args)
3773 def srandmember(
3774 self, name: KeyT, number: Optional[int] = None
3775 ) -> Union[str, List, None]:
3776 """
3777 If ``number`` is None, returns a random member of set ``name``.
3779 If ``number`` is supplied, returns a list of ``number`` random
3780 members of set ``name``. Note this is only available when running
3781 Redis 2.6+.
3783 For more information, see https://redis.io/commands/srandmember
3784 """
3785 args = (number is not None) and [number] or []
3786 return self.execute_command("SRANDMEMBER", name, *args)
3788 def srem(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
3789 """
3790 Remove ``values`` from set ``name``
3792 For more information, see https://redis.io/commands/srem
3793 """
3794 return self.execute_command("SREM", name, *values)
3796 def sunion(self, keys: List, *args: List) -> Union[Awaitable[List], List]:
3797 """
3798 Return the union of sets specified by ``keys``
3800 For more information, see https://redis.io/commands/sunion
3801 """
3802 args = list_or_args(keys, args)
3803 return self.execute_command("SUNION", *args, keys=args)
3805 def sunionstore(
3806 self, dest: KeyT, keys: List, *args: List
3807 ) -> Union[Awaitable[int], int]:
3808 """
3809 Store the union of sets specified by ``keys`` into a new
3810 set named ``dest``. Returns the number of keys in the new set.
3812 For more information, see https://redis.io/commands/sunionstore
3813 """
3814 args = list_or_args(keys, args)
3815 return self.execute_command("SUNIONSTORE", dest, *args)
3818AsyncSetCommands = SetCommands
3821class StreamCommands(CommandsProtocol):
3822 """
3823 Redis commands for Stream data type.
3824 see: https://redis.io/topics/streams-intro
3825 """
3827 def xack(self, name: KeyT, groupname: GroupT, *ids: StreamIdT) -> ResponseT:
3828 """
3829 Acknowledges the successful processing of one or more messages.
3831 Args:
3832 name: name of the stream.
3833 groupname: name of the consumer group.
3834 *ids: message ids to acknowledge.
3836 For more information, see https://redis.io/commands/xack
3837 """
3838 return self.execute_command("XACK", name, groupname, *ids)
3840 def xackdel(
3841 self,
3842 name: KeyT,
3843 groupname: GroupT,
3844 *ids: StreamIdT,
3845 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF",
3846 ) -> ResponseT:
3847 """
3848 Combines the functionality of XACK and XDEL. Acknowledges the specified
3849 message IDs in the given consumer group and simultaneously attempts to
3850 delete the corresponding entries from the stream.
3851 """
3852 if not ids:
3853 raise DataError("XACKDEL requires at least one message ID")
3855 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
3856 raise DataError("XACKDEL ref_policy must be one of: KEEPREF, DELREF, ACKED")
3858 pieces = [name, groupname, ref_policy, "IDS", len(ids)]
3859 pieces.extend(ids)
3860 return self.execute_command("XACKDEL", *pieces)
3862 def xadd(
3863 self,
3864 name: KeyT,
3865 fields: Dict[FieldT, EncodableT],
3866 id: StreamIdT = "*",
3867 maxlen: Optional[int] = None,
3868 approximate: bool = True,
3869 nomkstream: bool = False,
3870 minid: Union[StreamIdT, None] = None,
3871 limit: Optional[int] = None,
3872 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None,
3873 idmpauto: Optional[str] = None,
3874 idmp: Optional[tuple[str, bytes]] = None,
3875 ) -> ResponseT:
3876 """
3877 Add to a stream.
3878 name: name of the stream
3879 fields: dict of field/value pairs to insert into the stream
3880 id: Location to insert this record. By default it is appended.
3881 maxlen: truncate old stream members beyond this size.
3882 Can't be specified with minid.
3883 approximate: actual stream length may be slightly more than maxlen
3884 nomkstream: When set to true, do not make a stream
3885 minid: the minimum id in the stream to query.
3886 Can't be specified with maxlen.
3887 limit: specifies the maximum number of entries to retrieve
3888 ref_policy: optional reference policy for consumer groups when trimming:
3889 - KEEPREF (default): When trimming, preserves references in consumer groups' PEL
3890 - DELREF: When trimming, removes all references from consumer groups' PEL
3891 - ACKED: When trimming, only removes entries acknowledged by all consumer groups
3892 idmpauto: Producer ID for automatic idempotent ID calculation.
3893 Automatically calculates an idempotent ID based on entry content to prevent
3894 duplicate entries. Can only be used with id='*'. Creates an IDMP map if it
3895 doesn't exist yet. The producer ID must be unique per producer and consistent
3896 across restarts.
3897 idmp: Tuple of (producer_id, idempotent_id) for explicit idempotent ID.
3898 Uses a specific idempotent ID to prevent duplicate entries. Can only be used
3899 with id='*'. The producer ID must be unique per producer and consistent across
3900 restarts. The idempotent ID must be unique per message and per producer.
3901 Shorter idempotent IDs require less memory and allow faster processing.
3902 Creates an IDMP map if it doesn't exist yet.
3904 For more information, see https://redis.io/commands/xadd
3905 """
3906 pieces: list[EncodableT] = []
3907 if maxlen is not None and minid is not None:
3908 raise DataError("Only one of ```maxlen``` or ```minid``` may be specified")
3910 if idmpauto is not None and idmp is not None:
3911 raise DataError("Only one of ```idmpauto``` or ```idmp``` may be specified")
3913 if (idmpauto is not None or idmp is not None) and id != "*":
3914 raise DataError("IDMPAUTO and IDMP can only be used with id='*'")
3916 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
3917 raise DataError("XADD ref_policy must be one of: KEEPREF, DELREF, ACKED")
3919 if nomkstream:
3920 pieces.append(b"NOMKSTREAM")
3921 if ref_policy is not None:
3922 pieces.append(ref_policy)
3923 if idmpauto is not None:
3924 pieces.extend([b"IDMPAUTO", idmpauto])
3925 if idmp is not None:
3926 if not isinstance(idmp, tuple) or len(idmp) != 2:
3927 raise DataError(
3928 "XADD idmp must be a tuple of (producer_id, idempotent_id)"
3929 )
3930 pieces.extend([b"IDMP", idmp[0], idmp[1]])
3931 if maxlen is not None:
3932 if not isinstance(maxlen, int) or maxlen < 0:
3933 raise DataError("XADD maxlen must be non-negative integer")
3934 pieces.append(b"MAXLEN")
3935 if approximate:
3936 pieces.append(b"~")
3937 pieces.append(str(maxlen))
3938 if minid is not None:
3939 pieces.append(b"MINID")
3940 if approximate:
3941 pieces.append(b"~")
3942 pieces.append(minid)
3943 if limit is not None:
3944 pieces.extend([b"LIMIT", limit])
3945 pieces.append(id)
3946 if not isinstance(fields, dict) or len(fields) == 0:
3947 raise DataError("XADD fields must be a non-empty dict")
3948 for pair in fields.items():
3949 pieces.extend(pair)
3950 return self.execute_command("XADD", name, *pieces)
3952 def xcfgset(
3953 self,
3954 name: KeyT,
3955 idmp_duration: Optional[int] = None,
3956 idmp_maxsize: Optional[int] = None,
3957 ) -> ResponseT:
3958 """
3959 Configure the idempotency parameters for a stream's IDMP map.
3961 Sets how long Redis remembers each idempotent ID (iid) and the maximum
3962 number of iids to track. This command clears the existing IDMP map
3963 (Redis forgets all previously stored iids), but only if the configuration
3964 value actually changes.
3966 Args:
3967 name: The name of the stream.
3968 idmp_duration: How long Redis remembers each iid in seconds.
3969 Default: 100 seconds (or value set by stream-idmp-duration config).
3970 Minimum: 1 second, Maximum: 300 seconds.
3971 Redis won't forget an iid for this duration (unless maxsize is reached).
3972 Should accommodate application crash recovery time.
3973 idmp_maxsize: Maximum number of iids Redis remembers per producer ID (pid).
3974 Default: 100 iids (or value set by stream-idmp-maxsize config).
3975 Minimum: 1 iid, Maximum: 1,000,000 (1M) iids.
3976 Should be set to: mark-delay [in msec] × (messages/msec) + margin.
3977 Example: 10K msgs/sec (10 msgs/msec), 80 msec mark-delay
3978 → maxsize = 10 × 80 + margin = 1000 iids.
3980 Returns:
3981 OK on success.
3983 For more information, see https://redis.io/commands/xcfgset
3984 """
3985 if idmp_duration is None and idmp_maxsize is None:
3986 raise DataError(
3987 "XCFGSET requires at least one of idmp_duration or idmp_maxsize"
3988 )
3990 pieces: list[EncodableT] = []
3992 if idmp_duration is not None:
3993 if (
3994 not isinstance(idmp_duration, int)
3995 or idmp_duration < 1
3996 or idmp_duration > 300
3997 ):
3998 raise DataError(
3999 "XCFGSET idmp_duration must be an integer between 1 and 300"
4000 )
4001 pieces.extend([b"IDMP-DURATION", idmp_duration])
4003 if idmp_maxsize is not None:
4004 if (
4005 not isinstance(idmp_maxsize, int)
4006 or idmp_maxsize < 1
4007 or idmp_maxsize > 1000000
4008 ):
4009 raise DataError(
4010 "XCFGSET idmp_maxsize must be an integer between 1 and 1,000,000"
4011 )
4012 pieces.extend([b"IDMP-MAXSIZE", idmp_maxsize])
4014 return self.execute_command("XCFGSET", name, *pieces)
4016 def xautoclaim(
4017 self,
4018 name: KeyT,
4019 groupname: GroupT,
4020 consumername: ConsumerT,
4021 min_idle_time: int,
4022 start_id: StreamIdT = "0-0",
4023 count: Optional[int] = None,
4024 justid: bool = False,
4025 ) -> ResponseT:
4026 """
4027 Transfers ownership of pending stream entries that match the specified
4028 criteria. Conceptually, equivalent to calling XPENDING and then XCLAIM,
4029 but provides a more straightforward way to deal with message delivery
4030 failures via SCAN-like semantics.
4031 name: name of the stream.
4032 groupname: name of the consumer group.
4033 consumername: name of a consumer that claims the message.
4034 min_idle_time: filter messages that were idle less than this amount of
4035 milliseconds.
4036 start_id: filter messages with equal or greater ID.
4037 count: optional integer, upper limit of the number of entries that the
4038 command attempts to claim. Set to 100 by default.
4039 justid: optional boolean, false by default. Return just an array of IDs
4040 of messages successfully claimed, without returning the actual message
4042 For more information, see https://redis.io/commands/xautoclaim
4043 """
4044 try:
4045 if int(min_idle_time) < 0:
4046 raise DataError(
4047 "XAUTOCLAIM min_idle_time must be a nonnegative integer"
4048 )
4049 except TypeError:
4050 pass
4052 kwargs = {}
4053 pieces = [name, groupname, consumername, min_idle_time, start_id]
4055 try:
4056 if int(count) < 0:
4057 raise DataError("XPENDING count must be a integer >= 0")
4058 pieces.extend([b"COUNT", count])
4059 except TypeError:
4060 pass
4061 if justid:
4062 pieces.append(b"JUSTID")
4063 kwargs["parse_justid"] = True
4065 return self.execute_command("XAUTOCLAIM", *pieces, **kwargs)
4067 def xclaim(
4068 self,
4069 name: KeyT,
4070 groupname: GroupT,
4071 consumername: ConsumerT,
4072 min_idle_time: int,
4073 message_ids: Union[List[StreamIdT], Tuple[StreamIdT]],
4074 idle: Optional[int] = None,
4075 time: Optional[int] = None,
4076 retrycount: Optional[int] = None,
4077 force: bool = False,
4078 justid: bool = False,
4079 ) -> ResponseT:
4080 """
4081 Changes the ownership of a pending message.
4083 name: name of the stream.
4085 groupname: name of the consumer group.
4087 consumername: name of a consumer that claims the message.
4089 min_idle_time: filter messages that were idle less than this amount of
4090 milliseconds
4092 message_ids: non-empty list or tuple of message IDs to claim
4094 idle: optional. Set the idle time (last time it was delivered) of the
4095 message in ms
4097 time: optional integer. This is the same as idle but instead of a
4098 relative amount of milliseconds, it sets the idle time to a specific
4099 Unix time (in milliseconds).
4101 retrycount: optional integer. set the retry counter to the specified
4102 value. This counter is incremented every time a message is delivered
4103 again.
4105 force: optional boolean, false by default. Creates the pending message
4106 entry in the PEL even if certain specified IDs are not already in the
4107 PEL assigned to a different client.
4109 justid: optional boolean, false by default. Return just an array of IDs
4110 of messages successfully claimed, without returning the actual message
4112 For more information, see https://redis.io/commands/xclaim
4113 """
4114 if not isinstance(min_idle_time, int) or min_idle_time < 0:
4115 raise DataError("XCLAIM min_idle_time must be a non negative integer")
4116 if not isinstance(message_ids, (list, tuple)) or not message_ids:
4117 raise DataError(
4118 "XCLAIM message_ids must be a non empty list or "
4119 "tuple of message IDs to claim"
4120 )
4122 kwargs = {}
4123 pieces: list[EncodableT] = [name, groupname, consumername, str(min_idle_time)]
4124 pieces.extend(list(message_ids))
4126 if idle is not None:
4127 if not isinstance(idle, int):
4128 raise DataError("XCLAIM idle must be an integer")
4129 pieces.extend((b"IDLE", str(idle)))
4130 if time is not None:
4131 if not isinstance(time, int):
4132 raise DataError("XCLAIM time must be an integer")
4133 pieces.extend((b"TIME", str(time)))
4134 if retrycount is not None:
4135 if not isinstance(retrycount, int):
4136 raise DataError("XCLAIM retrycount must be an integer")
4137 pieces.extend((b"RETRYCOUNT", str(retrycount)))
4139 if force:
4140 if not isinstance(force, bool):
4141 raise DataError("XCLAIM force must be a boolean")
4142 pieces.append(b"FORCE")
4143 if justid:
4144 if not isinstance(justid, bool):
4145 raise DataError("XCLAIM justid must be a boolean")
4146 pieces.append(b"JUSTID")
4147 kwargs["parse_justid"] = True
4148 return self.execute_command("XCLAIM", *pieces, **kwargs)
4150 def xdel(self, name: KeyT, *ids: StreamIdT) -> ResponseT:
4151 """
4152 Deletes one or more messages from a stream.
4154 Args:
4155 name: name of the stream.
4156 *ids: message ids to delete.
4158 For more information, see https://redis.io/commands/xdel
4159 """
4160 return self.execute_command("XDEL", name, *ids)
4162 def xdelex(
4163 self,
4164 name: KeyT,
4165 *ids: StreamIdT,
4166 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF",
4167 ) -> ResponseT:
4168 """
4169 Extended version of XDEL that provides more control over how message entries
4170 are deleted concerning consumer groups.
4171 """
4172 if not ids:
4173 raise DataError("XDELEX requires at least one message ID")
4175 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
4176 raise DataError("XDELEX ref_policy must be one of: KEEPREF, DELREF, ACKED")
4178 pieces = [name, ref_policy, "IDS", len(ids)]
4179 pieces.extend(ids)
4180 return self.execute_command("XDELEX", *pieces)
4182 def xgroup_create(
4183 self,
4184 name: KeyT,
4185 groupname: GroupT,
4186 id: StreamIdT = "$",
4187 mkstream: bool = False,
4188 entries_read: Optional[int] = None,
4189 ) -> ResponseT:
4190 """
4191 Create a new consumer group associated with a stream.
4192 name: name of the stream.
4193 groupname: name of the consumer group.
4194 id: ID of the last item in the stream to consider already delivered.
4196 For more information, see https://redis.io/commands/xgroup-create
4197 """
4198 pieces: list[EncodableT] = ["XGROUP CREATE", name, groupname, id]
4199 if mkstream:
4200 pieces.append(b"MKSTREAM")
4201 if entries_read is not None:
4202 pieces.extend(["ENTRIESREAD", entries_read])
4204 return self.execute_command(*pieces)
4206 def xgroup_delconsumer(
4207 self, name: KeyT, groupname: GroupT, consumername: ConsumerT
4208 ) -> ResponseT:
4209 """
4210 Remove a specific consumer from a consumer group.
4211 Returns the number of pending messages that the consumer had before it
4212 was deleted.
4213 name: name of the stream.
4214 groupname: name of the consumer group.
4215 consumername: name of consumer to delete
4217 For more information, see https://redis.io/commands/xgroup-delconsumer
4218 """
4219 return self.execute_command("XGROUP DELCONSUMER", name, groupname, consumername)
4221 def xgroup_destroy(self, name: KeyT, groupname: GroupT) -> ResponseT:
4222 """
4223 Destroy a consumer group.
4224 name: name of the stream.
4225 groupname: name of the consumer group.
4227 For more information, see https://redis.io/commands/xgroup-destroy
4228 """
4229 return self.execute_command("XGROUP DESTROY", name, groupname)
4231 def xgroup_createconsumer(
4232 self, name: KeyT, groupname: GroupT, consumername: ConsumerT
4233 ) -> ResponseT:
4234 """
4235 Consumers in a consumer group are auto-created every time a new
4236 consumer name is mentioned by some command.
4237 They can be explicitly created by using this command.
4238 name: name of the stream.
4239 groupname: name of the consumer group.
4240 consumername: name of consumer to create.
4242 See: https://redis.io/commands/xgroup-createconsumer
4243 """
4244 return self.execute_command(
4245 "XGROUP CREATECONSUMER", name, groupname, consumername
4246 )
4248 def xgroup_setid(
4249 self,
4250 name: KeyT,
4251 groupname: GroupT,
4252 id: StreamIdT,
4253 entries_read: Optional[int] = None,
4254 ) -> ResponseT:
4255 """
4256 Set the consumer group last delivered ID to something else.
4257 name: name of the stream.
4258 groupname: name of the consumer group.
4259 id: ID of the last item in the stream to consider already delivered.
4261 For more information, see https://redis.io/commands/xgroup-setid
4262 """
4263 pieces = [name, groupname, id]
4264 if entries_read is not None:
4265 pieces.extend(["ENTRIESREAD", entries_read])
4266 return self.execute_command("XGROUP SETID", *pieces)
4268 def xinfo_consumers(self, name: KeyT, groupname: GroupT) -> ResponseT:
4269 """
4270 Returns general information about the consumers in the group.
4271 name: name of the stream.
4272 groupname: name of the consumer group.
4274 For more information, see https://redis.io/commands/xinfo-consumers
4275 """
4276 return self.execute_command("XINFO CONSUMERS", name, groupname)
4278 def xinfo_groups(self, name: KeyT) -> ResponseT:
4279 """
4280 Returns general information about the consumer groups of the stream.
4281 name: name of the stream.
4283 For more information, see https://redis.io/commands/xinfo-groups
4284 """
4285 return self.execute_command("XINFO GROUPS", name)
4287 def xinfo_stream(self, name: KeyT, full: bool = False) -> ResponseT:
4288 """
4289 Returns general information about the stream.
4290 name: name of the stream.
4291 full: optional boolean, false by default. Return full summary
4293 For more information, see https://redis.io/commands/xinfo-stream
4294 """
4295 pieces = [name]
4296 options = {}
4297 if full:
4298 pieces.append(b"FULL")
4299 options = {"full": full}
4300 return self.execute_command("XINFO STREAM", *pieces, **options)
4302 def xlen(self, name: KeyT) -> ResponseT:
4303 """
4304 Returns the number of elements in a given stream.
4306 For more information, see https://redis.io/commands/xlen
4307 """
4308 return self.execute_command("XLEN", name, keys=[name])
4310 def xpending(self, name: KeyT, groupname: GroupT) -> ResponseT:
4311 """
4312 Returns information about pending messages of a group.
4313 name: name of the stream.
4314 groupname: name of the consumer group.
4316 For more information, see https://redis.io/commands/xpending
4317 """
4318 return self.execute_command("XPENDING", name, groupname, keys=[name])
4320 def xpending_range(
4321 self,
4322 name: KeyT,
4323 groupname: GroupT,
4324 min: StreamIdT,
4325 max: StreamIdT,
4326 count: int,
4327 consumername: Union[ConsumerT, None] = None,
4328 idle: Optional[int] = None,
4329 ) -> ResponseT:
4330 """
4331 Returns information about pending messages, in a range.
4333 name: name of the stream.
4334 groupname: name of the consumer group.
4335 idle: available from version 6.2. filter entries by their
4336 idle-time, given in milliseconds (optional).
4337 min: minimum stream ID.
4338 max: maximum stream ID.
4339 count: number of messages to return
4340 consumername: name of a consumer to filter by (optional).
4341 """
4342 if {min, max, count} == {None}:
4343 if idle is not None or consumername is not None:
4344 raise DataError(
4345 "if XPENDING is provided with idle time"
4346 " or consumername, it must be provided"
4347 " with min, max and count parameters"
4348 )
4349 return self.xpending(name, groupname)
4351 pieces = [name, groupname]
4352 if min is None or max is None or count is None:
4353 raise DataError(
4354 "XPENDING must be provided with min, max "
4355 "and count parameters, or none of them."
4356 )
4357 # idle
4358 try:
4359 if int(idle) < 0:
4360 raise DataError("XPENDING idle must be a integer >= 0")
4361 pieces.extend(["IDLE", idle])
4362 except TypeError:
4363 pass
4364 # count
4365 try:
4366 if int(count) < 0:
4367 raise DataError("XPENDING count must be a integer >= 0")
4368 pieces.extend([min, max, count])
4369 except TypeError:
4370 pass
4371 # consumername
4372 if consumername:
4373 pieces.append(consumername)
4375 return self.execute_command("XPENDING", *pieces, parse_detail=True)
4377 def xrange(
4378 self,
4379 name: KeyT,
4380 min: StreamIdT = "-",
4381 max: StreamIdT = "+",
4382 count: Optional[int] = None,
4383 ) -> ResponseT:
4384 """
4385 Read stream values within an interval.
4387 name: name of the stream.
4389 start: first stream ID. defaults to '-',
4390 meaning the earliest available.
4392 finish: last stream ID. defaults to '+',
4393 meaning the latest available.
4395 count: if set, only return this many items, beginning with the
4396 earliest available.
4398 For more information, see https://redis.io/commands/xrange
4399 """
4400 pieces = [min, max]
4401 if count is not None:
4402 if not isinstance(count, int) or count < 1:
4403 raise DataError("XRANGE count must be a positive integer")
4404 pieces.append(b"COUNT")
4405 pieces.append(str(count))
4407 return self.execute_command("XRANGE", name, *pieces, keys=[name])
4409 def xread(
4410 self,
4411 streams: Dict[KeyT, StreamIdT],
4412 count: Optional[int] = None,
4413 block: Optional[int] = None,
4414 ) -> ResponseT:
4415 """
4416 Block and monitor multiple streams for new data.
4418 streams: a dict of stream names to stream IDs, where
4419 IDs indicate the last ID already seen.
4421 count: if set, only return this many items, beginning with the
4422 earliest available.
4424 block: number of milliseconds to wait, if nothing already present.
4426 For more information, see https://redis.io/commands/xread
4427 """
4428 pieces = []
4429 if block is not None:
4430 if not isinstance(block, int) or block < 0:
4431 raise DataError("XREAD block must be a non-negative integer")
4432 pieces.append(b"BLOCK")
4433 pieces.append(str(block))
4434 if count is not None:
4435 if not isinstance(count, int) or count < 1:
4436 raise DataError("XREAD count must be a positive integer")
4437 pieces.append(b"COUNT")
4438 pieces.append(str(count))
4439 if not isinstance(streams, dict) or len(streams) == 0:
4440 raise DataError("XREAD streams must be a non empty dict")
4441 pieces.append(b"STREAMS")
4442 keys, values = zip(*streams.items())
4443 pieces.extend(keys)
4444 pieces.extend(values)
4445 return self.execute_command("XREAD", *pieces, keys=keys)
4447 def xreadgroup(
4448 self,
4449 groupname: str,
4450 consumername: str,
4451 streams: Dict[KeyT, StreamIdT],
4452 count: Optional[int] = None,
4453 block: Optional[int] = None,
4454 noack: bool = False,
4455 claim_min_idle_time: Optional[int] = None,
4456 ) -> ResponseT:
4457 """
4458 Read from a stream via a consumer group.
4460 groupname: name of the consumer group.
4462 consumername: name of the requesting consumer.
4464 streams: a dict of stream names to stream IDs, where
4465 IDs indicate the last ID already seen.
4467 count: if set, only return this many items, beginning with the
4468 earliest available.
4470 block: number of milliseconds to wait, if nothing already present.
4471 noack: do not add messages to the PEL
4473 claim_min_idle_time: accepts an integer type and represents a
4474 time interval in milliseconds
4476 For more information, see https://redis.io/commands/xreadgroup
4477 """
4478 options = {}
4479 pieces: list[EncodableT] = [b"GROUP", groupname, consumername]
4480 if count is not None:
4481 if not isinstance(count, int) or count < 1:
4482 raise DataError("XREADGROUP count must be a positive integer")
4483 pieces.append(b"COUNT")
4484 pieces.append(str(count))
4485 if block is not None:
4486 if not isinstance(block, int) or block < 0:
4487 raise DataError("XREADGROUP block must be a non-negative integer")
4488 pieces.append(b"BLOCK")
4489 pieces.append(str(block))
4490 if noack:
4491 pieces.append(b"NOACK")
4492 if claim_min_idle_time is not None:
4493 if not isinstance(claim_min_idle_time, int) or claim_min_idle_time < 0:
4494 raise DataError(
4495 "XREADGROUP claim_min_idle_time must be a non-negative integer"
4496 )
4497 pieces.append(b"CLAIM")
4498 pieces.append(claim_min_idle_time)
4499 options["claim_min_idle_time"] = claim_min_idle_time
4500 if not isinstance(streams, dict) or len(streams) == 0:
4501 raise DataError("XREADGROUP streams must be a non empty dict")
4502 pieces.append(b"STREAMS")
4503 pieces.extend(streams.keys())
4504 pieces.extend(streams.values())
4505 return self.execute_command("XREADGROUP", *pieces, **options)
4507 def xrevrange(
4508 self,
4509 name: KeyT,
4510 max: StreamIdT = "+",
4511 min: StreamIdT = "-",
4512 count: Optional[int] = None,
4513 ) -> ResponseT:
4514 """
4515 Read stream values within an interval, in reverse order.
4517 name: name of the stream
4519 start: first stream ID. defaults to '+',
4520 meaning the latest available.
4522 finish: last stream ID. defaults to '-',
4523 meaning the earliest available.
4525 count: if set, only return this many items, beginning with the
4526 latest available.
4528 For more information, see https://redis.io/commands/xrevrange
4529 """
4530 pieces: list[EncodableT] = [max, min]
4531 if count is not None:
4532 if not isinstance(count, int) or count < 1:
4533 raise DataError("XREVRANGE count must be a positive integer")
4534 pieces.append(b"COUNT")
4535 pieces.append(str(count))
4537 return self.execute_command("XREVRANGE", name, *pieces, keys=[name])
4539 def xtrim(
4540 self,
4541 name: KeyT,
4542 maxlen: Optional[int] = None,
4543 approximate: bool = True,
4544 minid: Union[StreamIdT, None] = None,
4545 limit: Optional[int] = None,
4546 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None,
4547 ) -> ResponseT:
4548 """
4549 Trims old messages from a stream.
4550 name: name of the stream.
4551 maxlen: truncate old stream messages beyond this size
4552 Can't be specified with minid.
4553 approximate: actual stream length may be slightly more than maxlen
4554 minid: the minimum id in the stream to query
4555 Can't be specified with maxlen.
4556 limit: specifies the maximum number of entries to retrieve
4557 ref_policy: optional reference policy for consumer groups:
4558 - KEEPREF (default): Trims entries but preserves references in consumer groups' PEL
4559 - DELREF: Trims entries and removes all references from consumer groups' PEL
4560 - ACKED: Only trims entries that were read and acknowledged by all consumer groups
4562 For more information, see https://redis.io/commands/xtrim
4563 """
4564 pieces: list[EncodableT] = []
4565 if maxlen is not None and minid is not None:
4566 raise DataError("Only one of ``maxlen`` or ``minid`` may be specified")
4568 if maxlen is None and minid is None:
4569 raise DataError("One of ``maxlen`` or ``minid`` must be specified")
4571 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
4572 raise DataError("XTRIM ref_policy must be one of: KEEPREF, DELREF, ACKED")
4574 if maxlen is not None:
4575 pieces.append(b"MAXLEN")
4576 if minid is not None:
4577 pieces.append(b"MINID")
4578 if approximate:
4579 pieces.append(b"~")
4580 if maxlen is not None:
4581 pieces.append(maxlen)
4582 if minid is not None:
4583 pieces.append(minid)
4584 if limit is not None:
4585 pieces.append(b"LIMIT")
4586 pieces.append(limit)
4587 if ref_policy is not None:
4588 pieces.append(ref_policy)
4590 return self.execute_command("XTRIM", name, *pieces)
4593AsyncStreamCommands = StreamCommands
4596class SortedSetCommands(CommandsProtocol):
4597 """
4598 Redis commands for Sorted Sets data type.
4599 see: https://redis.io/topics/data-types-intro#redis-sorted-sets
4600 """
4602 def zadd(
4603 self,
4604 name: KeyT,
4605 mapping: Mapping[AnyKeyT, EncodableT],
4606 nx: bool = False,
4607 xx: bool = False,
4608 ch: bool = False,
4609 incr: bool = False,
4610 gt: bool = False,
4611 lt: bool = False,
4612 ) -> ResponseT:
4613 """
4614 Set any number of element-name, score pairs to the key ``name``. Pairs
4615 are specified as a dict of element-names keys to score values.
4617 ``nx`` forces ZADD to only create new elements and not to update
4618 scores for elements that already exist.
4620 ``xx`` forces ZADD to only update scores of elements that already
4621 exist. New elements will not be added.
4623 ``ch`` modifies the return value to be the numbers of elements changed.
4624 Changed elements include new elements that were added and elements
4625 whose scores changed.
4627 ``incr`` modifies ZADD to behave like ZINCRBY. In this mode only a
4628 single element/score pair can be specified and the score is the amount
4629 the existing score will be incremented by. When using this mode the
4630 return value of ZADD will be the new score of the element.
4632 ``lt`` only updates existing elements if the new score is less than
4633 the current score. This flag doesn't prevent adding new elements.
4635 ``gt`` only updates existing elements if the new score is greater than
4636 the current score. This flag doesn't prevent adding new elements.
4638 The return value of ZADD varies based on the mode specified. With no
4639 options, ZADD returns the number of new elements added to the sorted
4640 set.
4642 ``nx``, ``lt``, and ``gt`` are mutually exclusive options.
4644 See: https://redis.io/commands/ZADD
4645 """
4646 if not mapping:
4647 raise DataError("ZADD requires at least one element/score pair")
4648 if nx and xx:
4649 raise DataError("ZADD allows either 'nx' or 'xx', not both")
4650 if gt and lt:
4651 raise DataError("ZADD allows either 'gt' or 'lt', not both")
4652 if incr and len(mapping) != 1:
4653 raise DataError(
4654 "ZADD option 'incr' only works when passing a single element/score pair"
4655 )
4656 if nx and (gt or lt):
4657 raise DataError("Only one of 'nx', 'lt', or 'gr' may be defined.")
4659 pieces: list[EncodableT] = []
4660 options = {}
4661 if nx:
4662 pieces.append(b"NX")
4663 if xx:
4664 pieces.append(b"XX")
4665 if ch:
4666 pieces.append(b"CH")
4667 if incr:
4668 pieces.append(b"INCR")
4669 options["as_score"] = True
4670 if gt:
4671 pieces.append(b"GT")
4672 if lt:
4673 pieces.append(b"LT")
4674 for pair in mapping.items():
4675 pieces.append(pair[1])
4676 pieces.append(pair[0])
4677 return self.execute_command("ZADD", name, *pieces, **options)
4679 def zcard(self, name: KeyT) -> ResponseT:
4680 """
4681 Return the number of elements in the sorted set ``name``
4683 For more information, see https://redis.io/commands/zcard
4684 """
4685 return self.execute_command("ZCARD", name, keys=[name])
4687 def zcount(self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT) -> ResponseT:
4688 """
4689 Returns the number of elements in the sorted set at key ``name`` with
4690 a score between ``min`` and ``max``.
4692 For more information, see https://redis.io/commands/zcount
4693 """
4694 return self.execute_command("ZCOUNT", name, min, max, keys=[name])
4696 def zdiff(self, keys: KeysT, withscores: bool = False) -> ResponseT:
4697 """
4698 Returns the difference between the first and all successive input
4699 sorted sets provided in ``keys``.
4701 For more information, see https://redis.io/commands/zdiff
4702 """
4703 pieces = [len(keys), *keys]
4704 if withscores:
4705 pieces.append("WITHSCORES")
4706 return self.execute_command("ZDIFF", *pieces, keys=keys)
4708 def zdiffstore(self, dest: KeyT, keys: KeysT) -> ResponseT:
4709 """
4710 Computes the difference between the first and all successive input
4711 sorted sets provided in ``keys`` and stores the result in ``dest``.
4713 For more information, see https://redis.io/commands/zdiffstore
4714 """
4715 pieces = [len(keys), *keys]
4716 return self.execute_command("ZDIFFSTORE", dest, *pieces)
4718 def zincrby(self, name: KeyT, amount: float, value: EncodableT) -> ResponseT:
4719 """
4720 Increment the score of ``value`` in sorted set ``name`` by ``amount``
4722 For more information, see https://redis.io/commands/zincrby
4723 """
4724 return self.execute_command("ZINCRBY", name, amount, value)
4726 def zinter(
4727 self, keys: KeysT, aggregate: Optional[str] = None, withscores: bool = False
4728 ) -> ResponseT:
4729 """
4730 Return the intersect of multiple sorted sets specified by ``keys``.
4731 With the ``aggregate`` option, it is possible to specify how the
4732 results of the union are aggregated. This option defaults to SUM,
4733 where the score of an element is summed across the inputs where it
4734 exists. When this option is set to either MIN or MAX, the resulting
4735 set will contain the minimum or maximum score of an element across
4736 the inputs where it exists.
4738 For more information, see https://redis.io/commands/zinter
4739 """
4740 return self._zaggregate("ZINTER", None, keys, aggregate, withscores=withscores)
4742 def zinterstore(
4743 self,
4744 dest: KeyT,
4745 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
4746 aggregate: Optional[str] = None,
4747 ) -> ResponseT:
4748 """
4749 Intersect multiple sorted sets specified by ``keys`` into a new
4750 sorted set, ``dest``. Scores in the destination will be aggregated
4751 based on the ``aggregate``. This option defaults to SUM, where the
4752 score of an element is summed across the inputs where it exists.
4753 When this option is set to either MIN or MAX, the resulting set will
4754 contain the minimum or maximum score of an element across the inputs
4755 where it exists.
4757 For more information, see https://redis.io/commands/zinterstore
4758 """
4759 return self._zaggregate("ZINTERSTORE", dest, keys, aggregate)
4761 def zintercard(
4762 self, numkeys: int, keys: List[str], limit: int = 0
4763 ) -> Union[Awaitable[int], int]:
4764 """
4765 Return the cardinality of the intersect of multiple sorted sets
4766 specified by ``keys``.
4767 When LIMIT provided (defaults to 0 and means unlimited), if the intersection
4768 cardinality reaches limit partway through the computation, the algorithm will
4769 exit and yield limit as the cardinality
4771 For more information, see https://redis.io/commands/zintercard
4772 """
4773 args = [numkeys, *keys, "LIMIT", limit]
4774 return self.execute_command("ZINTERCARD", *args, keys=keys)
4776 def zlexcount(self, name, min, max):
4777 """
4778 Return the number of items in the sorted set ``name`` between the
4779 lexicographical range ``min`` and ``max``.
4781 For more information, see https://redis.io/commands/zlexcount
4782 """
4783 return self.execute_command("ZLEXCOUNT", name, min, max, keys=[name])
4785 def zpopmax(self, name: KeyT, count: Optional[int] = None) -> ResponseT:
4786 """
4787 Remove and return up to ``count`` members with the highest scores
4788 from the sorted set ``name``.
4790 For more information, see https://redis.io/commands/zpopmax
4791 """
4792 args = (count is not None) and [count] or []
4793 options = {"withscores": True}
4794 return self.execute_command("ZPOPMAX", name, *args, **options)
4796 def zpopmin(self, name: KeyT, count: Optional[int] = None) -> ResponseT:
4797 """
4798 Remove and return up to ``count`` members with the lowest scores
4799 from the sorted set ``name``.
4801 For more information, see https://redis.io/commands/zpopmin
4802 """
4803 args = (count is not None) and [count] or []
4804 options = {"withscores": True}
4805 return self.execute_command("ZPOPMIN", name, *args, **options)
4807 def zrandmember(
4808 self, key: KeyT, count: Optional[int] = None, withscores: bool = False
4809 ) -> ResponseT:
4810 """
4811 Return a random element from the sorted set value stored at key.
4813 ``count`` if the argument is positive, return an array of distinct
4814 fields. If called with a negative count, the behavior changes and
4815 the command is allowed to return the same field multiple times.
4816 In this case, the number of returned fields is the absolute value
4817 of the specified count.
4819 ``withscores`` The optional WITHSCORES modifier changes the reply so it
4820 includes the respective scores of the randomly selected elements from
4821 the sorted set.
4823 For more information, see https://redis.io/commands/zrandmember
4824 """
4825 params = []
4826 if count is not None:
4827 params.append(count)
4828 if withscores:
4829 params.append("WITHSCORES")
4831 return self.execute_command("ZRANDMEMBER", key, *params)
4833 def bzpopmax(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT:
4834 """
4835 ZPOPMAX a value off of the first non-empty sorted set
4836 named in the ``keys`` list.
4838 If none of the sorted sets in ``keys`` has a value to ZPOPMAX,
4839 then block for ``timeout`` seconds, or until a member gets added
4840 to one of the sorted sets.
4842 If timeout is 0, then block indefinitely.
4844 For more information, see https://redis.io/commands/bzpopmax
4845 """
4846 if timeout is None:
4847 timeout = 0
4848 keys = list_or_args(keys, None)
4849 keys.append(timeout)
4850 return self.execute_command("BZPOPMAX", *keys)
4852 def bzpopmin(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT:
4853 """
4854 ZPOPMIN a value off of the first non-empty sorted set
4855 named in the ``keys`` list.
4857 If none of the sorted sets in ``keys`` has a value to ZPOPMIN,
4858 then block for ``timeout`` seconds, or until a member gets added
4859 to one of the sorted sets.
4861 If timeout is 0, then block indefinitely.
4863 For more information, see https://redis.io/commands/bzpopmin
4864 """
4865 if timeout is None:
4866 timeout = 0
4867 keys: list[EncodableT] = list_or_args(keys, None)
4868 keys.append(timeout)
4869 return self.execute_command("BZPOPMIN", *keys)
4871 def zmpop(
4872 self,
4873 num_keys: int,
4874 keys: List[str],
4875 min: Optional[bool] = False,
4876 max: Optional[bool] = False,
4877 count: Optional[int] = 1,
4878 ) -> Union[Awaitable[list], list]:
4879 """
4880 Pop ``count`` values (default 1) off of the first non-empty sorted set
4881 named in the ``keys`` list.
4882 For more information, see https://redis.io/commands/zmpop
4883 """
4884 args = [num_keys] + keys
4885 if (min and max) or (not min and not max):
4886 raise DataError
4887 elif min:
4888 args.append("MIN")
4889 else:
4890 args.append("MAX")
4891 if count != 1:
4892 args.extend(["COUNT", count])
4894 return self.execute_command("ZMPOP", *args)
4896 def bzmpop(
4897 self,
4898 timeout: float,
4899 numkeys: int,
4900 keys: List[str],
4901 min: Optional[bool] = False,
4902 max: Optional[bool] = False,
4903 count: Optional[int] = 1,
4904 ) -> Optional[list]:
4905 """
4906 Pop ``count`` values (default 1) off of the first non-empty sorted set
4907 named in the ``keys`` list.
4909 If none of the sorted sets in ``keys`` has a value to pop,
4910 then block for ``timeout`` seconds, or until a member gets added
4911 to one of the sorted sets.
4913 If timeout is 0, then block indefinitely.
4915 For more information, see https://redis.io/commands/bzmpop
4916 """
4917 args = [timeout, numkeys, *keys]
4918 if (min and max) or (not min and not max):
4919 raise DataError("Either min or max, but not both must be set")
4920 elif min:
4921 args.append("MIN")
4922 else:
4923 args.append("MAX")
4924 args.extend(["COUNT", count])
4926 return self.execute_command("BZMPOP", *args)
4928 def _zrange(
4929 self,
4930 command,
4931 dest: Union[KeyT, None],
4932 name: KeyT,
4933 start: EncodableT,
4934 end: EncodableT,
4935 desc: bool = False,
4936 byscore: bool = False,
4937 bylex: bool = False,
4938 withscores: bool = False,
4939 score_cast_func: Union[type, Callable, None] = float,
4940 offset: Optional[int] = None,
4941 num: Optional[int] = None,
4942 ) -> ResponseT:
4943 if byscore and bylex:
4944 raise DataError("``byscore`` and ``bylex`` can not be specified together.")
4945 if (offset is not None and num is None) or (num is not None and offset is None):
4946 raise DataError("``offset`` and ``num`` must both be specified.")
4947 if bylex and withscores:
4948 raise DataError(
4949 "``withscores`` not supported in combination with ``bylex``."
4950 )
4951 pieces = [command]
4952 if dest:
4953 pieces.append(dest)
4954 pieces.extend([name, start, end])
4955 if byscore:
4956 pieces.append("BYSCORE")
4957 if bylex:
4958 pieces.append("BYLEX")
4959 if desc:
4960 pieces.append("REV")
4961 if offset is not None and num is not None:
4962 pieces.extend(["LIMIT", offset, num])
4963 if withscores:
4964 pieces.append("WITHSCORES")
4965 options = {"withscores": withscores, "score_cast_func": score_cast_func}
4966 options["keys"] = [name]
4967 return self.execute_command(*pieces, **options)
4969 def zrange(
4970 self,
4971 name: KeyT,
4972 start: EncodableT,
4973 end: EncodableT,
4974 desc: bool = False,
4975 withscores: bool = False,
4976 score_cast_func: Union[type, Callable] = float,
4977 byscore: bool = False,
4978 bylex: bool = False,
4979 offset: Optional[int] = None,
4980 num: Optional[int] = None,
4981 ) -> ResponseT:
4982 """
4983 Return a range of values from sorted set ``name`` between
4984 ``start`` and ``end`` sorted in ascending order.
4986 ``start`` and ``end`` can be negative, indicating the end of the range.
4988 ``desc`` a boolean indicating whether to sort the results in reversed
4989 order.
4991 ``withscores`` indicates to return the scores along with the values.
4992 The return type is a list of (value, score) pairs.
4994 ``score_cast_func`` a callable used to cast the score return value.
4996 ``byscore`` when set to True, returns the range of elements from the
4997 sorted set having scores equal or between ``start`` and ``end``.
4999 ``bylex`` when set to True, returns the range of elements from the
5000 sorted set between the ``start`` and ``end`` lexicographical closed
5001 range intervals.
5002 Valid ``start`` and ``end`` must start with ( or [, in order to specify
5003 whether the range interval is exclusive or inclusive, respectively.
5005 ``offset`` and ``num`` are specified, then return a slice of the range.
5006 Can't be provided when using ``bylex``.
5008 For more information, see https://redis.io/commands/zrange
5009 """
5010 # Need to support ``desc`` also when using old redis version
5011 # because it was supported in 3.5.3 (of redis-py)
5012 if not byscore and not bylex and (offset is None and num is None) and desc:
5013 return self.zrevrange(name, start, end, withscores, score_cast_func)
5015 return self._zrange(
5016 "ZRANGE",
5017 None,
5018 name,
5019 start,
5020 end,
5021 desc,
5022 byscore,
5023 bylex,
5024 withscores,
5025 score_cast_func,
5026 offset,
5027 num,
5028 )
5030 def zrevrange(
5031 self,
5032 name: KeyT,
5033 start: int,
5034 end: int,
5035 withscores: bool = False,
5036 score_cast_func: Union[type, Callable] = float,
5037 ) -> ResponseT:
5038 """
5039 Return a range of values from sorted set ``name`` between
5040 ``start`` and ``end`` sorted in descending order.
5042 ``start`` and ``end`` can be negative, indicating the end of the range.
5044 ``withscores`` indicates to return the scores along with the values
5045 The return type is a list of (value, score) pairs
5047 ``score_cast_func`` a callable used to cast the score return value
5049 For more information, see https://redis.io/commands/zrevrange
5050 """
5051 pieces = ["ZREVRANGE", name, start, end]
5052 if withscores:
5053 pieces.append(b"WITHSCORES")
5054 options = {"withscores": withscores, "score_cast_func": score_cast_func}
5055 options["keys"] = name
5056 return self.execute_command(*pieces, **options)
5058 def zrangestore(
5059 self,
5060 dest: KeyT,
5061 name: KeyT,
5062 start: EncodableT,
5063 end: EncodableT,
5064 byscore: bool = False,
5065 bylex: bool = False,
5066 desc: bool = False,
5067 offset: Optional[int] = None,
5068 num: Optional[int] = None,
5069 ) -> ResponseT:
5070 """
5071 Stores in ``dest`` the result of a range of values from sorted set
5072 ``name`` between ``start`` and ``end`` sorted in ascending order.
5074 ``start`` and ``end`` can be negative, indicating the end of the range.
5076 ``byscore`` when set to True, returns the range of elements from the
5077 sorted set having scores equal or between ``start`` and ``end``.
5079 ``bylex`` when set to True, returns the range of elements from the
5080 sorted set between the ``start`` and ``end`` lexicographical closed
5081 range intervals.
5082 Valid ``start`` and ``end`` must start with ( or [, in order to specify
5083 whether the range interval is exclusive or inclusive, respectively.
5085 ``desc`` a boolean indicating whether to sort the results in reversed
5086 order.
5088 ``offset`` and ``num`` are specified, then return a slice of the range.
5089 Can't be provided when using ``bylex``.
5091 For more information, see https://redis.io/commands/zrangestore
5092 """
5093 return self._zrange(
5094 "ZRANGESTORE",
5095 dest,
5096 name,
5097 start,
5098 end,
5099 desc,
5100 byscore,
5101 bylex,
5102 False,
5103 None,
5104 offset,
5105 num,
5106 )
5108 def zrangebylex(
5109 self,
5110 name: KeyT,
5111 min: EncodableT,
5112 max: EncodableT,
5113 start: Optional[int] = None,
5114 num: Optional[int] = None,
5115 ) -> ResponseT:
5116 """
5117 Return the lexicographical range of values from sorted set ``name``
5118 between ``min`` and ``max``.
5120 If ``start`` and ``num`` are specified, then return a slice of the
5121 range.
5123 For more information, see https://redis.io/commands/zrangebylex
5124 """
5125 if (start is not None and num is None) or (num is not None and start is None):
5126 raise DataError("``start`` and ``num`` must both be specified")
5127 pieces = ["ZRANGEBYLEX", name, min, max]
5128 if start is not None and num is not None:
5129 pieces.extend([b"LIMIT", start, num])
5130 return self.execute_command(*pieces, keys=[name])
5132 def zrevrangebylex(
5133 self,
5134 name: KeyT,
5135 max: EncodableT,
5136 min: EncodableT,
5137 start: Optional[int] = None,
5138 num: Optional[int] = None,
5139 ) -> ResponseT:
5140 """
5141 Return the reversed lexicographical range of values from sorted set
5142 ``name`` between ``max`` and ``min``.
5144 If ``start`` and ``num`` are specified, then return a slice of the
5145 range.
5147 For more information, see https://redis.io/commands/zrevrangebylex
5148 """
5149 if (start is not None and num is None) or (num is not None and start is None):
5150 raise DataError("``start`` and ``num`` must both be specified")
5151 pieces = ["ZREVRANGEBYLEX", name, max, min]
5152 if start is not None and num is not None:
5153 pieces.extend(["LIMIT", start, num])
5154 return self.execute_command(*pieces, keys=[name])
5156 def zrangebyscore(
5157 self,
5158 name: KeyT,
5159 min: ZScoreBoundT,
5160 max: ZScoreBoundT,
5161 start: Optional[int] = None,
5162 num: Optional[int] = None,
5163 withscores: bool = False,
5164 score_cast_func: Union[type, Callable] = float,
5165 ) -> ResponseT:
5166 """
5167 Return a range of values from the sorted set ``name`` with scores
5168 between ``min`` and ``max``.
5170 If ``start`` and ``num`` are specified, then return a slice
5171 of the range.
5173 ``withscores`` indicates to return the scores along with the values.
5174 The return type is a list of (value, score) pairs
5176 `score_cast_func`` a callable used to cast the score return value
5178 For more information, see https://redis.io/commands/zrangebyscore
5179 """
5180 if (start is not None and num is None) or (num is not None and start is None):
5181 raise DataError("``start`` and ``num`` must both be specified")
5182 pieces = ["ZRANGEBYSCORE", name, min, max]
5183 if start is not None and num is not None:
5184 pieces.extend(["LIMIT", start, num])
5185 if withscores:
5186 pieces.append("WITHSCORES")
5187 options = {"withscores": withscores, "score_cast_func": score_cast_func}
5188 options["keys"] = [name]
5189 return self.execute_command(*pieces, **options)
5191 def zrevrangebyscore(
5192 self,
5193 name: KeyT,
5194 max: ZScoreBoundT,
5195 min: ZScoreBoundT,
5196 start: Optional[int] = None,
5197 num: Optional[int] = None,
5198 withscores: bool = False,
5199 score_cast_func: Union[type, Callable] = float,
5200 ):
5201 """
5202 Return a range of values from the sorted set ``name`` with scores
5203 between ``min`` and ``max`` in descending order.
5205 If ``start`` and ``num`` are specified, then return a slice
5206 of the range.
5208 ``withscores`` indicates to return the scores along with the values.
5209 The return type is a list of (value, score) pairs
5211 ``score_cast_func`` a callable used to cast the score return value
5213 For more information, see https://redis.io/commands/zrevrangebyscore
5214 """
5215 if (start is not None and num is None) or (num is not None and start is None):
5216 raise DataError("``start`` and ``num`` must both be specified")
5217 pieces = ["ZREVRANGEBYSCORE", name, max, min]
5218 if start is not None and num is not None:
5219 pieces.extend(["LIMIT", start, num])
5220 if withscores:
5221 pieces.append("WITHSCORES")
5222 options = {"withscores": withscores, "score_cast_func": score_cast_func}
5223 options["keys"] = [name]
5224 return self.execute_command(*pieces, **options)
5226 def zrank(
5227 self,
5228 name: KeyT,
5229 value: EncodableT,
5230 withscore: bool = False,
5231 score_cast_func: Union[type, Callable] = float,
5232 ) -> ResponseT:
5233 """
5234 Returns a 0-based value indicating the rank of ``value`` in sorted set
5235 ``name``.
5236 The optional WITHSCORE argument supplements the command's
5237 reply with the score of the element returned.
5239 ``score_cast_func`` a callable used to cast the score return value
5241 For more information, see https://redis.io/commands/zrank
5242 """
5243 pieces = ["ZRANK", name, value]
5244 if withscore:
5245 pieces.append("WITHSCORE")
5247 options = {"withscore": withscore, "score_cast_func": score_cast_func}
5249 return self.execute_command(*pieces, **options)
5251 def zrem(self, name: KeyT, *values: FieldT) -> ResponseT:
5252 """
5253 Remove member ``values`` from sorted set ``name``
5255 For more information, see https://redis.io/commands/zrem
5256 """
5257 return self.execute_command("ZREM", name, *values)
5259 def zremrangebylex(self, name: KeyT, min: EncodableT, max: EncodableT) -> ResponseT:
5260 """
5261 Remove all elements in the sorted set ``name`` between the
5262 lexicographical range specified by ``min`` and ``max``.
5264 Returns the number of elements removed.
5266 For more information, see https://redis.io/commands/zremrangebylex
5267 """
5268 return self.execute_command("ZREMRANGEBYLEX", name, min, max)
5270 def zremrangebyrank(self, name: KeyT, min: int, max: int) -> ResponseT:
5271 """
5272 Remove all elements in the sorted set ``name`` with ranks between
5273 ``min`` and ``max``. Values are 0-based, ordered from smallest score
5274 to largest. Values can be negative indicating the highest scores.
5275 Returns the number of elements removed
5277 For more information, see https://redis.io/commands/zremrangebyrank
5278 """
5279 return self.execute_command("ZREMRANGEBYRANK", name, min, max)
5281 def zremrangebyscore(
5282 self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT
5283 ) -> ResponseT:
5284 """
5285 Remove all elements in the sorted set ``name`` with scores
5286 between ``min`` and ``max``. Returns the number of elements removed.
5288 For more information, see https://redis.io/commands/zremrangebyscore
5289 """
5290 return self.execute_command("ZREMRANGEBYSCORE", name, min, max)
5292 def zrevrank(
5293 self,
5294 name: KeyT,
5295 value: EncodableT,
5296 withscore: bool = False,
5297 score_cast_func: Union[type, Callable] = float,
5298 ) -> ResponseT:
5299 """
5300 Returns a 0-based value indicating the descending rank of
5301 ``value`` in sorted set ``name``.
5302 The optional ``withscore`` argument supplements the command's
5303 reply with the score of the element returned.
5305 ``score_cast_func`` a callable used to cast the score return value
5307 For more information, see https://redis.io/commands/zrevrank
5308 """
5309 pieces = ["ZREVRANK", name, value]
5310 if withscore:
5311 pieces.append("WITHSCORE")
5313 options = {"withscore": withscore, "score_cast_func": score_cast_func}
5315 return self.execute_command(*pieces, **options)
5317 def zscore(self, name: KeyT, value: EncodableT) -> ResponseT:
5318 """
5319 Return the score of element ``value`` in sorted set ``name``
5321 For more information, see https://redis.io/commands/zscore
5322 """
5323 return self.execute_command("ZSCORE", name, value, keys=[name])
5325 def zunion(
5326 self,
5327 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
5328 aggregate: Optional[str] = None,
5329 withscores: bool = False,
5330 score_cast_func: Union[type, Callable] = float,
5331 ) -> ResponseT:
5332 """
5333 Return the union of multiple sorted sets specified by ``keys``.
5334 ``keys`` can be provided as dictionary of keys and their weights.
5335 Scores will be aggregated based on the ``aggregate``, or SUM if
5336 none is provided.
5338 ``score_cast_func`` a callable used to cast the score return value
5340 For more information, see https://redis.io/commands/zunion
5341 """
5342 return self._zaggregate(
5343 "ZUNION",
5344 None,
5345 keys,
5346 aggregate,
5347 withscores=withscores,
5348 score_cast_func=score_cast_func,
5349 )
5351 def zunionstore(
5352 self,
5353 dest: KeyT,
5354 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
5355 aggregate: Optional[str] = None,
5356 ) -> ResponseT:
5357 """
5358 Union multiple sorted sets specified by ``keys`` into
5359 a new sorted set, ``dest``. Scores in the destination will be
5360 aggregated based on the ``aggregate``, or SUM if none is provided.
5362 For more information, see https://redis.io/commands/zunionstore
5363 """
5364 return self._zaggregate("ZUNIONSTORE", dest, keys, aggregate)
5366 def zmscore(self, key: KeyT, members: List[str]) -> ResponseT:
5367 """
5368 Returns the scores associated with the specified members
5369 in the sorted set stored at key.
5370 ``members`` should be a list of the member name.
5371 Return type is a list of score.
5372 If the member does not exist, a None will be returned
5373 in corresponding position.
5375 For more information, see https://redis.io/commands/zmscore
5376 """
5377 if not members:
5378 raise DataError("ZMSCORE members must be a non-empty list")
5379 pieces = [key] + members
5380 return self.execute_command("ZMSCORE", *pieces, keys=[key])
5382 def _zaggregate(
5383 self,
5384 command: str,
5385 dest: Union[KeyT, None],
5386 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
5387 aggregate: Optional[str] = None,
5388 **options,
5389 ) -> ResponseT:
5390 pieces: list[EncodableT] = [command]
5391 if dest is not None:
5392 pieces.append(dest)
5393 pieces.append(len(keys))
5394 if isinstance(keys, dict):
5395 keys, weights = keys.keys(), keys.values()
5396 else:
5397 weights = None
5398 pieces.extend(keys)
5399 if weights:
5400 pieces.append(b"WEIGHTS")
5401 pieces.extend(weights)
5402 if aggregate:
5403 if aggregate.upper() in ["SUM", "MIN", "MAX"]:
5404 pieces.append(b"AGGREGATE")
5405 pieces.append(aggregate)
5406 else:
5407 raise DataError("aggregate can be sum, min or max.")
5408 if options.get("withscores", False):
5409 pieces.append(b"WITHSCORES")
5410 options["keys"] = keys
5411 return self.execute_command(*pieces, **options)
5414AsyncSortedSetCommands = SortedSetCommands
5417class HyperlogCommands(CommandsProtocol):
5418 """
5419 Redis commands of HyperLogLogs data type.
5420 see: https://redis.io/topics/data-types-intro#hyperloglogs
5421 """
5423 def pfadd(self, name: KeyT, *values: FieldT) -> ResponseT:
5424 """
5425 Adds the specified elements to the specified HyperLogLog.
5427 For more information, see https://redis.io/commands/pfadd
5428 """
5429 return self.execute_command("PFADD", name, *values)
5431 def pfcount(self, *sources: KeyT) -> ResponseT:
5432 """
5433 Return the approximated cardinality of
5434 the set observed by the HyperLogLog at key(s).
5436 For more information, see https://redis.io/commands/pfcount
5437 """
5438 return self.execute_command("PFCOUNT", *sources)
5440 def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT:
5441 """
5442 Merge N different HyperLogLogs into a single one.
5444 For more information, see https://redis.io/commands/pfmerge
5445 """
5446 return self.execute_command("PFMERGE", dest, *sources)
5449AsyncHyperlogCommands = HyperlogCommands
5452class HashDataPersistOptions(Enum):
5453 # set the value for each provided key to each
5454 # provided value only if all do not already exist.
5455 FNX = "FNX"
5457 # set the value for each provided key to each
5458 # provided value only if all already exist.
5459 FXX = "FXX"
5462class HashCommands(CommandsProtocol):
5463 """
5464 Redis commands for Hash data type.
5465 see: https://redis.io/topics/data-types-intro#redis-hashes
5466 """
5468 def hdel(self, name: str, *keys: str) -> Union[Awaitable[int], int]:
5469 """
5470 Delete ``keys`` from hash ``name``
5472 For more information, see https://redis.io/commands/hdel
5473 """
5474 return self.execute_command("HDEL", name, *keys)
5476 def hexists(self, name: str, key: str) -> Union[Awaitable[bool], bool]:
5477 """
5478 Returns a boolean indicating if ``key`` exists within hash ``name``
5480 For more information, see https://redis.io/commands/hexists
5481 """
5482 return self.execute_command("HEXISTS", name, key, keys=[name])
5484 def hget(
5485 self, name: str, key: str
5486 ) -> Union[Awaitable[Optional[str]], Optional[str]]:
5487 """
5488 Return the value of ``key`` within the hash ``name``
5490 For more information, see https://redis.io/commands/hget
5491 """
5492 return self.execute_command("HGET", name, key, keys=[name])
5494 def hgetall(self, name: str) -> Union[Awaitable[dict], dict]:
5495 """
5496 Return a Python dict of the hash's name/value pairs
5498 For more information, see https://redis.io/commands/hgetall
5499 """
5500 return self.execute_command("HGETALL", name, keys=[name])
5502 def hgetdel(
5503 self, name: str, *keys: str
5504 ) -> Union[
5505 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]]
5506 ]:
5507 """
5508 Return the value of ``key`` within the hash ``name`` and
5509 delete the field in the hash.
5510 This command is similar to HGET, except for the fact that it also deletes
5511 the key on success from the hash with the provided ```name```.
5513 Available since Redis 8.0
5514 For more information, see https://redis.io/commands/hgetdel
5515 """
5516 if len(keys) == 0:
5517 raise DataError("'hgetdel' should have at least one key provided")
5519 return self.execute_command("HGETDEL", name, "FIELDS", len(keys), *keys)
5521 def hgetex(
5522 self,
5523 name: KeyT,
5524 *keys: str,
5525 ex: Optional[ExpiryT] = None,
5526 px: Optional[ExpiryT] = None,
5527 exat: Optional[AbsExpiryT] = None,
5528 pxat: Optional[AbsExpiryT] = None,
5529 persist: bool = False,
5530 ) -> Union[
5531 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]]
5532 ]:
5533 """
5534 Return the values of ``key`` and ``keys`` within the hash ``name``
5535 and optionally set their expiration.
5537 ``ex`` sets an expire flag on ``kyes`` for ``ex`` seconds.
5539 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds.
5541 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds,
5542 specified in unix time.
5544 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds,
5545 specified in unix time.
5547 ``persist`` remove the time to live associated with the ``keys``.
5549 Available since Redis 8.0
5550 For more information, see https://redis.io/commands/hgetex
5551 """
5552 if not keys:
5553 raise DataError("'hgetex' should have at least one key provided")
5555 if not at_most_one_value_set((ex, px, exat, pxat, persist)):
5556 raise DataError(
5557 "``ex``, ``px``, ``exat``, ``pxat``, "
5558 "and ``persist`` are mutually exclusive."
5559 )
5561 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat)
5563 if persist:
5564 exp_options.append("PERSIST")
5566 return self.execute_command(
5567 "HGETEX",
5568 name,
5569 *exp_options,
5570 "FIELDS",
5571 len(keys),
5572 *keys,
5573 )
5575 def hincrby(
5576 self, name: str, key: str, amount: int = 1
5577 ) -> Union[Awaitable[int], int]:
5578 """
5579 Increment the value of ``key`` in hash ``name`` by ``amount``
5581 For more information, see https://redis.io/commands/hincrby
5582 """
5583 return self.execute_command("HINCRBY", name, key, amount)
5585 def hincrbyfloat(
5586 self, name: str, key: str, amount: float = 1.0
5587 ) -> Union[Awaitable[float], float]:
5588 """
5589 Increment the value of ``key`` in hash ``name`` by floating ``amount``
5591 For more information, see https://redis.io/commands/hincrbyfloat
5592 """
5593 return self.execute_command("HINCRBYFLOAT", name, key, amount)
5595 def hkeys(self, name: str) -> Union[Awaitable[List], List]:
5596 """
5597 Return the list of keys within hash ``name``
5599 For more information, see https://redis.io/commands/hkeys
5600 """
5601 return self.execute_command("HKEYS", name, keys=[name])
5603 def hlen(self, name: str) -> Union[Awaitable[int], int]:
5604 """
5605 Return the number of elements in hash ``name``
5607 For more information, see https://redis.io/commands/hlen
5608 """
5609 return self.execute_command("HLEN", name, keys=[name])
5611 def hset(
5612 self,
5613 name: str,
5614 key: Optional[str] = None,
5615 value: Optional[str] = None,
5616 mapping: Optional[dict] = None,
5617 items: Optional[list] = None,
5618 ) -> Union[Awaitable[int], int]:
5619 """
5620 Set ``key`` to ``value`` within hash ``name``,
5621 ``mapping`` accepts a dict of key/value pairs that will be
5622 added to hash ``name``.
5623 ``items`` accepts a list of key/value pairs that will be
5624 added to hash ``name``.
5625 Returns the number of fields that were added.
5627 For more information, see https://redis.io/commands/hset
5628 """
5630 if key is None and not mapping and not items:
5631 raise DataError("'hset' with no key value pairs")
5633 pieces = []
5634 if items:
5635 pieces.extend(items)
5636 if key is not None:
5637 pieces.extend((key, value))
5638 if mapping:
5639 for pair in mapping.items():
5640 pieces.extend(pair)
5642 return self.execute_command("HSET", name, *pieces)
5644 def hsetex(
5645 self,
5646 name: str,
5647 key: Optional[str] = None,
5648 value: Optional[str] = None,
5649 mapping: Optional[dict] = None,
5650 items: Optional[list] = None,
5651 ex: Optional[ExpiryT] = None,
5652 px: Optional[ExpiryT] = None,
5653 exat: Optional[AbsExpiryT] = None,
5654 pxat: Optional[AbsExpiryT] = None,
5655 data_persist_option: Optional[HashDataPersistOptions] = None,
5656 keepttl: bool = False,
5657 ) -> Union[Awaitable[int], int]:
5658 """
5659 Set ``key`` to ``value`` within hash ``name``
5661 ``mapping`` accepts a dict of key/value pairs that will be
5662 added to hash ``name``.
5664 ``items`` accepts a list of key/value pairs that will be
5665 added to hash ``name``.
5667 ``ex`` sets an expire flag on ``keys`` for ``ex`` seconds.
5669 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds.
5671 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds,
5672 specified in unix time.
5674 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds,
5675 specified in unix time.
5677 ``data_persist_option`` can be set to ``FNX`` or ``FXX`` to control the
5678 behavior of the command.
5679 ``FNX`` will set the value for each provided key to each
5680 provided value only if all do not already exist.
5681 ``FXX`` will set the value for each provided key to each
5682 provided value only if all already exist.
5684 ``keepttl`` if True, retain the time to live associated with the keys.
5686 Returns the number of fields that were added.
5688 Available since Redis 8.0
5689 For more information, see https://redis.io/commands/hsetex
5690 """
5691 if key is None and not mapping and not items:
5692 raise DataError("'hsetex' with no key value pairs")
5694 if items and len(items) % 2 != 0:
5695 raise DataError(
5696 "'hsetex' with odd number of items. "
5697 "'items' must contain a list of key/value pairs."
5698 )
5700 if not at_most_one_value_set((ex, px, exat, pxat, keepttl)):
5701 raise DataError(
5702 "``ex``, ``px``, ``exat``, ``pxat``, "
5703 "and ``keepttl`` are mutually exclusive."
5704 )
5706 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat)
5707 if data_persist_option:
5708 exp_options.append(data_persist_option.value)
5710 if keepttl:
5711 exp_options.append("KEEPTTL")
5713 pieces = []
5714 if items:
5715 pieces.extend(items)
5716 if key is not None:
5717 pieces.extend((key, value))
5718 if mapping:
5719 for pair in mapping.items():
5720 pieces.extend(pair)
5722 return self.execute_command(
5723 "HSETEX", name, *exp_options, "FIELDS", int(len(pieces) / 2), *pieces
5724 )
5726 def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool]:
5727 """
5728 Set ``key`` to ``value`` within hash ``name`` if ``key`` does not
5729 exist. Returns 1 if HSETNX created a field, otherwise 0.
5731 For more information, see https://redis.io/commands/hsetnx
5732 """
5733 return self.execute_command("HSETNX", name, key, value)
5735 @deprecated_function(
5736 version="4.0.0",
5737 reason="Use 'hset' instead.",
5738 name="hmset",
5739 )
5740 def hmset(self, name: str, mapping: dict) -> Union[Awaitable[str], str]:
5741 """
5742 Set key to value within hash ``name`` for each corresponding
5743 key and value from the ``mapping`` dict.
5745 For more information, see https://redis.io/commands/hmset
5746 """
5747 if not mapping:
5748 raise DataError("'hmset' with 'mapping' of length 0")
5749 items = []
5750 for pair in mapping.items():
5751 items.extend(pair)
5752 return self.execute_command("HMSET", name, *items)
5754 def hmget(self, name: str, keys: List, *args: List) -> Union[Awaitable[List], List]:
5755 """
5756 Returns a list of values ordered identically to ``keys``
5758 For more information, see https://redis.io/commands/hmget
5759 """
5760 args = list_or_args(keys, args)
5761 return self.execute_command("HMGET", name, *args, keys=[name])
5763 def hvals(self, name: str) -> Union[Awaitable[List], List]:
5764 """
5765 Return the list of values within hash ``name``
5767 For more information, see https://redis.io/commands/hvals
5768 """
5769 return self.execute_command("HVALS", name, keys=[name])
5771 def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]:
5772 """
5773 Return the number of bytes stored in the value of ``key``
5774 within hash ``name``
5776 For more information, see https://redis.io/commands/hstrlen
5777 """
5778 return self.execute_command("HSTRLEN", name, key, keys=[name])
5780 def hexpire(
5781 self,
5782 name: KeyT,
5783 seconds: ExpiryT,
5784 *fields: str,
5785 nx: bool = False,
5786 xx: bool = False,
5787 gt: bool = False,
5788 lt: bool = False,
5789 ) -> ResponseT:
5790 """
5791 Sets or updates the expiration time for fields within a hash key, using relative
5792 time in seconds.
5794 If a field already has an expiration time, the behavior of the update can be
5795 controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5797 The return value provides detailed information about the outcome for each field.
5799 For more information, see https://redis.io/commands/hexpire
5801 Args:
5802 name: The name of the hash key.
5803 seconds: Expiration time in seconds, relative. Can be an integer, or a
5804 Python `timedelta` object.
5805 fields: List of fields within the hash to apply the expiration time to.
5806 nx: Set expiry only when the field has no expiry.
5807 xx: Set expiry only when the field has an existing expiry.
5808 gt: Set expiry only when the new expiry is greater than the current one.
5809 lt: Set expiry only when the new expiry is less than the current one.
5811 Returns:
5812 Returns a list which contains for each field in the request:
5813 - `-2` if the field does not exist, or if the key does not exist.
5814 - `0` if the specified NX | XX | GT | LT condition was not met.
5815 - `1` if the expiration time was set or updated.
5816 - `2` if the field was deleted because the specified expiration time is
5817 in the past.
5818 """
5819 conditions = [nx, xx, gt, lt]
5820 if sum(conditions) > 1:
5821 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5823 if isinstance(seconds, datetime.timedelta):
5824 seconds = int(seconds.total_seconds())
5826 options = []
5827 if nx:
5828 options.append("NX")
5829 if xx:
5830 options.append("XX")
5831 if gt:
5832 options.append("GT")
5833 if lt:
5834 options.append("LT")
5836 return self.execute_command(
5837 "HEXPIRE", name, seconds, *options, "FIELDS", len(fields), *fields
5838 )
5840 def hpexpire(
5841 self,
5842 name: KeyT,
5843 milliseconds: ExpiryT,
5844 *fields: str,
5845 nx: bool = False,
5846 xx: bool = False,
5847 gt: bool = False,
5848 lt: bool = False,
5849 ) -> ResponseT:
5850 """
5851 Sets or updates the expiration time for fields within a hash key, using relative
5852 time in milliseconds.
5854 If a field already has an expiration time, the behavior of the update can be
5855 controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5857 The return value provides detailed information about the outcome for each field.
5859 For more information, see https://redis.io/commands/hpexpire
5861 Args:
5862 name: The name of the hash key.
5863 milliseconds: Expiration time in milliseconds, relative. Can be an integer,
5864 or a Python `timedelta` object.
5865 fields: List of fields within the hash to apply the expiration time to.
5866 nx: Set expiry only when the field has no expiry.
5867 xx: Set expiry only when the field has an existing expiry.
5868 gt: Set expiry only when the new expiry is greater than the current one.
5869 lt: Set expiry only when the new expiry is less than the current one.
5871 Returns:
5872 Returns a list which contains for each field in the request:
5873 - `-2` if the field does not exist, or if the key does not exist.
5874 - `0` if the specified NX | XX | GT | LT condition was not met.
5875 - `1` if the expiration time was set or updated.
5876 - `2` if the field was deleted because the specified expiration time is
5877 in the past.
5878 """
5879 conditions = [nx, xx, gt, lt]
5880 if sum(conditions) > 1:
5881 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5883 if isinstance(milliseconds, datetime.timedelta):
5884 milliseconds = int(milliseconds.total_seconds() * 1000)
5886 options = []
5887 if nx:
5888 options.append("NX")
5889 if xx:
5890 options.append("XX")
5891 if gt:
5892 options.append("GT")
5893 if lt:
5894 options.append("LT")
5896 return self.execute_command(
5897 "HPEXPIRE", name, milliseconds, *options, "FIELDS", len(fields), *fields
5898 )
5900 def hexpireat(
5901 self,
5902 name: KeyT,
5903 unix_time_seconds: AbsExpiryT,
5904 *fields: str,
5905 nx: bool = False,
5906 xx: bool = False,
5907 gt: bool = False,
5908 lt: bool = False,
5909 ) -> ResponseT:
5910 """
5911 Sets or updates the expiration time for fields within a hash key, using an
5912 absolute Unix timestamp in seconds.
5914 If a field already has an expiration time, the behavior of the update can be
5915 controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5917 The return value provides detailed information about the outcome for each field.
5919 For more information, see https://redis.io/commands/hexpireat
5921 Args:
5922 name: The name of the hash key.
5923 unix_time_seconds: Expiration time as Unix timestamp in seconds. Can be an
5924 integer or a Python `datetime` object.
5925 fields: List of fields within the hash to apply the expiration time to.
5926 nx: Set expiry only when the field has no expiry.
5927 xx: Set expiry only when the field has an existing expiration time.
5928 gt: Set expiry only when the new expiry is greater than the current one.
5929 lt: Set expiry only when the new expiry is less than the current one.
5931 Returns:
5932 Returns a list which contains for each field in the request:
5933 - `-2` if the field does not exist, or if the key does not exist.
5934 - `0` if the specified NX | XX | GT | LT condition was not met.
5935 - `1` if the expiration time was set or updated.
5936 - `2` if the field was deleted because the specified expiration time is
5937 in the past.
5938 """
5939 conditions = [nx, xx, gt, lt]
5940 if sum(conditions) > 1:
5941 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5943 if isinstance(unix_time_seconds, datetime.datetime):
5944 unix_time_seconds = int(unix_time_seconds.timestamp())
5946 options = []
5947 if nx:
5948 options.append("NX")
5949 if xx:
5950 options.append("XX")
5951 if gt:
5952 options.append("GT")
5953 if lt:
5954 options.append("LT")
5956 return self.execute_command(
5957 "HEXPIREAT",
5958 name,
5959 unix_time_seconds,
5960 *options,
5961 "FIELDS",
5962 len(fields),
5963 *fields,
5964 )
5966 def hpexpireat(
5967 self,
5968 name: KeyT,
5969 unix_time_milliseconds: AbsExpiryT,
5970 *fields: str,
5971 nx: bool = False,
5972 xx: bool = False,
5973 gt: bool = False,
5974 lt: bool = False,
5975 ) -> ResponseT:
5976 """
5977 Sets or updates the expiration time for fields within a hash key, using an
5978 absolute Unix timestamp in milliseconds.
5980 If a field already has an expiration time, the behavior of the update can be
5981 controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5983 The return value provides detailed information about the outcome for each field.
5985 For more information, see https://redis.io/commands/hpexpireat
5987 Args:
5988 name: The name of the hash key.
5989 unix_time_milliseconds: Expiration time as Unix timestamp in milliseconds.
5990 Can be an integer or a Python `datetime` object.
5991 fields: List of fields within the hash to apply the expiry.
5992 nx: Set expiry only when the field has no expiry.
5993 xx: Set expiry only when the field has an existing expiry.
5994 gt: Set expiry only when the new expiry is greater than the current one.
5995 lt: Set expiry only when the new expiry is less than the current one.
5997 Returns:
5998 Returns a list which contains for each field in the request:
5999 - `-2` if the field does not exist, or if the key does not exist.
6000 - `0` if the specified NX | XX | GT | LT condition was not met.
6001 - `1` if the expiration time was set or updated.
6002 - `2` if the field was deleted because the specified expiration time is
6003 in the past.
6004 """
6005 conditions = [nx, xx, gt, lt]
6006 if sum(conditions) > 1:
6007 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
6009 if isinstance(unix_time_milliseconds, datetime.datetime):
6010 unix_time_milliseconds = int(unix_time_milliseconds.timestamp() * 1000)
6012 options = []
6013 if nx:
6014 options.append("NX")
6015 if xx:
6016 options.append("XX")
6017 if gt:
6018 options.append("GT")
6019 if lt:
6020 options.append("LT")
6022 return self.execute_command(
6023 "HPEXPIREAT",
6024 name,
6025 unix_time_milliseconds,
6026 *options,
6027 "FIELDS",
6028 len(fields),
6029 *fields,
6030 )
6032 def hpersist(self, name: KeyT, *fields: str) -> ResponseT:
6033 """
6034 Removes the expiration time for each specified field in a hash.
6036 For more information, see https://redis.io/commands/hpersist
6038 Args:
6039 name: The name of the hash key.
6040 fields: A list of fields within the hash from which to remove the
6041 expiration time.
6043 Returns:
6044 Returns a list which contains for each field in the request:
6045 - `-2` if the field does not exist, or if the key does not exist.
6046 - `-1` if the field exists but has no associated expiration time.
6047 - `1` if the expiration time was successfully removed from the field.
6048 """
6049 return self.execute_command("HPERSIST", name, "FIELDS", len(fields), *fields)
6051 def hexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
6052 """
6053 Returns the expiration times of hash fields as Unix timestamps in seconds.
6055 For more information, see https://redis.io/commands/hexpiretime
6057 Args:
6058 key: The hash key.
6059 fields: A list of fields within the hash for which to get the expiration
6060 time.
6062 Returns:
6063 Returns a list which contains for each field in the request:
6064 - `-2` if the field does not exist, or if the key does not exist.
6065 - `-1` if the field exists but has no associated expire time.
6066 - A positive integer representing the expiration Unix timestamp in
6067 seconds, if the field has an associated expiration time.
6068 """
6069 return self.execute_command(
6070 "HEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key]
6071 )
6073 def hpexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
6074 """
6075 Returns the expiration times of hash fields as Unix timestamps in milliseconds.
6077 For more information, see https://redis.io/commands/hpexpiretime
6079 Args:
6080 key: The hash key.
6081 fields: A list of fields within the hash for which to get the expiration
6082 time.
6084 Returns:
6085 Returns a list which contains for each field in the request:
6086 - `-2` if the field does not exist, or if the key does not exist.
6087 - `-1` if the field exists but has no associated expire time.
6088 - A positive integer representing the expiration Unix timestamp in
6089 milliseconds, if the field has an associated expiration time.
6090 """
6091 return self.execute_command(
6092 "HPEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key]
6093 )
6095 def httl(self, key: KeyT, *fields: str) -> ResponseT:
6096 """
6097 Returns the TTL (Time To Live) in seconds for each specified field within a hash
6098 key.
6100 For more information, see https://redis.io/commands/httl
6102 Args:
6103 key: The hash key.
6104 fields: A list of fields within the hash for which to get the TTL.
6106 Returns:
6107 Returns a list which contains for each field in the request:
6108 - `-2` if the field does not exist, or if the key does not exist.
6109 - `-1` if the field exists but has no associated expire time.
6110 - A positive integer representing the TTL in seconds if the field has
6111 an associated expiration time.
6112 """
6113 return self.execute_command(
6114 "HTTL", key, "FIELDS", len(fields), *fields, keys=[key]
6115 )
6117 def hpttl(self, key: KeyT, *fields: str) -> ResponseT:
6118 """
6119 Returns the TTL (Time To Live) in milliseconds for each specified field within a
6120 hash key.
6122 For more information, see https://redis.io/commands/hpttl
6124 Args:
6125 key: The hash key.
6126 fields: A list of fields within the hash for which to get the TTL.
6128 Returns:
6129 Returns a list which contains for each field in the request:
6130 - `-2` if the field does not exist, or if the key does not exist.
6131 - `-1` if the field exists but has no associated expire time.
6132 - A positive integer representing the TTL in milliseconds if the field
6133 has an associated expiration time.
6134 """
6135 return self.execute_command(
6136 "HPTTL", key, "FIELDS", len(fields), *fields, keys=[key]
6137 )
6140AsyncHashCommands = HashCommands
6143class Script:
6144 """
6145 An executable Lua script object returned by ``register_script``
6146 """
6148 def __init__(self, registered_client: "redis.client.Redis", script: ScriptTextT):
6149 self.registered_client = registered_client
6150 self.script = script
6151 # Precalculate and store the SHA1 hex digest of the script.
6153 if isinstance(script, str):
6154 # We need the encoding from the client in order to generate an
6155 # accurate byte representation of the script
6156 encoder = self.get_encoder()
6157 script = encoder.encode(script)
6158 self.sha = hashlib.sha1(script).hexdigest()
6160 def __call__(
6161 self,
6162 keys: Union[Sequence[KeyT], None] = None,
6163 args: Union[Iterable[EncodableT], None] = None,
6164 client: Union["redis.client.Redis", None] = None,
6165 ):
6166 """Execute the script, passing any required ``args``"""
6167 keys = keys or []
6168 args = args or []
6169 if client is None:
6170 client = self.registered_client
6171 args = tuple(keys) + tuple(args)
6172 # make sure the Redis server knows about the script
6173 from redis.client import Pipeline
6175 if isinstance(client, Pipeline):
6176 # Make sure the pipeline can register the script before executing.
6177 client.scripts.add(self)
6178 try:
6179 return client.evalsha(self.sha, len(keys), *args)
6180 except NoScriptError:
6181 # Maybe the client is pointed to a different server than the client
6182 # that created this instance?
6183 # Overwrite the sha just in case there was a discrepancy.
6184 self.sha = client.script_load(self.script)
6185 return client.evalsha(self.sha, len(keys), *args)
6187 def get_encoder(self):
6188 """Get the encoder to encode string scripts into bytes."""
6189 try:
6190 return self.registered_client.get_encoder()
6191 except AttributeError:
6192 # DEPRECATED
6193 # In version <=4.1.2, this was the code we used to get the encoder.
6194 # However, after 4.1.2 we added support for scripting in clustered
6195 # redis. ClusteredRedis doesn't have a `.connection_pool` attribute
6196 # so we changed the Script class to use
6197 # `self.registered_client.get_encoder` (see above).
6198 # However, that is technically a breaking change, as consumers who
6199 # use Scripts directly might inject a `registered_client` that
6200 # doesn't have a `.get_encoder` field. This try/except prevents us
6201 # from breaking backward-compatibility. Ideally, it would be
6202 # removed in the next major release.
6203 return self.registered_client.connection_pool.get_encoder()
6206class AsyncScript:
6207 """
6208 An executable Lua script object returned by ``register_script``
6209 """
6211 def __init__(
6212 self,
6213 registered_client: "redis.asyncio.client.Redis",
6214 script: ScriptTextT,
6215 ):
6216 self.registered_client = registered_client
6217 self.script = script
6218 # Precalculate and store the SHA1 hex digest of the script.
6220 if isinstance(script, str):
6221 # We need the encoding from the client in order to generate an
6222 # accurate byte representation of the script
6223 try:
6224 encoder = registered_client.connection_pool.get_encoder()
6225 except AttributeError:
6226 # Cluster
6227 encoder = registered_client.get_encoder()
6228 script = encoder.encode(script)
6229 self.sha = hashlib.sha1(script).hexdigest()
6231 async def __call__(
6232 self,
6233 keys: Union[Sequence[KeyT], None] = None,
6234 args: Union[Iterable[EncodableT], None] = None,
6235 client: Union["redis.asyncio.client.Redis", None] = None,
6236 ):
6237 """Execute the script, passing any required ``args``"""
6238 keys = keys or []
6239 args = args or []
6240 if client is None:
6241 client = self.registered_client
6242 args = tuple(keys) + tuple(args)
6243 # make sure the Redis server knows about the script
6244 from redis.asyncio.client import Pipeline
6246 if isinstance(client, Pipeline):
6247 # Make sure the pipeline can register the script before executing.
6248 client.scripts.add(self)
6249 try:
6250 return await client.evalsha(self.sha, len(keys), *args)
6251 except NoScriptError:
6252 # Maybe the client is pointed to a different server than the client
6253 # that created this instance?
6254 # Overwrite the sha just in case there was a discrepancy.
6255 self.sha = await client.script_load(self.script)
6256 return await client.evalsha(self.sha, len(keys), *args)
6259class PubSubCommands(CommandsProtocol):
6260 """
6261 Redis PubSub commands.
6262 see https://redis.io/topics/pubsub
6263 """
6265 def publish(self, channel: ChannelT, message: EncodableT, **kwargs) -> ResponseT:
6266 """
6267 Publish ``message`` on ``channel``.
6268 Returns the number of subscribers the message was delivered to.
6270 For more information, see https://redis.io/commands/publish
6271 """
6272 return self.execute_command("PUBLISH", channel, message, **kwargs)
6274 def spublish(self, shard_channel: ChannelT, message: EncodableT) -> ResponseT:
6275 """
6276 Posts a message to the given shard channel.
6277 Returns the number of clients that received the message
6279 For more information, see https://redis.io/commands/spublish
6280 """
6281 return self.execute_command("SPUBLISH", shard_channel, message)
6283 def pubsub_channels(self, pattern: PatternT = "*", **kwargs) -> ResponseT:
6284 """
6285 Return a list of channels that have at least one subscriber
6287 For more information, see https://redis.io/commands/pubsub-channels
6288 """
6289 return self.execute_command("PUBSUB CHANNELS", pattern, **kwargs)
6291 def pubsub_shardchannels(self, pattern: PatternT = "*", **kwargs) -> ResponseT:
6292 """
6293 Return a list of shard_channels that have at least one subscriber
6295 For more information, see https://redis.io/commands/pubsub-shardchannels
6296 """
6297 return self.execute_command("PUBSUB SHARDCHANNELS", pattern, **kwargs)
6299 def pubsub_numpat(self, **kwargs) -> ResponseT:
6300 """
6301 Returns the number of subscriptions to patterns
6303 For more information, see https://redis.io/commands/pubsub-numpat
6304 """
6305 return self.execute_command("PUBSUB NUMPAT", **kwargs)
6307 def pubsub_numsub(self, *args: ChannelT, **kwargs) -> ResponseT:
6308 """
6309 Return a list of (channel, number of subscribers) tuples
6310 for each channel given in ``*args``
6312 For more information, see https://redis.io/commands/pubsub-numsub
6313 """
6314 return self.execute_command("PUBSUB NUMSUB", *args, **kwargs)
6316 def pubsub_shardnumsub(self, *args: ChannelT, **kwargs) -> ResponseT:
6317 """
6318 Return a list of (shard_channel, number of subscribers) tuples
6319 for each channel given in ``*args``
6321 For more information, see https://redis.io/commands/pubsub-shardnumsub
6322 """
6323 return self.execute_command("PUBSUB SHARDNUMSUB", *args, **kwargs)
6326AsyncPubSubCommands = PubSubCommands
6329class ScriptCommands(CommandsProtocol):
6330 """
6331 Redis Lua script commands. see:
6332 https://redis.io/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/
6333 """
6335 def _eval(
6336 self,
6337 command: str,
6338 script: str,
6339 numkeys: int,
6340 *keys_and_args: Union[KeyT, EncodableT],
6341 ) -> Union[Awaitable[str], str]:
6342 return self.execute_command(command, script, numkeys, *keys_and_args)
6344 def eval(
6345 self, script: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT]
6346 ) -> Union[Awaitable[str], str]:
6347 """
6348 Execute the Lua ``script``, specifying the ``numkeys`` the script
6349 will touch and the key names and argument values in ``keys_and_args``.
6350 Returns the result of the script.
6352 In practice, use the object returned by ``register_script``. This
6353 function exists purely for Redis API completion.
6355 For more information, see https://redis.io/commands/eval
6356 """
6357 return self._eval("EVAL", script, numkeys, *keys_and_args)
6359 def eval_ro(
6360 self, script: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT]
6361 ) -> Union[Awaitable[str], str]:
6362 """
6363 The read-only variant of the EVAL command
6365 Execute the read-only Lua ``script`` specifying the ``numkeys`` the script
6366 will touch and the key names and argument values in ``keys_and_args``.
6367 Returns the result of the script.
6369 For more information, see https://redis.io/commands/eval_ro
6370 """
6371 return self._eval("EVAL_RO", script, numkeys, *keys_and_args)
6373 def _evalsha(
6374 self,
6375 command: str,
6376 sha: str,
6377 numkeys: int,
6378 *keys_and_args: Union[KeyT, EncodableT],
6379 ) -> Union[Awaitable[str], str]:
6380 return self.execute_command(command, sha, numkeys, *keys_and_args)
6382 def evalsha(
6383 self, sha: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT]
6384 ) -> Union[Awaitable[str], str]:
6385 """
6386 Use the ``sha`` to execute a Lua script already registered via EVAL
6387 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the
6388 key names and argument values in ``keys_and_args``. Returns the result
6389 of the script.
6391 In practice, use the object returned by ``register_script``. This
6392 function exists purely for Redis API completion.
6394 For more information, see https://redis.io/commands/evalsha
6395 """
6396 return self._evalsha("EVALSHA", sha, numkeys, *keys_and_args)
6398 def evalsha_ro(
6399 self, sha: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT]
6400 ) -> Union[Awaitable[str], str]:
6401 """
6402 The read-only variant of the EVALSHA command
6404 Use the ``sha`` to execute a read-only Lua script already registered via EVAL
6405 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the
6406 key names and argument values in ``keys_and_args``. Returns the result
6407 of the script.
6409 For more information, see https://redis.io/commands/evalsha_ro
6410 """
6411 return self._evalsha("EVALSHA_RO", sha, numkeys, *keys_and_args)
6413 def script_exists(self, *args: str) -> ResponseT:
6414 """
6415 Check if a script exists in the script cache by specifying the SHAs of
6416 each script as ``args``. Returns a list of boolean values indicating if
6417 if each already script exists in the cache_data.
6419 For more information, see https://redis.io/commands/script-exists
6420 """
6421 return self.execute_command("SCRIPT EXISTS", *args)
6423 def script_debug(self, *args) -> None:
6424 raise NotImplementedError(
6425 "SCRIPT DEBUG is intentionally not implemented in the client."
6426 )
6428 def script_flush(
6429 self, sync_type: Union[Literal["SYNC"], Literal["ASYNC"]] = None
6430 ) -> ResponseT:
6431 """Flush all scripts from the script cache_data.
6433 ``sync_type`` is by default SYNC (synchronous) but it can also be
6434 ASYNC.
6436 For more information, see https://redis.io/commands/script-flush
6437 """
6439 # Redis pre 6 had no sync_type.
6440 if sync_type not in ["SYNC", "ASYNC", None]:
6441 raise DataError(
6442 "SCRIPT FLUSH defaults to SYNC in redis > 6.2, or "
6443 "accepts SYNC/ASYNC. For older versions, "
6444 "of redis leave as None."
6445 )
6446 if sync_type is None:
6447 pieces = []
6448 else:
6449 pieces = [sync_type]
6450 return self.execute_command("SCRIPT FLUSH", *pieces)
6452 def script_kill(self) -> ResponseT:
6453 """
6454 Kill the currently executing Lua script
6456 For more information, see https://redis.io/commands/script-kill
6457 """
6458 return self.execute_command("SCRIPT KILL")
6460 def script_load(self, script: ScriptTextT) -> ResponseT:
6461 """
6462 Load a Lua ``script`` into the script cache_data. Returns the SHA.
6464 For more information, see https://redis.io/commands/script-load
6465 """
6466 return self.execute_command("SCRIPT LOAD", script)
6468 def register_script(self: "redis.client.Redis", script: ScriptTextT) -> Script:
6469 """
6470 Register a Lua ``script`` specifying the ``keys`` it will touch.
6471 Returns a Script object that is callable and hides the complexity of
6472 deal with scripts, keys, and shas. This is the preferred way to work
6473 with Lua scripts.
6474 """
6475 return Script(self, script)
6478class AsyncScriptCommands(ScriptCommands):
6479 async def script_debug(self, *args) -> None:
6480 return super().script_debug()
6482 def register_script(
6483 self: "redis.asyncio.client.Redis",
6484 script: ScriptTextT,
6485 ) -> AsyncScript:
6486 """
6487 Register a Lua ``script`` specifying the ``keys`` it will touch.
6488 Returns a Script object that is callable and hides the complexity of
6489 deal with scripts, keys, and shas. This is the preferred way to work
6490 with Lua scripts.
6491 """
6492 return AsyncScript(self, script)
6495class GeoCommands(CommandsProtocol):
6496 """
6497 Redis Geospatial commands.
6498 see: https://redis.com/redis-best-practices/indexing-patterns/geospatial/
6499 """
6501 def geoadd(
6502 self,
6503 name: KeyT,
6504 values: Sequence[EncodableT],
6505 nx: bool = False,
6506 xx: bool = False,
6507 ch: bool = False,
6508 ) -> ResponseT:
6509 """
6510 Add the specified geospatial items to the specified key identified
6511 by the ``name`` argument. The Geospatial items are given as ordered
6512 members of the ``values`` argument, each item or place is formed by
6513 the triad longitude, latitude and name.
6515 Note: You can use ZREM to remove elements.
6517 ``nx`` forces ZADD to only create new elements and not to update
6518 scores for elements that already exist.
6520 ``xx`` forces ZADD to only update scores of elements that already
6521 exist. New elements will not be added.
6523 ``ch`` modifies the return value to be the numbers of elements changed.
6524 Changed elements include new elements that were added and elements
6525 whose scores changed.
6527 For more information, see https://redis.io/commands/geoadd
6528 """
6529 if nx and xx:
6530 raise DataError("GEOADD allows either 'nx' or 'xx', not both")
6531 if len(values) % 3 != 0:
6532 raise DataError("GEOADD requires places with lon, lat and name values")
6533 pieces = [name]
6534 if nx:
6535 pieces.append("NX")
6536 if xx:
6537 pieces.append("XX")
6538 if ch:
6539 pieces.append("CH")
6540 pieces.extend(values)
6541 return self.execute_command("GEOADD", *pieces)
6543 def geodist(
6544 self, name: KeyT, place1: FieldT, place2: FieldT, unit: Optional[str] = None
6545 ) -> ResponseT:
6546 """
6547 Return the distance between ``place1`` and ``place2`` members of the
6548 ``name`` key.
6549 The units must be one of the following : m, km mi, ft. By default
6550 meters are used.
6552 For more information, see https://redis.io/commands/geodist
6553 """
6554 pieces: list[EncodableT] = [name, place1, place2]
6555 if unit and unit not in ("m", "km", "mi", "ft"):
6556 raise DataError("GEODIST invalid unit")
6557 elif unit:
6558 pieces.append(unit)
6559 return self.execute_command("GEODIST", *pieces, keys=[name])
6561 def geohash(self, name: KeyT, *values: FieldT) -> ResponseT:
6562 """
6563 Return the geo hash string for each item of ``values`` members of
6564 the specified key identified by the ``name`` argument.
6566 For more information, see https://redis.io/commands/geohash
6567 """
6568 return self.execute_command("GEOHASH", name, *values, keys=[name])
6570 def geopos(self, name: KeyT, *values: FieldT) -> ResponseT:
6571 """
6572 Return the positions of each item of ``values`` as members of
6573 the specified key identified by the ``name`` argument. Each position
6574 is represented by the pairs lon and lat.
6576 For more information, see https://redis.io/commands/geopos
6577 """
6578 return self.execute_command("GEOPOS", name, *values, keys=[name])
6580 def georadius(
6581 self,
6582 name: KeyT,
6583 longitude: float,
6584 latitude: float,
6585 radius: float,
6586 unit: Optional[str] = None,
6587 withdist: bool = False,
6588 withcoord: bool = False,
6589 withhash: bool = False,
6590 count: Optional[int] = None,
6591 sort: Optional[str] = None,
6592 store: Optional[KeyT] = None,
6593 store_dist: Optional[KeyT] = None,
6594 any: bool = False,
6595 ) -> ResponseT:
6596 """
6597 Return the members of the specified key identified by the
6598 ``name`` argument which are within the borders of the area specified
6599 with the ``latitude`` and ``longitude`` location and the maximum
6600 distance from the center specified by the ``radius`` value.
6602 The units must be one of the following : m, km mi, ft. By default
6604 ``withdist`` indicates to return the distances of each place.
6606 ``withcoord`` indicates to return the latitude and longitude of
6607 each place.
6609 ``withhash`` indicates to return the geohash string of each place.
6611 ``count`` indicates to return the number of elements up to N.
6613 ``sort`` indicates to return the places in a sorted way, ASC for
6614 nearest to fairest and DESC for fairest to nearest.
6616 ``store`` indicates to save the places names in a sorted set named
6617 with a specific key, each element of the destination sorted set is
6618 populated with the score got from the original geo sorted set.
6620 ``store_dist`` indicates to save the places names in a sorted set
6621 named with a specific key, instead of ``store`` the sorted set
6622 destination score is set with the distance.
6624 For more information, see https://redis.io/commands/georadius
6625 """
6626 return self._georadiusgeneric(
6627 "GEORADIUS",
6628 name,
6629 longitude,
6630 latitude,
6631 radius,
6632 unit=unit,
6633 withdist=withdist,
6634 withcoord=withcoord,
6635 withhash=withhash,
6636 count=count,
6637 sort=sort,
6638 store=store,
6639 store_dist=store_dist,
6640 any=any,
6641 )
6643 def georadiusbymember(
6644 self,
6645 name: KeyT,
6646 member: FieldT,
6647 radius: float,
6648 unit: Optional[str] = None,
6649 withdist: bool = False,
6650 withcoord: bool = False,
6651 withhash: bool = False,
6652 count: Optional[int] = None,
6653 sort: Optional[str] = None,
6654 store: Union[KeyT, None] = None,
6655 store_dist: Union[KeyT, None] = None,
6656 any: bool = False,
6657 ) -> ResponseT:
6658 """
6659 This command is exactly like ``georadius`` with the sole difference
6660 that instead of taking, as the center of the area to query, a longitude
6661 and latitude value, it takes the name of a member already existing
6662 inside the geospatial index represented by the sorted set.
6664 For more information, see https://redis.io/commands/georadiusbymember
6665 """
6666 return self._georadiusgeneric(
6667 "GEORADIUSBYMEMBER",
6668 name,
6669 member,
6670 radius,
6671 unit=unit,
6672 withdist=withdist,
6673 withcoord=withcoord,
6674 withhash=withhash,
6675 count=count,
6676 sort=sort,
6677 store=store,
6678 store_dist=store_dist,
6679 any=any,
6680 )
6682 def _georadiusgeneric(
6683 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None]
6684 ) -> ResponseT:
6685 pieces = list(args)
6686 if kwargs["unit"] and kwargs["unit"] not in ("m", "km", "mi", "ft"):
6687 raise DataError("GEORADIUS invalid unit")
6688 elif kwargs["unit"]:
6689 pieces.append(kwargs["unit"])
6690 else:
6691 pieces.append("m")
6693 if kwargs["any"] and kwargs["count"] is None:
6694 raise DataError("``any`` can't be provided without ``count``")
6696 for arg_name, byte_repr in (
6697 ("withdist", "WITHDIST"),
6698 ("withcoord", "WITHCOORD"),
6699 ("withhash", "WITHHASH"),
6700 ):
6701 if kwargs[arg_name]:
6702 pieces.append(byte_repr)
6704 if kwargs["count"] is not None:
6705 pieces.extend(["COUNT", kwargs["count"]])
6706 if kwargs["any"]:
6707 pieces.append("ANY")
6709 if kwargs["sort"]:
6710 if kwargs["sort"] == "ASC":
6711 pieces.append("ASC")
6712 elif kwargs["sort"] == "DESC":
6713 pieces.append("DESC")
6714 else:
6715 raise DataError("GEORADIUS invalid sort")
6717 if kwargs["store"] and kwargs["store_dist"]:
6718 raise DataError("GEORADIUS store and store_dist cant be set together")
6720 if kwargs["store"]:
6721 pieces.extend([b"STORE", kwargs["store"]])
6723 if kwargs["store_dist"]:
6724 pieces.extend([b"STOREDIST", kwargs["store_dist"]])
6726 return self.execute_command(command, *pieces, **kwargs)
6728 def geosearch(
6729 self,
6730 name: KeyT,
6731 member: Union[FieldT, None] = None,
6732 longitude: Union[float, None] = None,
6733 latitude: Union[float, None] = None,
6734 unit: str = "m",
6735 radius: Union[float, None] = None,
6736 width: Union[float, None] = None,
6737 height: Union[float, None] = None,
6738 sort: Optional[str] = None,
6739 count: Optional[int] = None,
6740 any: bool = False,
6741 withcoord: bool = False,
6742 withdist: bool = False,
6743 withhash: bool = False,
6744 ) -> ResponseT:
6745 """
6746 Return the members of specified key identified by the
6747 ``name`` argument, which are within the borders of the
6748 area specified by a given shape. This command extends the
6749 GEORADIUS command, so in addition to searching within circular
6750 areas, it supports searching within rectangular areas.
6752 This command should be used in place of the deprecated
6753 GEORADIUS and GEORADIUSBYMEMBER commands.
6755 ``member`` Use the position of the given existing
6756 member in the sorted set. Can't be given with ``longitude``
6757 and ``latitude``.
6759 ``longitude`` and ``latitude`` Use the position given by
6760 this coordinates. Can't be given with ``member``
6761 ``radius`` Similar to GEORADIUS, search inside circular
6762 area according the given radius. Can't be given with
6763 ``height`` and ``width``.
6764 ``height`` and ``width`` Search inside an axis-aligned
6765 rectangle, determined by the given height and width.
6766 Can't be given with ``radius``
6768 ``unit`` must be one of the following : m, km, mi, ft.
6769 `m` for meters (the default value), `km` for kilometers,
6770 `mi` for miles and `ft` for feet.
6772 ``sort`` indicates to return the places in a sorted way,
6773 ASC for nearest to furthest and DESC for furthest to nearest.
6775 ``count`` limit the results to the first count matching items.
6777 ``any`` is set to True, the command will return as soon as
6778 enough matches are found. Can't be provided without ``count``
6780 ``withdist`` indicates to return the distances of each place.
6781 ``withcoord`` indicates to return the latitude and longitude of
6782 each place.
6784 ``withhash`` indicates to return the geohash string of each place.
6786 For more information, see https://redis.io/commands/geosearch
6787 """
6789 return self._geosearchgeneric(
6790 "GEOSEARCH",
6791 name,
6792 member=member,
6793 longitude=longitude,
6794 latitude=latitude,
6795 unit=unit,
6796 radius=radius,
6797 width=width,
6798 height=height,
6799 sort=sort,
6800 count=count,
6801 any=any,
6802 withcoord=withcoord,
6803 withdist=withdist,
6804 withhash=withhash,
6805 store=None,
6806 store_dist=None,
6807 )
6809 def geosearchstore(
6810 self,
6811 dest: KeyT,
6812 name: KeyT,
6813 member: Optional[FieldT] = None,
6814 longitude: Optional[float] = None,
6815 latitude: Optional[float] = None,
6816 unit: str = "m",
6817 radius: Optional[float] = None,
6818 width: Optional[float] = None,
6819 height: Optional[float] = None,
6820 sort: Optional[str] = None,
6821 count: Optional[int] = None,
6822 any: bool = False,
6823 storedist: bool = False,
6824 ) -> ResponseT:
6825 """
6826 This command is like GEOSEARCH, but stores the result in
6827 ``dest``. By default, it stores the results in the destination
6828 sorted set with their geospatial information.
6829 if ``store_dist`` set to True, the command will stores the
6830 items in a sorted set populated with their distance from the
6831 center of the circle or box, as a floating-point number.
6833 For more information, see https://redis.io/commands/geosearchstore
6834 """
6835 return self._geosearchgeneric(
6836 "GEOSEARCHSTORE",
6837 dest,
6838 name,
6839 member=member,
6840 longitude=longitude,
6841 latitude=latitude,
6842 unit=unit,
6843 radius=radius,
6844 width=width,
6845 height=height,
6846 sort=sort,
6847 count=count,
6848 any=any,
6849 withcoord=None,
6850 withdist=None,
6851 withhash=None,
6852 store=None,
6853 store_dist=storedist,
6854 )
6856 def _geosearchgeneric(
6857 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None]
6858 ) -> ResponseT:
6859 pieces = list(args)
6861 # FROMMEMBER or FROMLONLAT
6862 if kwargs["member"] is None:
6863 if kwargs["longitude"] is None or kwargs["latitude"] is None:
6864 raise DataError("GEOSEARCH must have member or longitude and latitude")
6865 if kwargs["member"]:
6866 if kwargs["longitude"] or kwargs["latitude"]:
6867 raise DataError(
6868 "GEOSEARCH member and longitude or latitude cant be set together"
6869 )
6870 pieces.extend([b"FROMMEMBER", kwargs["member"]])
6871 if kwargs["longitude"] is not None and kwargs["latitude"] is not None:
6872 pieces.extend([b"FROMLONLAT", kwargs["longitude"], kwargs["latitude"]])
6874 # BYRADIUS or BYBOX
6875 if kwargs["radius"] is None:
6876 if kwargs["width"] is None or kwargs["height"] is None:
6877 raise DataError("GEOSEARCH must have radius or width and height")
6878 if kwargs["unit"] is None:
6879 raise DataError("GEOSEARCH must have unit")
6880 if kwargs["unit"].lower() not in ("m", "km", "mi", "ft"):
6881 raise DataError("GEOSEARCH invalid unit")
6882 if kwargs["radius"]:
6883 if kwargs["width"] or kwargs["height"]:
6884 raise DataError(
6885 "GEOSEARCH radius and width or height cant be set together"
6886 )
6887 pieces.extend([b"BYRADIUS", kwargs["radius"], kwargs["unit"]])
6888 if kwargs["width"] and kwargs["height"]:
6889 pieces.extend([b"BYBOX", kwargs["width"], kwargs["height"], kwargs["unit"]])
6891 # sort
6892 if kwargs["sort"]:
6893 if kwargs["sort"].upper() == "ASC":
6894 pieces.append(b"ASC")
6895 elif kwargs["sort"].upper() == "DESC":
6896 pieces.append(b"DESC")
6897 else:
6898 raise DataError("GEOSEARCH invalid sort")
6900 # count any
6901 if kwargs["count"]:
6902 pieces.extend([b"COUNT", kwargs["count"]])
6903 if kwargs["any"]:
6904 pieces.append(b"ANY")
6905 elif kwargs["any"]:
6906 raise DataError("GEOSEARCH ``any`` can't be provided without count")
6908 # other properties
6909 for arg_name, byte_repr in (
6910 ("withdist", b"WITHDIST"),
6911 ("withcoord", b"WITHCOORD"),
6912 ("withhash", b"WITHHASH"),
6913 ("store_dist", b"STOREDIST"),
6914 ):
6915 if kwargs[arg_name]:
6916 pieces.append(byte_repr)
6918 kwargs["keys"] = [args[0] if command == "GEOSEARCH" else args[1]]
6920 return self.execute_command(command, *pieces, **kwargs)
6923AsyncGeoCommands = GeoCommands
6926class ModuleCommands(CommandsProtocol):
6927 """
6928 Redis Module commands.
6929 see: https://redis.io/topics/modules-intro
6930 """
6932 def module_load(self, path, *args) -> ResponseT:
6933 """
6934 Loads the module from ``path``.
6935 Passes all ``*args`` to the module, during loading.
6936 Raises ``ModuleError`` if a module is not found at ``path``.
6938 For more information, see https://redis.io/commands/module-load
6939 """
6940 return self.execute_command("MODULE LOAD", path, *args)
6942 def module_loadex(
6943 self,
6944 path: str,
6945 options: Optional[List[str]] = None,
6946 args: Optional[List[str]] = None,
6947 ) -> ResponseT:
6948 """
6949 Loads a module from a dynamic library at runtime with configuration directives.
6951 For more information, see https://redis.io/commands/module-loadex
6952 """
6953 pieces = []
6954 if options is not None:
6955 pieces.append("CONFIG")
6956 pieces.extend(options)
6957 if args is not None:
6958 pieces.append("ARGS")
6959 pieces.extend(args)
6961 return self.execute_command("MODULE LOADEX", path, *pieces)
6963 def module_unload(self, name) -> ResponseT:
6964 """
6965 Unloads the module ``name``.
6966 Raises ``ModuleError`` if ``name`` is not in loaded modules.
6968 For more information, see https://redis.io/commands/module-unload
6969 """
6970 return self.execute_command("MODULE UNLOAD", name)
6972 def module_list(self) -> ResponseT:
6973 """
6974 Returns a list of dictionaries containing the name and version of
6975 all loaded modules.
6977 For more information, see https://redis.io/commands/module-list
6978 """
6979 return self.execute_command("MODULE LIST")
6981 def command_info(self) -> None:
6982 raise NotImplementedError(
6983 "COMMAND INFO is intentionally not implemented in the client."
6984 )
6986 def command_count(self) -> ResponseT:
6987 return self.execute_command("COMMAND COUNT")
6989 def command_getkeys(self, *args) -> ResponseT:
6990 return self.execute_command("COMMAND GETKEYS", *args)
6992 def command(self) -> ResponseT:
6993 return self.execute_command("COMMAND")
6996class AsyncModuleCommands(ModuleCommands):
6997 async def command_info(self) -> None:
6998 return super().command_info()
7001class ClusterCommands(CommandsProtocol):
7002 """
7003 Class for Redis Cluster commands
7004 """
7006 def cluster(self, cluster_arg, *args, **kwargs) -> ResponseT:
7007 return self.execute_command(f"CLUSTER {cluster_arg.upper()}", *args, **kwargs)
7009 def readwrite(self, **kwargs) -> ResponseT:
7010 """
7011 Disables read queries for a connection to a Redis Cluster slave node.
7013 For more information, see https://redis.io/commands/readwrite
7014 """
7015 return self.execute_command("READWRITE", **kwargs)
7017 def readonly(self, **kwargs) -> ResponseT:
7018 """
7019 Enables read queries for a connection to a Redis Cluster replica node.
7021 For more information, see https://redis.io/commands/readonly
7022 """
7023 return self.execute_command("READONLY", **kwargs)
7026AsyncClusterCommands = ClusterCommands
7029class FunctionCommands:
7030 """
7031 Redis Function commands
7032 """
7034 def function_load(
7035 self, code: str, replace: Optional[bool] = False
7036 ) -> Union[Awaitable[str], str]:
7037 """
7038 Load a library to Redis.
7039 :param code: the source code (must start with
7040 Shebang statement that provides a metadata about the library)
7041 :param replace: changes the behavior to overwrite the existing library
7042 with the new contents.
7043 Return the library name that was loaded.
7045 For more information, see https://redis.io/commands/function-load
7046 """
7047 pieces = ["REPLACE"] if replace else []
7048 pieces.append(code)
7049 return self.execute_command("FUNCTION LOAD", *pieces)
7051 def function_delete(self, library: str) -> Union[Awaitable[str], str]:
7052 """
7053 Delete the library called ``library`` and all its functions.
7055 For more information, see https://redis.io/commands/function-delete
7056 """
7057 return self.execute_command("FUNCTION DELETE", library)
7059 def function_flush(self, mode: str = "SYNC") -> Union[Awaitable[str], str]:
7060 """
7061 Deletes all the libraries.
7063 For more information, see https://redis.io/commands/function-flush
7064 """
7065 return self.execute_command("FUNCTION FLUSH", mode)
7067 def function_list(
7068 self, library: Optional[str] = "*", withcode: Optional[bool] = False
7069 ) -> Union[Awaitable[List], List]:
7070 """
7071 Return information about the functions and libraries.
7073 Args:
7075 library: specify a pattern for matching library names
7076 withcode: cause the server to include the libraries source implementation
7077 in the reply
7078 """
7079 args = ["LIBRARYNAME", library]
7080 if withcode:
7081 args.append("WITHCODE")
7082 return self.execute_command("FUNCTION LIST", *args)
7084 def _fcall(
7085 self, command: str, function, numkeys: int, *keys_and_args: Any
7086 ) -> Union[Awaitable[str], str]:
7087 return self.execute_command(command, function, numkeys, *keys_and_args)
7089 def fcall(
7090 self, function, numkeys: int, *keys_and_args: Any
7091 ) -> Union[Awaitable[str], str]:
7092 """
7093 Invoke a function.
7095 For more information, see https://redis.io/commands/fcall
7096 """
7097 return self._fcall("FCALL", function, numkeys, *keys_and_args)
7099 def fcall_ro(
7100 self, function, numkeys: int, *keys_and_args: Any
7101 ) -> Union[Awaitable[str], str]:
7102 """
7103 This is a read-only variant of the FCALL command that cannot
7104 execute commands that modify data.
7106 For more information, see https://redis.io/commands/fcall_ro
7107 """
7108 return self._fcall("FCALL_RO", function, numkeys, *keys_and_args)
7110 def function_dump(self) -> Union[Awaitable[str], str]:
7111 """
7112 Return the serialized payload of loaded libraries.
7114 For more information, see https://redis.io/commands/function-dump
7115 """
7116 from redis.client import NEVER_DECODE
7118 options = {}
7119 options[NEVER_DECODE] = []
7121 return self.execute_command("FUNCTION DUMP", **options)
7123 def function_restore(
7124 self, payload: str, policy: Optional[str] = "APPEND"
7125 ) -> Union[Awaitable[str], str]:
7126 """
7127 Restore libraries from the serialized ``payload``.
7128 You can use the optional policy argument to provide a policy
7129 for handling existing libraries.
7131 For more information, see https://redis.io/commands/function-restore
7132 """
7133 return self.execute_command("FUNCTION RESTORE", payload, policy)
7135 def function_kill(self) -> Union[Awaitable[str], str]:
7136 """
7137 Kill a function that is currently executing.
7139 For more information, see https://redis.io/commands/function-kill
7140 """
7141 return self.execute_command("FUNCTION KILL")
7143 def function_stats(self) -> Union[Awaitable[List], List]:
7144 """
7145 Return information about the function that's currently running
7146 and information about the available execution engines.
7148 For more information, see https://redis.io/commands/function-stats
7149 """
7150 return self.execute_command("FUNCTION STATS")
7153AsyncFunctionCommands = FunctionCommands
7156class DataAccessCommands(
7157 BasicKeyCommands,
7158 HyperlogCommands,
7159 HashCommands,
7160 GeoCommands,
7161 ListCommands,
7162 ScanCommands,
7163 SetCommands,
7164 StreamCommands,
7165 SortedSetCommands,
7166):
7167 """
7168 A class containing all of the implemented data access redis commands.
7169 This class is to be used as a mixin for synchronous Redis clients.
7170 """
7173class AsyncDataAccessCommands(
7174 AsyncBasicKeyCommands,
7175 AsyncHyperlogCommands,
7176 AsyncHashCommands,
7177 AsyncGeoCommands,
7178 AsyncListCommands,
7179 AsyncScanCommands,
7180 AsyncSetCommands,
7181 AsyncStreamCommands,
7182 AsyncSortedSetCommands,
7183):
7184 """
7185 A class containing all of the implemented data access redis commands.
7186 This class is to be used as a mixin for asynchronous Redis clients.
7187 """
7190class CoreCommands(
7191 ACLCommands,
7192 ClusterCommands,
7193 DataAccessCommands,
7194 ManagementCommands,
7195 ModuleCommands,
7196 PubSubCommands,
7197 ScriptCommands,
7198 FunctionCommands,
7199):
7200 """
7201 A class containing all of the implemented redis commands. This class is
7202 to be used as a mixin for synchronous Redis clients.
7203 """
7206class AsyncCoreCommands(
7207 AsyncACLCommands,
7208 AsyncClusterCommands,
7209 AsyncDataAccessCommands,
7210 AsyncManagementCommands,
7211 AsyncModuleCommands,
7212 AsyncPubSubCommands,
7213 AsyncScriptCommands,
7214 AsyncFunctionCommands,
7215):
7216 """
7217 A class containing all of the implemented redis commands. This class is
7218 to be used as a mixin for asynchronous Redis clients.
7219 """