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
5import inspect
7# Try to import the xxhash library as an optional dependency
8try:
9 import xxhash
11 HAS_XXHASH = True
12except ImportError:
13 HAS_XXHASH = False
15import warnings
16from enum import Enum
17from typing import (
18 TYPE_CHECKING,
19 Any,
20 AsyncIterator,
21 Awaitable,
22 Callable,
23 Dict,
24 Iterable,
25 Iterator,
26 List,
27 Literal,
28 Mapping,
29 Optional,
30 Sequence,
31 Set,
32 Tuple,
33 Union,
34)
36from redis.asyncio.observability.recorder import (
37 record_streaming_lag_from_response as async_record_streaming_lag,
38)
39from redis.exceptions import ConnectionError, DataError, NoScriptError, RedisError
40from redis.typing import (
41 AbsExpiryT,
42 AnyKeyT,
43 BitfieldOffsetT,
44 ChannelT,
45 CommandsProtocol,
46 ConsumerT,
47 EncodableT,
48 ExpiryT,
49 FieldT,
50 GroupT,
51 KeysT,
52 KeyT,
53 Number,
54 PatternT,
55 ResponseT,
56 ScriptTextT,
57 StreamIdT,
58 TimeoutSecT,
59 ZScoreBoundT,
60)
61from redis.utils import (
62 deprecated_function,
63 experimental_args,
64 experimental_method,
65 extract_expire_flags,
66 str_if_bytes,
67)
69from ..observability.attributes import PubSubDirection
70from ..observability.recorder import (
71 record_pubsub_message,
72 record_streaming_lag_from_response,
73)
74from .helpers import at_most_one_value_set, list_or_args
76if TYPE_CHECKING:
77 import redis.asyncio.client
78 import redis.client
81class ACLCommands(CommandsProtocol):
82 """
83 Redis Access Control List (ACL) commands.
84 see: https://redis.io/topics/acl
85 """
87 def acl_cat(self, category: Optional[str] = None, **kwargs) -> ResponseT:
88 """
89 Returns a list of categories or commands within a category.
91 If ``category`` is not supplied, returns a list of all categories.
92 If ``category`` is supplied, returns a list of all commands within
93 that category.
95 For more information, see https://redis.io/commands/acl-cat
96 """
97 pieces: list[EncodableT] = [category] if category else []
98 return self.execute_command("ACL CAT", *pieces, **kwargs)
100 def acl_dryrun(self, username, *args, **kwargs):
101 """
102 Simulate the execution of a given command by a given ``username``.
104 For more information, see https://redis.io/commands/acl-dryrun
105 """
106 return self.execute_command("ACL DRYRUN", username, *args, **kwargs)
108 def acl_deluser(self, *username: str, **kwargs) -> ResponseT:
109 """
110 Delete the ACL for the specified ``username``\\s
112 For more information, see https://redis.io/commands/acl-deluser
113 """
114 return self.execute_command("ACL DELUSER", *username, **kwargs)
116 def acl_genpass(self, bits: Optional[int] = None, **kwargs) -> ResponseT:
117 """Generate a random password value.
118 If ``bits`` is supplied then use this number of bits, rounded to
119 the next multiple of 4.
120 See: https://redis.io/commands/acl-genpass
121 """
122 pieces = []
123 if bits is not None:
124 try:
125 b = int(bits)
126 if b < 0 or b > 4096:
127 raise ValueError
128 pieces.append(b)
129 except ValueError:
130 raise DataError(
131 "genpass optionally accepts a bits argument, between 0 and 4096."
132 )
133 return self.execute_command("ACL GENPASS", *pieces, **kwargs)
135 def acl_getuser(self, username: str, **kwargs) -> ResponseT:
136 """
137 Get the ACL details for the specified ``username``.
139 If ``username`` does not exist, return None
141 For more information, see https://redis.io/commands/acl-getuser
142 """
143 return self.execute_command("ACL GETUSER", username, **kwargs)
145 def acl_help(self, **kwargs) -> ResponseT:
146 """The ACL HELP command returns helpful text describing
147 the different subcommands.
149 For more information, see https://redis.io/commands/acl-help
150 """
151 return self.execute_command("ACL HELP", **kwargs)
153 def acl_list(self, **kwargs) -> ResponseT:
154 """
155 Return a list of all ACLs on the server
157 For more information, see https://redis.io/commands/acl-list
158 """
159 return self.execute_command("ACL LIST", **kwargs)
161 def acl_log(self, count: Optional[int] = None, **kwargs) -> ResponseT:
162 """
163 Get ACL logs as a list.
164 :param int count: Get logs[0:count].
165 :rtype: List.
167 For more information, see https://redis.io/commands/acl-log
168 """
169 args = []
170 if count is not None:
171 if not isinstance(count, int):
172 raise DataError("ACL LOG count must be an integer")
173 args.append(count)
175 return self.execute_command("ACL LOG", *args, **kwargs)
177 def acl_log_reset(self, **kwargs) -> ResponseT:
178 """
179 Reset ACL logs.
180 :rtype: Boolean.
182 For more information, see https://redis.io/commands/acl-log
183 """
184 args = [b"RESET"]
185 return self.execute_command("ACL LOG", *args, **kwargs)
187 def acl_load(self, **kwargs) -> ResponseT:
188 """
189 Load ACL rules from the configured ``aclfile``.
191 Note that the server must be configured with the ``aclfile``
192 directive to be able to load ACL rules from an aclfile.
194 For more information, see https://redis.io/commands/acl-load
195 """
196 return self.execute_command("ACL LOAD", **kwargs)
198 def acl_save(self, **kwargs) -> ResponseT:
199 """
200 Save ACL rules to the configured ``aclfile``.
202 Note that the server must be configured with the ``aclfile``
203 directive to be able to save ACL rules to an aclfile.
205 For more information, see https://redis.io/commands/acl-save
206 """
207 return self.execute_command("ACL SAVE", **kwargs)
209 def acl_setuser(
210 self,
211 username: str,
212 enabled: bool = False,
213 nopass: bool = False,
214 passwords: Optional[Union[str, Iterable[str]]] = None,
215 hashed_passwords: Optional[Union[str, Iterable[str]]] = None,
216 categories: Optional[Iterable[str]] = None,
217 commands: Optional[Iterable[str]] = None,
218 keys: Optional[Iterable[KeyT]] = None,
219 channels: Optional[Iterable[ChannelT]] = None,
220 selectors: Optional[Iterable[Tuple[str, KeyT]]] = None,
221 reset: bool = False,
222 reset_keys: bool = False,
223 reset_channels: bool = False,
224 reset_passwords: bool = False,
225 **kwargs,
226 ) -> ResponseT:
227 """
228 Create or update an ACL user.
230 Create or update the ACL for `username`. If the user already exists,
231 the existing ACL is completely overwritten and replaced with the
232 specified values.
234 For more information, see https://redis.io/commands/acl-setuser
236 Args:
237 username: The name of the user whose ACL is to be created or updated.
238 enabled: Indicates whether the user should be allowed to authenticate.
239 Defaults to `False`.
240 nopass: Indicates whether the user can authenticate without a password.
241 This cannot be `True` if `passwords` are also specified.
242 passwords: A list of plain text passwords to add to or remove from the user.
243 Each password must be prefixed with a '+' to add or a '-' to
244 remove. For convenience, a single prefixed string can be used
245 when adding or removing a single password.
246 hashed_passwords: A list of SHA-256 hashed passwords to add to or remove
247 from the user. Each hashed password must be prefixed with
248 a '+' to add or a '-' to remove. For convenience, a single
249 prefixed string can be used when adding or removing a
250 single password.
251 categories: A list of strings representing category permissions. Each string
252 must be prefixed with either a '+' to add the category
253 permission or a '-' to remove the category permission.
254 commands: A list of strings representing command permissions. Each string
255 must be prefixed with either a '+' to add the command permission
256 or a '-' to remove the command permission.
257 keys: A list of key patterns to grant the user access to. Key patterns allow
258 ``'*'`` to support wildcard matching. For example, ``'*'`` grants
259 access to all keys while ``'cache:*'`` grants access to all keys that
260 are prefixed with ``cache:``.
261 `keys` should not be prefixed with a ``'~'``.
262 reset: Indicates whether the user should be fully reset prior to applying
263 the new ACL. Setting this to `True` will remove all existing
264 passwords, flags, and privileges from the user and then apply the
265 specified rules. If `False`, the user's existing passwords, flags,
266 and privileges will be kept and any new specified rules will be
267 applied on top.
268 reset_keys: Indicates whether the user's key permissions should be reset
269 prior to applying any new key permissions specified in `keys`.
270 If `False`, the user's existing key permissions will be kept and
271 any new specified key permissions will be applied on top.
272 reset_channels: Indicates whether the user's channel permissions should be
273 reset prior to applying any new channel permissions
274 specified in `channels`. If `False`, the user's existing
275 channel permissions will be kept and any new specified
276 channel permissions will be applied on top.
277 reset_passwords: Indicates whether to remove all existing passwords and the
278 `nopass` flag from the user prior to applying any new
279 passwords specified in `passwords` or `hashed_passwords`.
280 If `False`, the user's existing passwords and `nopass`
281 status will be kept and any new specified passwords or
282 hashed passwords will be applied on top.
283 """
284 encoder = self.get_encoder()
285 pieces: List[EncodableT] = [username]
287 if reset:
288 pieces.append(b"reset")
290 if reset_keys:
291 pieces.append(b"resetkeys")
293 if reset_channels:
294 pieces.append(b"resetchannels")
296 if reset_passwords:
297 pieces.append(b"resetpass")
299 if enabled:
300 pieces.append(b"on")
301 else:
302 pieces.append(b"off")
304 if (passwords or hashed_passwords) and nopass:
305 raise DataError(
306 "Cannot set 'nopass' and supply 'passwords' or 'hashed_passwords'"
307 )
309 if passwords:
310 # as most users will have only one password, allow remove_passwords
311 # to be specified as a simple string or a list
312 passwords = list_or_args(passwords, [])
313 for i, password in enumerate(passwords):
314 password = encoder.encode(password)
315 if password.startswith(b"+"):
316 pieces.append(b">%s" % password[1:])
317 elif password.startswith(b"-"):
318 pieces.append(b"<%s" % password[1:])
319 else:
320 raise DataError(
321 f"Password {i} must be prefixed with a "
322 f'"+" to add or a "-" to remove'
323 )
325 if hashed_passwords:
326 # as most users will have only one password, allow remove_passwords
327 # to be specified as a simple string or a list
328 hashed_passwords = list_or_args(hashed_passwords, [])
329 for i, hashed_password in enumerate(hashed_passwords):
330 hashed_password = encoder.encode(hashed_password)
331 if hashed_password.startswith(b"+"):
332 pieces.append(b"#%s" % hashed_password[1:])
333 elif hashed_password.startswith(b"-"):
334 pieces.append(b"!%s" % hashed_password[1:])
335 else:
336 raise DataError(
337 f"Hashed password {i} must be prefixed with a "
338 f'"+" to add or a "-" to remove'
339 )
341 if nopass:
342 pieces.append(b"nopass")
344 if categories:
345 for category in categories:
346 category = encoder.encode(category)
347 # categories can be prefixed with one of (+@, +, -@, -)
348 if category.startswith(b"+@"):
349 pieces.append(category)
350 elif category.startswith(b"+"):
351 pieces.append(b"+@%s" % category[1:])
352 elif category.startswith(b"-@"):
353 pieces.append(category)
354 elif category.startswith(b"-"):
355 pieces.append(b"-@%s" % category[1:])
356 else:
357 raise DataError(
358 f'Category "{encoder.decode(category, force=True)}" '
359 'must be prefixed with "+" or "-"'
360 )
361 if commands:
362 for cmd in commands:
363 cmd = encoder.encode(cmd)
364 if not cmd.startswith(b"+") and not cmd.startswith(b"-"):
365 raise DataError(
366 f'Command "{encoder.decode(cmd, force=True)}" '
367 'must be prefixed with "+" or "-"'
368 )
369 pieces.append(cmd)
371 if keys:
372 for key in keys:
373 key = encoder.encode(key)
374 if not key.startswith(b"%") and not key.startswith(b"~"):
375 key = b"~%s" % key
376 pieces.append(key)
378 if channels:
379 for channel in channels:
380 channel = encoder.encode(channel)
381 pieces.append(b"&%s" % channel)
383 if selectors:
384 for cmd, key in selectors:
385 cmd = encoder.encode(cmd)
386 if not cmd.startswith(b"+") and not cmd.startswith(b"-"):
387 raise DataError(
388 f'Command "{encoder.decode(cmd, force=True)}" '
389 'must be prefixed with "+" or "-"'
390 )
392 key = encoder.encode(key)
393 if not key.startswith(b"%") and not key.startswith(b"~"):
394 key = b"~%s" % key
396 pieces.append(b"(%s %s)" % (cmd, key))
398 return self.execute_command("ACL SETUSER", *pieces, **kwargs)
400 def acl_users(self, **kwargs) -> ResponseT:
401 """Returns a list of all registered users on the server.
403 For more information, see https://redis.io/commands/acl-users
404 """
405 return self.execute_command("ACL USERS", **kwargs)
407 def acl_whoami(self, **kwargs) -> ResponseT:
408 """Get the username for the current connection
410 For more information, see https://redis.io/commands/acl-whoami
411 """
412 return self.execute_command("ACL WHOAMI", **kwargs)
415AsyncACLCommands = ACLCommands
418class HotkeysMetricsTypes(Enum):
419 CPU = "CPU"
420 NET = "NET"
423class ManagementCommands(CommandsProtocol):
424 """
425 Redis management commands
426 """
428 def auth(self, password: str, username: Optional[str] = None, **kwargs):
429 """
430 Authenticates the user. If you do not pass username, Redis will try to
431 authenticate for the "default" user. If you do pass username, it will
432 authenticate for the given user.
433 For more information, see https://redis.io/commands/auth
434 """
435 pieces = []
436 if username is not None:
437 pieces.append(username)
438 pieces.append(password)
439 return self.execute_command("AUTH", *pieces, **kwargs)
441 def bgrewriteaof(self, **kwargs):
442 """Tell the Redis server to rewrite the AOF file from data in memory.
444 For more information, see https://redis.io/commands/bgrewriteaof
445 """
446 return self.execute_command("BGREWRITEAOF", **kwargs)
448 def bgsave(self, schedule: bool = True, **kwargs) -> ResponseT:
449 """
450 Tell the Redis server to save its data to disk. Unlike save(),
451 this method is asynchronous and returns immediately.
453 For more information, see https://redis.io/commands/bgsave
454 """
455 pieces = []
456 if schedule:
457 pieces.append("SCHEDULE")
458 return self.execute_command("BGSAVE", *pieces, **kwargs)
460 def role(self) -> ResponseT:
461 """
462 Provide information on the role of a Redis instance in
463 the context of replication, by returning if the instance
464 is currently a master, slave, or sentinel.
466 For more information, see https://redis.io/commands/role
467 """
468 return self.execute_command("ROLE")
470 def client_kill(self, address: str, **kwargs) -> ResponseT:
471 """Disconnects the client at ``address`` (ip:port)
473 For more information, see https://redis.io/commands/client-kill
474 """
475 return self.execute_command("CLIENT KILL", address, **kwargs)
477 def client_kill_filter(
478 self,
479 _id: Optional[str] = None,
480 _type: Optional[str] = None,
481 addr: Optional[str] = None,
482 skipme: Optional[bool] = None,
483 laddr: Optional[bool] = None,
484 user: Optional[str] = None,
485 maxage: Optional[int] = None,
486 **kwargs,
487 ) -> ResponseT:
488 """
489 Disconnects client(s) using a variety of filter options
490 :param _id: Kills a client by its unique ID field
491 :param _type: Kills a client by type where type is one of 'normal',
492 'master', 'slave' or 'pubsub'
493 :param addr: Kills a client by its 'address:port'
494 :param skipme: If True, then the client calling the command
495 will not get killed even if it is identified by one of the filter
496 options. If skipme is not provided, the server defaults to skipme=True
497 :param laddr: Kills a client by its 'local (bind) address:port'
498 :param user: Kills a client for a specific user name
499 :param maxage: Kills clients that are older than the specified age in seconds
500 """
501 args = []
502 if _type is not None:
503 client_types = ("normal", "master", "slave", "pubsub")
504 if str(_type).lower() not in client_types:
505 raise DataError(f"CLIENT KILL type must be one of {client_types!r}")
506 args.extend((b"TYPE", _type))
507 if skipme is not None:
508 if not isinstance(skipme, bool):
509 raise DataError("CLIENT KILL skipme must be a bool")
510 if skipme:
511 args.extend((b"SKIPME", b"YES"))
512 else:
513 args.extend((b"SKIPME", b"NO"))
514 if _id is not None:
515 args.extend((b"ID", _id))
516 if addr is not None:
517 args.extend((b"ADDR", addr))
518 if laddr is not None:
519 args.extend((b"LADDR", laddr))
520 if user is not None:
521 args.extend((b"USER", user))
522 if maxage is not None:
523 args.extend((b"MAXAGE", maxage))
524 if not args:
525 raise DataError(
526 "CLIENT KILL <filter> <value> ... ... <filter> "
527 "<value> must specify at least one filter"
528 )
529 return self.execute_command("CLIENT KILL", *args, **kwargs)
531 def client_info(self, **kwargs) -> ResponseT:
532 """
533 Returns information and statistics about the current
534 client connection.
536 For more information, see https://redis.io/commands/client-info
537 """
538 return self.execute_command("CLIENT INFO", **kwargs)
540 def client_list(
541 self, _type: Optional[str] = None, client_id: List[EncodableT] = [], **kwargs
542 ) -> ResponseT:
543 """
544 Returns a list of currently connected clients.
545 If type of client specified, only that type will be returned.
547 :param _type: optional. one of the client types (normal, master,
548 replica, pubsub)
549 :param client_id: optional. a list of client ids
551 For more information, see https://redis.io/commands/client-list
552 """
553 args = []
554 if _type is not None:
555 client_types = ("normal", "master", "replica", "pubsub")
556 if str(_type).lower() not in client_types:
557 raise DataError(f"CLIENT LIST _type must be one of {client_types!r}")
558 args.append(b"TYPE")
559 args.append(_type)
560 if not isinstance(client_id, list):
561 raise DataError("client_id must be a list")
562 if client_id:
563 args.append(b"ID")
564 args += client_id
565 return self.execute_command("CLIENT LIST", *args, **kwargs)
567 def client_getname(self, **kwargs) -> ResponseT:
568 """
569 Returns the current connection name
571 For more information, see https://redis.io/commands/client-getname
572 """
573 return self.execute_command("CLIENT GETNAME", **kwargs)
575 def client_getredir(self, **kwargs) -> ResponseT:
576 """
577 Returns the ID (an integer) of the client to whom we are
578 redirecting tracking notifications.
580 see: https://redis.io/commands/client-getredir
581 """
582 return self.execute_command("CLIENT GETREDIR", **kwargs)
584 def client_reply(
585 self, reply: Union[Literal["ON"], Literal["OFF"], Literal["SKIP"]], **kwargs
586 ) -> ResponseT:
587 """
588 Enable and disable redis server replies.
590 ``reply`` Must be ON OFF or SKIP,
591 ON - The default most with server replies to commands
592 OFF - Disable server responses to commands
593 SKIP - Skip the response of the immediately following command.
595 Note: When setting OFF or SKIP replies, you will need a client object
596 with a timeout specified in seconds, and will need to catch the
597 TimeoutError.
598 The test_client_reply unit test illustrates this, and
599 conftest.py has a client with a timeout.
601 See https://redis.io/commands/client-reply
602 """
603 replies = ["ON", "OFF", "SKIP"]
604 if reply not in replies:
605 raise DataError(f"CLIENT REPLY must be one of {replies!r}")
606 return self.execute_command("CLIENT REPLY", reply, **kwargs)
608 def client_id(self, **kwargs) -> ResponseT:
609 """
610 Returns the current connection id
612 For more information, see https://redis.io/commands/client-id
613 """
614 return self.execute_command("CLIENT ID", **kwargs)
616 def client_tracking_on(
617 self,
618 clientid: Optional[int] = None,
619 prefix: Sequence[KeyT] = [],
620 bcast: bool = False,
621 optin: bool = False,
622 optout: bool = False,
623 noloop: bool = False,
624 ) -> ResponseT:
625 """
626 Turn on the tracking mode.
627 For more information, about the options look at client_tracking func.
629 See https://redis.io/commands/client-tracking
630 """
631 return self.client_tracking(
632 True, clientid, prefix, bcast, optin, optout, noloop
633 )
635 def client_tracking_off(
636 self,
637 clientid: Optional[int] = None,
638 prefix: Sequence[KeyT] = [],
639 bcast: bool = False,
640 optin: bool = False,
641 optout: bool = False,
642 noloop: bool = False,
643 ) -> ResponseT:
644 """
645 Turn off the tracking mode.
646 For more information, about the options look at client_tracking func.
648 See https://redis.io/commands/client-tracking
649 """
650 return self.client_tracking(
651 False, clientid, prefix, bcast, optin, optout, noloop
652 )
654 def client_tracking(
655 self,
656 on: bool = True,
657 clientid: Optional[int] = None,
658 prefix: Sequence[KeyT] = [],
659 bcast: bool = False,
660 optin: bool = False,
661 optout: bool = False,
662 noloop: bool = False,
663 **kwargs,
664 ) -> ResponseT:
665 """
666 Enables the tracking feature of the Redis server, that is used
667 for server assisted client side caching.
669 ``on`` indicate for tracking on or tracking off. The default is on.
671 ``clientid`` send invalidation messages to the connection with
672 the specified ID.
674 ``bcast`` enable tracking in broadcasting mode. In this mode
675 invalidation messages are reported for all the prefixes
676 specified, regardless of the keys requested by the connection.
678 ``optin`` when broadcasting is NOT active, normally don't track
679 keys in read only commands, unless they are called immediately
680 after a CLIENT CACHING yes command.
682 ``optout`` when broadcasting is NOT active, normally track keys in
683 read only commands, unless they are called immediately after a
684 CLIENT CACHING no command.
686 ``noloop`` don't send notifications about keys modified by this
687 connection itself.
689 ``prefix`` for broadcasting, register a given key prefix, so that
690 notifications will be provided only for keys starting with this string.
692 See https://redis.io/commands/client-tracking
693 """
695 if len(prefix) != 0 and bcast is False:
696 raise DataError("Prefix can only be used with bcast")
698 pieces = ["ON"] if on else ["OFF"]
699 if clientid is not None:
700 pieces.extend(["REDIRECT", clientid])
701 for p in prefix:
702 pieces.extend(["PREFIX", p])
703 if bcast:
704 pieces.append("BCAST")
705 if optin:
706 pieces.append("OPTIN")
707 if optout:
708 pieces.append("OPTOUT")
709 if noloop:
710 pieces.append("NOLOOP")
712 return self.execute_command("CLIENT TRACKING", *pieces, **kwargs)
714 def client_trackinginfo(self, **kwargs) -> ResponseT:
715 """
716 Returns the information about the current client connection's
717 use of the server assisted client side cache.
719 See https://redis.io/commands/client-trackinginfo
720 """
721 return self.execute_command("CLIENT TRACKINGINFO", **kwargs)
723 def client_setname(self, name: str, **kwargs) -> ResponseT:
724 """
725 Sets the current connection name
727 For more information, see https://redis.io/commands/client-setname
729 .. note::
730 This method sets client name only for **current** connection.
732 If you want to set a common name for all connections managed
733 by this client, use ``client_name`` constructor argument.
734 """
735 return self.execute_command("CLIENT SETNAME", name, **kwargs)
737 def client_setinfo(self, attr: str, value: str, **kwargs) -> ResponseT:
738 """
739 Sets the current connection library name or version
740 For mor information see https://redis.io/commands/client-setinfo
741 """
742 return self.execute_command("CLIENT SETINFO", attr, value, **kwargs)
744 def client_unblock(
745 self, client_id: int, error: bool = False, **kwargs
746 ) -> ResponseT:
747 """
748 Unblocks a connection by its client id.
749 If ``error`` is True, unblocks the client with a special error message.
750 If ``error`` is False (default), the client is unblocked using the
751 regular timeout mechanism.
753 For more information, see https://redis.io/commands/client-unblock
754 """
755 args = ["CLIENT UNBLOCK", int(client_id)]
756 if error:
757 args.append(b"ERROR")
758 return self.execute_command(*args, **kwargs)
760 def client_pause(self, timeout: int, all: bool = True, **kwargs) -> ResponseT:
761 """
762 Suspend all the Redis clients for the specified amount of time.
765 For more information, see https://redis.io/commands/client-pause
767 Args:
768 timeout: milliseconds to pause clients
769 all: If true (default) all client commands are blocked.
770 otherwise, clients are only blocked if they attempt to execute
771 a write command.
773 For the WRITE mode, some commands have special behavior:
775 * EVAL/EVALSHA: Will block client for all scripts.
776 * PUBLISH: Will block client.
777 * PFCOUNT: Will block client.
778 * WAIT: Acknowledgments will be delayed, so this command will
779 appear blocked.
780 """
781 args = ["CLIENT PAUSE", str(timeout)]
782 if not isinstance(timeout, int):
783 raise DataError("CLIENT PAUSE timeout must be an integer")
784 if not all:
785 args.append("WRITE")
786 return self.execute_command(*args, **kwargs)
788 def client_unpause(self, **kwargs) -> ResponseT:
789 """
790 Unpause all redis clients
792 For more information, see https://redis.io/commands/client-unpause
793 """
794 return self.execute_command("CLIENT UNPAUSE", **kwargs)
796 def client_no_evict(self, mode: str) -> Union[Awaitable[str], str]:
797 """
798 Sets the client eviction mode for the current connection.
800 For more information, see https://redis.io/commands/client-no-evict
801 """
802 return self.execute_command("CLIENT NO-EVICT", mode)
804 def client_no_touch(self, mode: str) -> Union[Awaitable[str], str]:
805 """
806 # The command controls whether commands sent by the client will alter
807 # the LRU/LFU of the keys they access.
808 # When turned on, the current client will not change LFU/LRU stats,
809 # unless it sends the TOUCH command.
811 For more information, see https://redis.io/commands/client-no-touch
812 """
813 return self.execute_command("CLIENT NO-TOUCH", mode)
815 def command(self, **kwargs):
816 """
817 Returns dict reply of details about all Redis commands.
819 For more information, see https://redis.io/commands/command
820 """
821 return self.execute_command("COMMAND", **kwargs)
823 def command_info(self, **kwargs) -> None:
824 raise NotImplementedError(
825 "COMMAND INFO is intentionally not implemented in the client."
826 )
828 def command_count(self, **kwargs) -> ResponseT:
829 return self.execute_command("COMMAND COUNT", **kwargs)
831 def command_list(
832 self,
833 module: Optional[str] = None,
834 category: Optional[str] = None,
835 pattern: Optional[str] = None,
836 ) -> ResponseT:
837 """
838 Return an array of the server's command names.
839 You can use one of the following filters:
840 ``module``: get the commands that belong to the module
841 ``category``: get the commands in the ACL category
842 ``pattern``: get the commands that match the given pattern
844 For more information, see https://redis.io/commands/command-list/
845 """
846 pieces = []
847 if module is not None:
848 pieces.extend(["MODULE", module])
849 if category is not None:
850 pieces.extend(["ACLCAT", category])
851 if pattern is not None:
852 pieces.extend(["PATTERN", pattern])
854 if pieces:
855 pieces.insert(0, "FILTERBY")
857 return self.execute_command("COMMAND LIST", *pieces)
859 def command_getkeysandflags(self, *args: str) -> List[Union[str, List[str]]]:
860 """
861 Returns array of keys from a full Redis command and their usage flags.
863 For more information, see https://redis.io/commands/command-getkeysandflags
864 """
865 return self.execute_command("COMMAND GETKEYSANDFLAGS", *args)
867 def command_docs(self, *args):
868 """
869 This function throws a NotImplementedError since it is intentionally
870 not supported.
871 """
872 raise NotImplementedError(
873 "COMMAND DOCS is intentionally not implemented in the client."
874 )
876 def config_get(
877 self, pattern: PatternT = "*", *args: PatternT, **kwargs
878 ) -> ResponseT:
879 """
880 Return a dictionary of configuration based on the ``pattern``
882 For more information, see https://redis.io/commands/config-get
883 """
884 return self.execute_command("CONFIG GET", pattern, *args, **kwargs)
886 def config_set(
887 self,
888 name: KeyT,
889 value: EncodableT,
890 *args: Union[KeyT, EncodableT],
891 **kwargs,
892 ) -> ResponseT:
893 """Set config item ``name`` with ``value``
895 For more information, see https://redis.io/commands/config-set
896 """
897 return self.execute_command("CONFIG SET", name, value, *args, **kwargs)
899 def config_resetstat(self, **kwargs) -> ResponseT:
900 """
901 Reset runtime statistics
903 For more information, see https://redis.io/commands/config-resetstat
904 """
905 return self.execute_command("CONFIG RESETSTAT", **kwargs)
907 def config_rewrite(self, **kwargs) -> ResponseT:
908 """
909 Rewrite config file with the minimal change to reflect running config.
911 For more information, see https://redis.io/commands/config-rewrite
912 """
913 return self.execute_command("CONFIG REWRITE", **kwargs)
915 def dbsize(self, **kwargs) -> ResponseT:
916 """
917 Returns the number of keys in the current database
919 For more information, see https://redis.io/commands/dbsize
920 """
921 return self.execute_command("DBSIZE", **kwargs)
923 def debug_object(self, key: KeyT, **kwargs) -> ResponseT:
924 """
925 Returns version specific meta information about a given key
927 For more information, see https://redis.io/commands/debug-object
928 """
929 return self.execute_command("DEBUG OBJECT", key, **kwargs)
931 def debug_segfault(self, **kwargs) -> None:
932 raise NotImplementedError(
933 """
934 DEBUG SEGFAULT is intentionally not implemented in the client.
936 For more information, see https://redis.io/commands/debug-segfault
937 """
938 )
940 def echo(self, value: EncodableT, **kwargs) -> ResponseT:
941 """
942 Echo the string back from the server
944 For more information, see https://redis.io/commands/echo
945 """
946 return self.execute_command("ECHO", value, **kwargs)
948 def flushall(self, asynchronous: bool = False, **kwargs) -> ResponseT:
949 """
950 Delete all keys in all databases on the current host.
952 ``asynchronous`` indicates whether the operation is
953 executed asynchronously by the server.
955 For more information, see https://redis.io/commands/flushall
956 """
957 args = []
958 if asynchronous:
959 args.append(b"ASYNC")
960 return self.execute_command("FLUSHALL", *args, **kwargs)
962 def flushdb(self, asynchronous: bool = False, **kwargs) -> ResponseT:
963 """
964 Delete all keys in the current database.
966 ``asynchronous`` indicates whether the operation is
967 executed asynchronously by the server.
969 For more information, see https://redis.io/commands/flushdb
970 """
971 args = []
972 if asynchronous:
973 args.append(b"ASYNC")
974 return self.execute_command("FLUSHDB", *args, **kwargs)
976 def sync(self) -> ResponseT:
977 """
978 Initiates a replication stream from the master.
980 For more information, see https://redis.io/commands/sync
981 """
982 from redis.client import NEVER_DECODE
984 options = {}
985 options[NEVER_DECODE] = []
986 return self.execute_command("SYNC", **options)
988 def psync(self, replicationid: str, offset: int):
989 """
990 Initiates a replication stream from the master.
991 Newer version for `sync`.
993 For more information, see https://redis.io/commands/sync
994 """
995 from redis.client import NEVER_DECODE
997 options = {}
998 options[NEVER_DECODE] = []
999 return self.execute_command("PSYNC", replicationid, offset, **options)
1001 def swapdb(self, first: int, second: int, **kwargs) -> ResponseT:
1002 """
1003 Swap two databases
1005 For more information, see https://redis.io/commands/swapdb
1006 """
1007 return self.execute_command("SWAPDB", first, second, **kwargs)
1009 def select(self, index: int, **kwargs) -> ResponseT:
1010 """Select the Redis logical database at index.
1012 See: https://redis.io/commands/select
1013 """
1014 return self.execute_command("SELECT", index, **kwargs)
1016 def info(self, section: Optional[str] = None, *args: str, **kwargs) -> ResponseT:
1017 """
1018 Returns a dictionary containing information about the Redis server
1020 The ``section`` option can be used to select a specific section
1021 of information
1023 The section option is not supported by older versions of Redis Server,
1024 and will generate ResponseError
1026 For more information, see https://redis.io/commands/info
1027 """
1028 if section is None:
1029 return self.execute_command("INFO", **kwargs)
1030 else:
1031 return self.execute_command("INFO", section, *args, **kwargs)
1033 def lastsave(self, **kwargs) -> ResponseT:
1034 """
1035 Return a Python datetime object representing the last time the
1036 Redis database was saved to disk
1038 For more information, see https://redis.io/commands/lastsave
1039 """
1040 return self.execute_command("LASTSAVE", **kwargs)
1042 def latency_doctor(self):
1043 """Raise a NotImplementedError, as the client will not support LATENCY DOCTOR.
1044 This function is best used within the redis-cli.
1046 For more information, see https://redis.io/commands/latency-doctor
1047 """
1048 raise NotImplementedError(
1049 """
1050 LATENCY DOCTOR is intentionally not implemented in the client.
1052 For more information, see https://redis.io/commands/latency-doctor
1053 """
1054 )
1056 def latency_graph(self):
1057 """Raise a NotImplementedError, as the client will not support LATENCY GRAPH.
1058 This function is best used within the redis-cli.
1060 For more information, see https://redis.io/commands/latency-graph.
1061 """
1062 raise NotImplementedError(
1063 """
1064 LATENCY GRAPH is intentionally not implemented in the client.
1066 For more information, see https://redis.io/commands/latency-graph
1067 """
1068 )
1070 def lolwut(self, *version_numbers: Union[str, float], **kwargs) -> ResponseT:
1071 """
1072 Get the Redis version and a piece of generative computer art
1074 See: https://redis.io/commands/lolwut
1075 """
1076 if version_numbers:
1077 return self.execute_command("LOLWUT VERSION", *version_numbers, **kwargs)
1078 else:
1079 return self.execute_command("LOLWUT", **kwargs)
1081 def reset(self) -> ResponseT:
1082 """Perform a full reset on the connection's server-side context.
1084 See: https://redis.io/commands/reset
1085 """
1086 return self.execute_command("RESET")
1088 def migrate(
1089 self,
1090 host: str,
1091 port: int,
1092 keys: KeysT,
1093 destination_db: int,
1094 timeout: int,
1095 copy: bool = False,
1096 replace: bool = False,
1097 auth: Optional[str] = None,
1098 **kwargs,
1099 ) -> ResponseT:
1100 """
1101 Migrate 1 or more keys from the current Redis server to a different
1102 server specified by the ``host``, ``port`` and ``destination_db``.
1104 The ``timeout``, specified in milliseconds, indicates the maximum
1105 time the connection between the two servers can be idle before the
1106 command is interrupted.
1108 If ``copy`` is True, the specified ``keys`` are NOT deleted from
1109 the source server.
1111 If ``replace`` is True, this operation will overwrite the keys
1112 on the destination server if they exist.
1114 If ``auth`` is specified, authenticate to the destination server with
1115 the password provided.
1117 For more information, see https://redis.io/commands/migrate
1118 """
1119 keys = list_or_args(keys, [])
1120 if not keys:
1121 raise DataError("MIGRATE requires at least one key")
1122 pieces = []
1123 if copy:
1124 pieces.append(b"COPY")
1125 if replace:
1126 pieces.append(b"REPLACE")
1127 if auth:
1128 pieces.append(b"AUTH")
1129 pieces.append(auth)
1130 pieces.append(b"KEYS")
1131 pieces.extend(keys)
1132 return self.execute_command(
1133 "MIGRATE", host, port, "", destination_db, timeout, *pieces, **kwargs
1134 )
1136 def object(self, infotype: str, key: KeyT, **kwargs) -> ResponseT:
1137 """
1138 Return the encoding, idletime, or refcount about the key
1139 """
1140 return self.execute_command(
1141 "OBJECT", infotype, key, infotype=infotype, **kwargs
1142 )
1144 def memory_doctor(self, **kwargs) -> None:
1145 raise NotImplementedError(
1146 """
1147 MEMORY DOCTOR is intentionally not implemented in the client.
1149 For more information, see https://redis.io/commands/memory-doctor
1150 """
1151 )
1153 def memory_help(self, **kwargs) -> None:
1154 raise NotImplementedError(
1155 """
1156 MEMORY HELP is intentionally not implemented in the client.
1158 For more information, see https://redis.io/commands/memory-help
1159 """
1160 )
1162 def memory_stats(self, **kwargs) -> ResponseT:
1163 """
1164 Return a dictionary of memory stats
1166 For more information, see https://redis.io/commands/memory-stats
1167 """
1168 return self.execute_command("MEMORY STATS", **kwargs)
1170 def memory_malloc_stats(self, **kwargs) -> ResponseT:
1171 """
1172 Return an internal statistics report from the memory allocator.
1174 See: https://redis.io/commands/memory-malloc-stats
1175 """
1176 return self.execute_command("MEMORY MALLOC-STATS", **kwargs)
1178 def memory_usage(
1179 self, key: KeyT, samples: Optional[int] = None, **kwargs
1180 ) -> ResponseT:
1181 """
1182 Return the total memory usage for key, its value and associated
1183 administrative overheads.
1185 For nested data structures, ``samples`` is the number of elements to
1186 sample. If left unspecified, the server's default is 5. Use 0 to sample
1187 all elements.
1189 For more information, see https://redis.io/commands/memory-usage
1190 """
1191 args = []
1192 if isinstance(samples, int):
1193 args.extend([b"SAMPLES", samples])
1194 return self.execute_command("MEMORY USAGE", key, *args, **kwargs)
1196 def memory_purge(self, **kwargs) -> ResponseT:
1197 """
1198 Attempts to purge dirty pages for reclamation by allocator
1200 For more information, see https://redis.io/commands/memory-purge
1201 """
1202 return self.execute_command("MEMORY PURGE", **kwargs)
1204 def latency_histogram(self, *args):
1205 """
1206 This function throws a NotImplementedError since it is intentionally
1207 not supported.
1208 """
1209 raise NotImplementedError(
1210 "LATENCY HISTOGRAM is intentionally not implemented in the client."
1211 )
1213 def latency_history(self, event: str) -> ResponseT:
1214 """
1215 Returns the raw data of the ``event``'s latency spikes time series.
1217 For more information, see https://redis.io/commands/latency-history
1218 """
1219 return self.execute_command("LATENCY HISTORY", event)
1221 def latency_latest(self) -> ResponseT:
1222 """
1223 Reports the latest latency events logged.
1225 For more information, see https://redis.io/commands/latency-latest
1226 """
1227 return self.execute_command("LATENCY LATEST")
1229 def latency_reset(self, *events: str) -> ResponseT:
1230 """
1231 Resets the latency spikes time series of all, or only some, events.
1233 For more information, see https://redis.io/commands/latency-reset
1234 """
1235 return self.execute_command("LATENCY RESET", *events)
1237 def ping(self, **kwargs) -> Union[Awaitable[bool], bool]:
1238 """
1239 Ping the Redis server to test connectivity.
1241 Sends a PING command to the Redis server and returns True if the server
1242 responds with "PONG".
1244 This command is useful for:
1245 - Testing whether a connection is still alive
1246 - Verifying the server's ability to serve data
1248 For more information on the underlying ping command see https://redis.io/commands/ping
1249 """
1250 return self.execute_command("PING", **kwargs)
1252 def quit(self, **kwargs) -> ResponseT:
1253 """
1254 Ask the server to close the connection.
1256 For more information, see https://redis.io/commands/quit
1257 """
1258 return self.execute_command("QUIT", **kwargs)
1260 def replicaof(self, *args, **kwargs) -> ResponseT:
1261 """
1262 Update the replication settings of a redis replica, on the fly.
1264 Examples of valid arguments include:
1266 NO ONE (set no replication)
1267 host port (set to the host and port of a redis server)
1269 For more information, see https://redis.io/commands/replicaof
1270 """
1271 return self.execute_command("REPLICAOF", *args, **kwargs)
1273 def save(self, **kwargs) -> ResponseT:
1274 """
1275 Tell the Redis server to save its data to disk,
1276 blocking until the save is complete
1278 For more information, see https://redis.io/commands/save
1279 """
1280 return self.execute_command("SAVE", **kwargs)
1282 def shutdown(
1283 self,
1284 save: bool = False,
1285 nosave: bool = False,
1286 now: bool = False,
1287 force: bool = False,
1288 abort: bool = False,
1289 **kwargs,
1290 ) -> None:
1291 """Shutdown the Redis server. If Redis has persistence configured,
1292 data will be flushed before shutdown.
1293 It is possible to specify modifiers to alter the behavior of the command:
1294 ``save`` will force a DB saving operation even if no save points are configured.
1295 ``nosave`` will prevent a DB saving operation even if one or more save points
1296 are configured.
1297 ``now`` skips waiting for lagging replicas, i.e. it bypasses the first step in
1298 the shutdown sequence.
1299 ``force`` ignores any errors that would normally prevent the server from exiting
1300 ``abort`` cancels an ongoing shutdown and cannot be combined with other flags.
1302 For more information, see https://redis.io/commands/shutdown
1303 """
1304 if save and nosave:
1305 raise DataError("SHUTDOWN save and nosave cannot both be set")
1306 args = ["SHUTDOWN"]
1307 if save:
1308 args.append("SAVE")
1309 if nosave:
1310 args.append("NOSAVE")
1311 if now:
1312 args.append("NOW")
1313 if force:
1314 args.append("FORCE")
1315 if abort:
1316 args.append("ABORT")
1317 try:
1318 self.execute_command(*args, **kwargs)
1319 except ConnectionError:
1320 # a ConnectionError here is expected
1321 return
1322 raise RedisError("SHUTDOWN seems to have failed.")
1324 def slaveof(
1325 self, host: Optional[str] = None, port: Optional[int] = None, **kwargs
1326 ) -> ResponseT:
1327 """
1328 Set the server to be a replicated slave of the instance identified
1329 by the ``host`` and ``port``. If called without arguments, the
1330 instance is promoted to a master instead.
1332 For more information, see https://redis.io/commands/slaveof
1333 """
1334 if host is None and port is None:
1335 return self.execute_command("SLAVEOF", b"NO", b"ONE", **kwargs)
1336 return self.execute_command("SLAVEOF", host, port, **kwargs)
1338 def slowlog_get(self, num: Optional[int] = None, **kwargs) -> ResponseT:
1339 """
1340 Get the entries from the slowlog. If ``num`` is specified, get the
1341 most recent ``num`` items.
1343 For more information, see https://redis.io/commands/slowlog-get
1344 """
1345 from redis.client import NEVER_DECODE
1347 args = ["SLOWLOG GET"]
1348 if num is not None:
1349 args.append(num)
1350 decode_responses = self.get_connection_kwargs().get("decode_responses", False)
1351 if decode_responses is True:
1352 kwargs[NEVER_DECODE] = []
1353 return self.execute_command(*args, **kwargs)
1355 def slowlog_len(self, **kwargs) -> ResponseT:
1356 """
1357 Get the number of items in the slowlog
1359 For more information, see https://redis.io/commands/slowlog-len
1360 """
1361 return self.execute_command("SLOWLOG LEN", **kwargs)
1363 def slowlog_reset(self, **kwargs) -> ResponseT:
1364 """
1365 Remove all items in the slowlog
1367 For more information, see https://redis.io/commands/slowlog-reset
1368 """
1369 return self.execute_command("SLOWLOG RESET", **kwargs)
1371 def time(self, **kwargs) -> ResponseT:
1372 """
1373 Returns the server time as a 2-item tuple of ints:
1374 (seconds since epoch, microseconds into this second).
1376 For more information, see https://redis.io/commands/time
1377 """
1378 return self.execute_command("TIME", **kwargs)
1380 def wait(self, num_replicas: int, timeout: int, **kwargs) -> ResponseT:
1381 """
1382 Redis synchronous replication
1383 That returns the number of replicas that processed the query when
1384 we finally have at least ``num_replicas``, or when the ``timeout`` was
1385 reached.
1387 For more information, see https://redis.io/commands/wait
1388 """
1389 return self.execute_command("WAIT", num_replicas, timeout, **kwargs)
1391 def waitaof(
1392 self, num_local: int, num_replicas: int, timeout: int, **kwargs
1393 ) -> ResponseT:
1394 """
1395 This command blocks the current client until all previous write
1396 commands by that client are acknowledged as having been fsynced
1397 to the AOF of the local Redis and/or at least the specified number
1398 of replicas.
1400 For more information, see https://redis.io/commands/waitaof
1401 """
1402 return self.execute_command(
1403 "WAITAOF", num_local, num_replicas, timeout, **kwargs
1404 )
1406 def hello(self):
1407 """
1408 This function throws a NotImplementedError since it is intentionally
1409 not supported.
1410 """
1411 raise NotImplementedError(
1412 "HELLO is intentionally not implemented in the client."
1413 )
1415 def failover(self):
1416 """
1417 This function throws a NotImplementedError since it is intentionally
1418 not supported.
1419 """
1420 raise NotImplementedError(
1421 "FAILOVER is intentionally not implemented in the client."
1422 )
1424 def hotkeys_start(
1425 self,
1426 metrics: List[HotkeysMetricsTypes],
1427 count: Optional[int] = None,
1428 duration: Optional[int] = None,
1429 sample_ratio: Optional[int] = None,
1430 slots: Optional[List[int]] = None,
1431 **kwargs,
1432 ) -> Union[Awaitable[Union[str, bytes]], Union[str, bytes]]:
1433 """
1434 Start collecting hotkeys data.
1435 Returns an error if there is an ongoing collection session.
1437 Args:
1438 count: The number of keys to collect in each criteria (CPU and network consumption)
1439 metrics: List of metrics to track. Supported values: [HotkeysMetricsTypes.CPU, HotkeysMetricsTypes.NET]
1440 duration: Automatically stop the collection after `duration` seconds
1441 sample_ratio: Commands are sampled with probability 1/ratio (1 means no sampling)
1442 slots: Only track keys on the specified hash slots
1444 For more information, see https://redis.io/commands/hotkeys-start
1445 """
1446 args: List[Union[str, int]] = ["HOTKEYS", "START"]
1448 # Add METRICS
1449 args.extend(["METRICS", len(metrics)])
1450 args.extend([str(m.value) for m in metrics])
1452 # Add COUNT
1453 if count is not None:
1454 args.extend(["COUNT", count])
1456 # Add optional DURATION
1457 if duration is not None:
1458 args.extend(["DURATION", duration])
1460 # Add optional SAMPLE ratio
1461 if sample_ratio is not None:
1462 args.extend(["SAMPLE", sample_ratio])
1464 # Add optional SLOTS
1465 if slots is not None:
1466 args.append("SLOTS")
1467 args.append(len(slots))
1468 args.extend(slots)
1470 return self.execute_command(*args, **kwargs)
1472 def hotkeys_stop(
1473 self, **kwargs
1474 ) -> Union[Awaitable[Union[str, bytes]], Union[str, bytes]]:
1475 """
1476 Stop the ongoing hotkeys collection session (if any).
1477 The results of the last collection session are kept for consumption with HOTKEYS GET.
1479 For more information, see https://redis.io/commands/hotkeys-stop
1480 """
1481 return self.execute_command("HOTKEYS STOP", **kwargs)
1483 def hotkeys_reset(
1484 self, **kwargs
1485 ) -> Union[Awaitable[Union[str, bytes]], Union[str, bytes]]:
1486 """
1487 Discard the last hotkeys collection session results (in order to save memory).
1488 Error if there is an ongoing collection session.
1490 For more information, see https://redis.io/commands/hotkeys-reset
1491 """
1492 return self.execute_command("HOTKEYS RESET", **kwargs)
1494 def hotkeys_get(
1495 self, **kwargs
1496 ) -> Union[
1497 Awaitable[list[dict[Union[str, bytes], Any]]],
1498 list[dict[Union[str, bytes], Any]],
1499 ]:
1500 """
1501 Retrieve the result of the ongoing collection session (if any),
1502 or the last collection session (if any).
1504 HOTKEYS GET response is wrapped in an array for aggregation support.
1505 Each node returns a single-element array, allowing multiple node
1506 responses to be concatenated by DMC or other aggregators.
1508 For more information, see https://redis.io/commands/hotkeys-get
1509 """
1510 return self.execute_command("HOTKEYS GET", **kwargs)
1513class AsyncManagementCommands(ManagementCommands):
1514 async def command_info(self, **kwargs) -> None:
1515 return super().command_info(**kwargs)
1517 async def debug_segfault(self, **kwargs) -> None:
1518 return super().debug_segfault(**kwargs)
1520 async def memory_doctor(self, **kwargs) -> None:
1521 return super().memory_doctor(**kwargs)
1523 async def memory_help(self, **kwargs) -> None:
1524 return super().memory_help(**kwargs)
1526 async def shutdown(
1527 self,
1528 save: bool = False,
1529 nosave: bool = False,
1530 now: bool = False,
1531 force: bool = False,
1532 abort: bool = False,
1533 **kwargs,
1534 ) -> None:
1535 """Shutdown the Redis server. If Redis has persistence configured,
1536 data will be flushed before shutdown. If the "save" option is set,
1537 a data flush will be attempted even if there is no persistence
1538 configured. If the "nosave" option is set, no data flush will be
1539 attempted. The "save" and "nosave" options cannot both be set.
1541 For more information, see https://redis.io/commands/shutdown
1542 """
1543 if save and nosave:
1544 raise DataError("SHUTDOWN save and nosave cannot both be set")
1545 args = ["SHUTDOWN"]
1546 if save:
1547 args.append("SAVE")
1548 if nosave:
1549 args.append("NOSAVE")
1550 if now:
1551 args.append("NOW")
1552 if force:
1553 args.append("FORCE")
1554 if abort:
1555 args.append("ABORT")
1556 try:
1557 await self.execute_command(*args, **kwargs)
1558 except ConnectionError:
1559 # a ConnectionError here is expected
1560 return
1561 raise RedisError("SHUTDOWN seems to have failed.")
1564class BitFieldOperation:
1565 """
1566 Command builder for BITFIELD commands.
1567 """
1569 def __init__(
1570 self,
1571 client: Union["redis.client.Redis", "redis.asyncio.client.Redis"],
1572 key: str,
1573 default_overflow: Optional[str] = None,
1574 ):
1575 self.client = client
1576 self.key = key
1577 self._default_overflow = default_overflow
1578 # for typing purposes, run the following in constructor and in reset()
1579 self.operations: list[tuple[EncodableT, ...]] = []
1580 self._last_overflow = "WRAP"
1581 self.reset()
1583 def reset(self):
1584 """
1585 Reset the state of the instance to when it was constructed
1586 """
1587 self.operations = []
1588 self._last_overflow = "WRAP"
1589 self.overflow(self._default_overflow or self._last_overflow)
1591 def overflow(self, overflow: str):
1592 """
1593 Update the overflow algorithm of successive INCRBY operations
1594 :param overflow: Overflow algorithm, one of WRAP, SAT, FAIL. See the
1595 Redis docs for descriptions of these algorithmsself.
1596 :returns: a :py:class:`BitFieldOperation` instance.
1597 """
1598 overflow = overflow.upper()
1599 if overflow != self._last_overflow:
1600 self._last_overflow = overflow
1601 self.operations.append(("OVERFLOW", overflow))
1602 return self
1604 def incrby(
1605 self,
1606 fmt: str,
1607 offset: BitfieldOffsetT,
1608 increment: int,
1609 overflow: Optional[str] = None,
1610 ):
1611 """
1612 Increment a bitfield by a given amount.
1613 :param fmt: format-string for the bitfield being updated, e.g. 'u8'
1614 for an unsigned 8-bit integer.
1615 :param offset: offset (in number of bits). If prefixed with a
1616 '#', this is an offset multiplier, e.g. given the arguments
1617 fmt='u8', offset='#2', the offset will be 16.
1618 :param int increment: value to increment the bitfield by.
1619 :param str overflow: overflow algorithm. Defaults to WRAP, but other
1620 acceptable values are SAT and FAIL. See the Redis docs for
1621 descriptions of these algorithms.
1622 :returns: a :py:class:`BitFieldOperation` instance.
1623 """
1624 if overflow is not None:
1625 self.overflow(overflow)
1627 self.operations.append(("INCRBY", fmt, offset, increment))
1628 return self
1630 def get(self, fmt: str, offset: BitfieldOffsetT):
1631 """
1632 Get the value of a given bitfield.
1633 :param fmt: format-string for the bitfield being read, e.g. 'u8' for
1634 an unsigned 8-bit integer.
1635 :param offset: offset (in number of bits). If prefixed with a
1636 '#', this is an offset multiplier, e.g. given the arguments
1637 fmt='u8', offset='#2', the offset will be 16.
1638 :returns: a :py:class:`BitFieldOperation` instance.
1639 """
1640 self.operations.append(("GET", fmt, offset))
1641 return self
1643 def set(self, fmt: str, offset: BitfieldOffsetT, value: int):
1644 """
1645 Set the value of a given bitfield.
1646 :param fmt: format-string for the bitfield being read, e.g. 'u8' for
1647 an unsigned 8-bit integer.
1648 :param offset: offset (in number of bits). If prefixed with a
1649 '#', this is an offset multiplier, e.g. given the arguments
1650 fmt='u8', offset='#2', the offset will be 16.
1651 :param int value: value to set at the given position.
1652 :returns: a :py:class:`BitFieldOperation` instance.
1653 """
1654 self.operations.append(("SET", fmt, offset, value))
1655 return self
1657 @property
1658 def command(self):
1659 cmd = ["BITFIELD", self.key]
1660 for ops in self.operations:
1661 cmd.extend(ops)
1662 return cmd
1664 def execute(self) -> ResponseT:
1665 """
1666 Execute the operation(s) in a single BITFIELD command. The return value
1667 is a list of values corresponding to each operation. If the client
1668 used to create this instance was a pipeline, the list of values
1669 will be present within the pipeline's execute.
1670 """
1671 command = self.command
1672 self.reset()
1673 return self.client.execute_command(*command)
1676class DataPersistOptions(Enum):
1677 # set the value for each provided key to each
1678 # provided value only if all do not already exist.
1679 NX = "NX"
1681 # set the value for each provided key to each
1682 # provided value only if all already exist.
1683 XX = "XX"
1686class BasicKeyCommands(CommandsProtocol):
1687 """
1688 Redis basic key-based commands
1689 """
1691 def append(self, key: KeyT, value: EncodableT) -> ResponseT:
1692 """
1693 Appends the string ``value`` to the value at ``key``. If ``key``
1694 doesn't already exist, create it with a value of ``value``.
1695 Returns the new length of the value at ``key``.
1697 For more information, see https://redis.io/commands/append
1698 """
1699 return self.execute_command("APPEND", key, value)
1701 def bitcount(
1702 self,
1703 key: KeyT,
1704 start: Optional[int] = None,
1705 end: Optional[int] = None,
1706 mode: Optional[str] = None,
1707 ) -> ResponseT:
1708 """
1709 Returns the count of set bits in the value of ``key``. Optional
1710 ``start`` and ``end`` parameters indicate which bytes to consider
1712 For more information, see https://redis.io/commands/bitcount
1713 """
1714 params = [key]
1715 if start is not None and end is not None:
1716 params.append(start)
1717 params.append(end)
1718 elif (start is not None and end is None) or (end is not None and start is None):
1719 raise DataError("Both start and end must be specified")
1720 if mode is not None:
1721 params.append(mode)
1722 return self.execute_command("BITCOUNT", *params, keys=[key])
1724 def bitfield(
1725 self: Union["redis.client.Redis", "redis.asyncio.client.Redis"],
1726 key: KeyT,
1727 default_overflow: Optional[str] = None,
1728 ) -> BitFieldOperation:
1729 """
1730 Return a BitFieldOperation instance to conveniently construct one or
1731 more bitfield operations on ``key``.
1733 For more information, see https://redis.io/commands/bitfield
1734 """
1735 return BitFieldOperation(self, key, default_overflow=default_overflow)
1737 def bitfield_ro(
1738 self: Union["redis.client.Redis", "redis.asyncio.client.Redis"],
1739 key: KeyT,
1740 encoding: str,
1741 offset: BitfieldOffsetT,
1742 items: Optional[list] = None,
1743 ) -> ResponseT:
1744 """
1745 Return an array of the specified bitfield values
1746 where the first value is found using ``encoding`` and ``offset``
1747 parameters and remaining values are result of corresponding
1748 encoding/offset pairs in optional list ``items``
1749 Read-only variant of the BITFIELD command.
1751 For more information, see https://redis.io/commands/bitfield_ro
1752 """
1753 params = [key, "GET", encoding, offset]
1755 items = items or []
1756 for encoding, offset in items:
1757 params.extend(["GET", encoding, offset])
1758 return self.execute_command("BITFIELD_RO", *params, keys=[key])
1760 def bitop(self, operation: str, dest: KeyT, *keys: KeyT) -> ResponseT:
1761 """
1762 Perform a bitwise operation using ``operation`` between ``keys`` and
1763 store the result in ``dest``.
1765 For more information, see https://redis.io/commands/bitop
1766 """
1767 return self.execute_command("BITOP", operation, dest, *keys)
1769 def bitpos(
1770 self,
1771 key: KeyT,
1772 bit: int,
1773 start: Optional[int] = None,
1774 end: Optional[int] = None,
1775 mode: Optional[str] = None,
1776 ) -> ResponseT:
1777 """
1778 Return the position of the first bit set to 1 or 0 in a string.
1779 ``start`` and ``end`` defines search range. The range is interpreted
1780 as a range of bytes and not a range of bits, so start=0 and end=2
1781 means to look at the first three bytes.
1783 For more information, see https://redis.io/commands/bitpos
1784 """
1785 if bit not in (0, 1):
1786 raise DataError("bit must be 0 or 1")
1787 params = [key, bit]
1789 start is not None and params.append(start)
1791 if start is not None and end is not None:
1792 params.append(end)
1793 elif start is None and end is not None:
1794 raise DataError("start argument is not set, when end is specified")
1796 if mode is not None:
1797 params.append(mode)
1798 return self.execute_command("BITPOS", *params, keys=[key])
1800 def copy(
1801 self,
1802 source: str,
1803 destination: str,
1804 destination_db: Optional[str] = None,
1805 replace: bool = False,
1806 ) -> ResponseT:
1807 """
1808 Copy the value stored in the ``source`` key to the ``destination`` key.
1810 ``destination_db`` an alternative destination database. By default,
1811 the ``destination`` key is created in the source Redis database.
1813 ``replace`` whether the ``destination`` key should be removed before
1814 copying the value to it. By default, the value is not copied if
1815 the ``destination`` key already exists.
1817 For more information, see https://redis.io/commands/copy
1818 """
1819 params = [source, destination]
1820 if destination_db is not None:
1821 params.extend(["DB", destination_db])
1822 if replace:
1823 params.append("REPLACE")
1824 return self.execute_command("COPY", *params)
1826 def decrby(self, name: KeyT, amount: int = 1) -> ResponseT:
1827 """
1828 Decrements the value of ``key`` by ``amount``. If no key exists,
1829 the value will be initialized as 0 - ``amount``
1831 For more information, see https://redis.io/commands/decrby
1832 """
1833 return self.execute_command("DECRBY", name, amount)
1835 decr = decrby
1837 def delete(self, *names: KeyT) -> ResponseT:
1838 """
1839 Delete one or more keys specified by ``names``
1840 """
1841 return self.execute_command("DEL", *names)
1843 def __delitem__(self, name: KeyT):
1844 self.delete(name)
1846 @experimental_method()
1847 def delex(
1848 self,
1849 name: KeyT,
1850 ifeq: Optional[Union[bytes, str]] = None,
1851 ifne: Optional[Union[bytes, str]] = None,
1852 ifdeq: Optional[str] = None, # hex digest
1853 ifdne: Optional[str] = None, # hex digest
1854 ) -> int:
1855 """
1856 Conditionally removes the specified key.
1858 Warning:
1859 **Experimental** since 7.1.
1860 This API may change or be removed without notice.
1861 The API may change based on feedback.
1863 Arguments:
1864 name: KeyT - the key to delete
1865 ifeq match-valu: Optional[Union[bytes, str]] - Delete the key only if its value is equal to match-value
1866 ifne match-value: Optional[Union[bytes, str]] - Delete the key only if its value is not equal to match-value
1867 ifdeq match-digest: Optional[str] - Delete the key only if the digest of its value is equal to match-digest
1868 ifdne match-digest: Optional[str] - Delete the key only if the digest of its value is not equal to match-digest
1870 Returns:
1871 int: 1 if the key was deleted, 0 otherwise.
1872 Raises:
1873 redis.exceptions.ResponseError: if key exists but is not a string
1874 and a condition is specified.
1875 ValueError: if more than one condition is provided.
1878 Requires Redis 8.4 or greater.
1879 For more information, see https://redis.io/commands/delex
1880 """
1881 conds = [x is not None for x in (ifeq, ifne, ifdeq, ifdne)]
1882 if sum(conds) > 1:
1883 raise ValueError("Only one of IFEQ/IFNE/IFDEQ/IFDNE may be specified")
1885 pieces = ["DELEX", name]
1886 if ifeq is not None:
1887 pieces += ["IFEQ", ifeq]
1888 elif ifne is not None:
1889 pieces += ["IFNE", ifne]
1890 elif ifdeq is not None:
1891 pieces += ["IFDEQ", ifdeq]
1892 elif ifdne is not None:
1893 pieces += ["IFDNE", ifdne]
1895 return self.execute_command(*pieces)
1897 def dump(self, name: KeyT) -> ResponseT:
1898 """
1899 Return a serialized version of the value stored at the specified key.
1900 If key does not exist a nil bulk reply is returned.
1902 For more information, see https://redis.io/commands/dump
1903 """
1904 from redis.client import NEVER_DECODE
1906 options = {}
1907 options[NEVER_DECODE] = []
1908 return self.execute_command("DUMP", name, **options)
1910 def exists(self, *names: KeyT) -> ResponseT:
1911 """
1912 Returns the number of ``names`` that exist
1914 For more information, see https://redis.io/commands/exists
1915 """
1916 return self.execute_command("EXISTS", *names, keys=names)
1918 __contains__ = exists
1920 def expire(
1921 self,
1922 name: KeyT,
1923 time: ExpiryT,
1924 nx: bool = False,
1925 xx: bool = False,
1926 gt: bool = False,
1927 lt: bool = False,
1928 ) -> ResponseT:
1929 """
1930 Set an expire flag on key ``name`` for ``time`` seconds with given
1931 ``option``. ``time`` can be represented by an integer or a Python timedelta
1932 object.
1934 Valid options are:
1935 NX -> Set expiry only when the key has no expiry
1936 XX -> Set expiry only when the key has an existing expiry
1937 GT -> Set expiry only when the new expiry is greater than current one
1938 LT -> Set expiry only when the new expiry is less than current one
1940 For more information, see https://redis.io/commands/expire
1941 """
1942 if isinstance(time, datetime.timedelta):
1943 time = int(time.total_seconds())
1945 exp_option = list()
1946 if nx:
1947 exp_option.append("NX")
1948 if xx:
1949 exp_option.append("XX")
1950 if gt:
1951 exp_option.append("GT")
1952 if lt:
1953 exp_option.append("LT")
1955 return self.execute_command("EXPIRE", name, time, *exp_option)
1957 def expireat(
1958 self,
1959 name: KeyT,
1960 when: AbsExpiryT,
1961 nx: bool = False,
1962 xx: bool = False,
1963 gt: bool = False,
1964 lt: bool = False,
1965 ) -> ResponseT:
1966 """
1967 Set an expire flag on key ``name`` with given ``option``. ``when``
1968 can be represented as an integer indicating unix time or a Python
1969 datetime object.
1971 Valid options are:
1972 -> NX -- Set expiry only when the key has no expiry
1973 -> XX -- Set expiry only when the key has an existing expiry
1974 -> GT -- Set expiry only when the new expiry is greater than current one
1975 -> LT -- Set expiry only when the new expiry is less than current one
1977 For more information, see https://redis.io/commands/expireat
1978 """
1979 if isinstance(when, datetime.datetime):
1980 when = int(when.timestamp())
1982 exp_option = list()
1983 if nx:
1984 exp_option.append("NX")
1985 if xx:
1986 exp_option.append("XX")
1987 if gt:
1988 exp_option.append("GT")
1989 if lt:
1990 exp_option.append("LT")
1992 return self.execute_command("EXPIREAT", name, when, *exp_option)
1994 def expiretime(self, key: str) -> int:
1995 """
1996 Returns the absolute Unix timestamp (since January 1, 1970) in seconds
1997 at which the given key will expire.
1999 For more information, see https://redis.io/commands/expiretime
2000 """
2001 return self.execute_command("EXPIRETIME", key)
2003 @experimental_method()
2004 def digest_local(self, value: Union[bytes, str]) -> Union[bytes, str]:
2005 """
2006 Compute the hexadecimal digest of the value locally, without sending it to the server.
2008 This is useful for conditional operations like IFDEQ/IFDNE where you need to
2009 compute the digest client-side before sending a command.
2011 Warning:
2012 **Experimental** - This API may change or be removed without notice.
2014 Arguments:
2015 - value: Union[bytes, str] - the value to compute the digest of.
2016 If a string is provided, it will be encoded using UTF-8 before hashing,
2017 which matches Redis's default encoding behavior.
2019 Returns:
2020 - (str | bytes) the XXH3 digest of the value as a hex string (16 hex characters).
2021 Returns bytes if decode_responses is False, otherwise returns str.
2023 For more information, see https://redis.io/commands/digest
2024 """
2025 if not HAS_XXHASH:
2026 raise NotImplementedError(
2027 "XXHASH support requires the optional 'xxhash' library. "
2028 "Install it with 'pip install xxhash' or use this package's extra with "
2029 "'pip install redis[xxhash]' to enable this feature."
2030 )
2032 local_digest = xxhash.xxh3_64(value).hexdigest()
2034 # To align with digest, we want to return bytes if decode_responses is False.
2035 # The following works because of Python's mixin-based client class hierarchy.
2036 if not self.get_encoder().decode_responses:
2037 local_digest = local_digest.encode()
2039 return local_digest
2041 @experimental_method()
2042 def digest(self, name: KeyT) -> Union[str, bytes, None]:
2043 """
2044 Return the digest of the value stored at the specified key.
2046 Warning:
2047 **Experimental** since 7.1.
2048 This API may change or be removed without notice.
2049 The API may change based on feedback.
2051 Arguments:
2052 - name: KeyT - the key to get the digest of
2054 Returns:
2055 - None if the key does not exist
2056 - (bulk string) the XXH3 digest of the value as a hex string
2057 Raises:
2058 - ResponseError if key exists but is not a string
2061 Requires Redis 8.4 or greater.
2062 For more information, see https://redis.io/commands/digest
2063 """
2064 # Bulk string response is already handled (bytes/str based on decode_responses)
2065 return self.execute_command("DIGEST", name)
2067 def get(self, name: KeyT) -> ResponseT:
2068 """
2069 Return the value at key ``name``, or None if the key doesn't exist
2071 For more information, see https://redis.io/commands/get
2072 """
2073 return self.execute_command("GET", name, keys=[name])
2075 def getdel(self, name: KeyT) -> ResponseT:
2076 """
2077 Get the value at key ``name`` and delete the key. This command
2078 is similar to GET, except for the fact that it also deletes
2079 the key on success (if and only if the key's value type
2080 is a string).
2082 For more information, see https://redis.io/commands/getdel
2083 """
2084 return self.execute_command("GETDEL", name)
2086 def getex(
2087 self,
2088 name: KeyT,
2089 ex: Optional[ExpiryT] = None,
2090 px: Optional[ExpiryT] = None,
2091 exat: Optional[AbsExpiryT] = None,
2092 pxat: Optional[AbsExpiryT] = None,
2093 persist: bool = False,
2094 ) -> ResponseT:
2095 """
2096 Get the value of key and optionally set its expiration.
2097 GETEX is similar to GET, but is a write command with
2098 additional options. All time parameters can be given as
2099 datetime.timedelta or integers.
2101 ``ex`` sets an expire flag on key ``name`` for ``ex`` seconds.
2103 ``px`` sets an expire flag on key ``name`` for ``px`` milliseconds.
2105 ``exat`` sets an expire flag on key ``name`` for ``ex`` seconds,
2106 specified in unix time.
2108 ``pxat`` sets an expire flag on key ``name`` for ``ex`` milliseconds,
2109 specified in unix time.
2111 ``persist`` remove the time to live associated with ``name``.
2113 For more information, see https://redis.io/commands/getex
2114 """
2115 if not at_most_one_value_set((ex, px, exat, pxat, persist)):
2116 raise DataError(
2117 "``ex``, ``px``, ``exat``, ``pxat``, "
2118 "and ``persist`` are mutually exclusive."
2119 )
2121 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat)
2123 if persist:
2124 exp_options.append("PERSIST")
2126 return self.execute_command("GETEX", name, *exp_options)
2128 def __getitem__(self, name: KeyT):
2129 """
2130 Return the value at key ``name``, raises a KeyError if the key
2131 doesn't exist.
2132 """
2133 value = self.get(name)
2134 if value is not None:
2135 return value
2136 raise KeyError(name)
2138 def getbit(self, name: KeyT, offset: int) -> ResponseT:
2139 """
2140 Returns an integer indicating the value of ``offset`` in ``name``
2142 For more information, see https://redis.io/commands/getbit
2143 """
2144 return self.execute_command("GETBIT", name, offset, keys=[name])
2146 def getrange(self, key: KeyT, start: int, end: int) -> ResponseT:
2147 """
2148 Returns the substring of the string value stored at ``key``,
2149 determined by the offsets ``start`` and ``end`` (both are inclusive)
2151 For more information, see https://redis.io/commands/getrange
2152 """
2153 return self.execute_command("GETRANGE", key, start, end, keys=[key])
2155 def getset(self, name: KeyT, value: EncodableT) -> ResponseT:
2156 """
2157 Sets the value at key ``name`` to ``value``
2158 and returns the old value at key ``name`` atomically.
2160 As per Redis 6.2, GETSET is considered deprecated.
2161 Please use SET with GET parameter in new code.
2163 For more information, see https://redis.io/commands/getset
2164 """
2165 return self.execute_command("GETSET", name, value)
2167 def incrby(self, name: KeyT, amount: int = 1) -> ResponseT:
2168 """
2169 Increments the value of ``key`` by ``amount``. If no key exists,
2170 the value will be initialized as ``amount``
2172 For more information, see https://redis.io/commands/incrby
2173 """
2174 return self.execute_command("INCRBY", name, amount)
2176 incr = incrby
2178 def incrbyfloat(self, name: KeyT, amount: float = 1.0) -> ResponseT:
2179 """
2180 Increments the value at key ``name`` by floating ``amount``.
2181 If no key exists, the value will be initialized as ``amount``
2183 For more information, see https://redis.io/commands/incrbyfloat
2184 """
2185 return self.execute_command("INCRBYFLOAT", name, amount)
2187 def keys(self, pattern: PatternT = "*", **kwargs) -> ResponseT:
2188 """
2189 Returns a list of keys matching ``pattern``
2191 For more information, see https://redis.io/commands/keys
2192 """
2193 return self.execute_command("KEYS", pattern, **kwargs)
2195 def lmove(
2196 self, first_list: str, second_list: str, src: str = "LEFT", dest: str = "RIGHT"
2197 ) -> ResponseT:
2198 """
2199 Atomically returns and removes the first/last element of a list,
2200 pushing it as the first/last element on the destination list.
2201 Returns the element being popped and pushed.
2203 For more information, see https://redis.io/commands/lmove
2204 """
2205 params = [first_list, second_list, src, dest]
2206 return self.execute_command("LMOVE", *params)
2208 def blmove(
2209 self,
2210 first_list: str,
2211 second_list: str,
2212 timeout: int,
2213 src: str = "LEFT",
2214 dest: str = "RIGHT",
2215 ) -> ResponseT:
2216 """
2217 Blocking version of lmove.
2219 For more information, see https://redis.io/commands/blmove
2220 """
2221 params = [first_list, second_list, src, dest, timeout]
2222 return self.execute_command("BLMOVE", *params)
2224 def mget(self, keys: KeysT, *args: EncodableT) -> ResponseT:
2225 """
2226 Returns a list of values ordered identically to ``keys``
2228 ** Important ** When this method is used with Cluster clients, all keys
2229 must be in the same hash slot, otherwise a RedisClusterException
2230 will be raised.
2232 For more information, see https://redis.io/commands/mget
2233 """
2234 from redis.client import EMPTY_RESPONSE
2236 args = list_or_args(keys, args)
2237 options = {}
2238 if not args:
2239 options[EMPTY_RESPONSE] = []
2240 options["keys"] = args
2241 return self.execute_command("MGET", *args, **options)
2243 def mset(self, mapping: Mapping[AnyKeyT, EncodableT]) -> ResponseT:
2244 """
2245 Sets key/values based on a mapping. Mapping is a dictionary of
2246 key/value pairs. Both keys and values should be strings or types that
2247 can be cast to a string via str().
2249 ** Important ** When this method is used with Cluster clients, all keys
2250 must be in the same hash slot, otherwise a RedisClusterException
2251 will be raised.
2253 For more information, see https://redis.io/commands/mset
2254 """
2255 items = []
2256 for pair in mapping.items():
2257 items.extend(pair)
2258 return self.execute_command("MSET", *items)
2260 def msetex(
2261 self,
2262 mapping: Mapping[AnyKeyT, EncodableT],
2263 data_persist_option: Optional[DataPersistOptions] = None,
2264 ex: Optional[ExpiryT] = None,
2265 px: Optional[ExpiryT] = None,
2266 exat: Optional[AbsExpiryT] = None,
2267 pxat: Optional[AbsExpiryT] = None,
2268 keepttl: bool = False,
2269 ) -> Union[Awaitable[int], int]:
2270 """
2271 Sets key/values based on the provided ``mapping`` items.
2273 ** Important ** When this method is used with Cluster clients, all keys
2274 must be in the same hash slot, otherwise a RedisClusterException
2275 will be raised.
2277 ``mapping`` accepts a dict of key/value pairs that will be added to the database.
2279 ``data_persist_option`` can be set to ``NX`` or ``XX`` to control the
2280 behavior of the command.
2281 ``NX`` will set the value for each provided key to each
2282 provided value only if all do not already exist.
2283 ``XX`` will set the value for each provided key to each
2284 provided value only if all already exist.
2286 ``ex`` sets an expire flag on the keys in ``mapping`` for ``ex`` seconds.
2288 ``px`` sets an expire flag on the keys in ``mapping`` for ``px`` milliseconds.
2290 ``exat`` sets an expire flag on the keys in ``mapping`` for ``exat`` seconds,
2291 specified in unix time.
2293 ``pxat`` sets an expire flag on the keys in ``mapping`` for ``pxat`` milliseconds,
2294 specified in unix time.
2296 ``keepttl`` if True, retain the time to live associated with the keys.
2298 Returns the number of fields that were added.
2300 Available since Redis 8.4
2301 For more information, see https://redis.io/commands/msetex
2302 """
2303 if not at_most_one_value_set((ex, px, exat, pxat, keepttl)):
2304 raise DataError(
2305 "``ex``, ``px``, ``exat``, ``pxat``, "
2306 "and ``keepttl`` are mutually exclusive."
2307 )
2309 exp_options: list[EncodableT] = []
2310 if data_persist_option:
2311 exp_options.append(data_persist_option.value)
2313 exp_options.extend(extract_expire_flags(ex, px, exat, pxat))
2315 if keepttl:
2316 exp_options.append("KEEPTTL")
2318 pieces = ["MSETEX", len(mapping)]
2320 for pair in mapping.items():
2321 pieces.extend(pair)
2323 return self.execute_command(*pieces, *exp_options)
2325 def msetnx(self, mapping: Mapping[AnyKeyT, EncodableT]) -> ResponseT:
2326 """
2327 Sets key/values based on a mapping if none of the keys are already set.
2328 Mapping is a dictionary of key/value pairs. Both keys and values
2329 should be strings or types that can be cast to a string via str().
2330 Returns a boolean indicating if the operation was successful.
2332 ** Important ** When this method is used with Cluster clients, all keys
2333 must be in the same hash slot, otherwise a RedisClusterException
2334 will be raised.
2336 For more information, see https://redis.io/commands/msetnx
2337 """
2338 items = []
2339 for pair in mapping.items():
2340 items.extend(pair)
2341 return self.execute_command("MSETNX", *items)
2343 def move(self, name: KeyT, db: int) -> ResponseT:
2344 """
2345 Moves the key ``name`` to a different Redis database ``db``
2347 For more information, see https://redis.io/commands/move
2348 """
2349 return self.execute_command("MOVE", name, db)
2351 def persist(self, name: KeyT) -> ResponseT:
2352 """
2353 Removes an expiration on ``name``
2355 For more information, see https://redis.io/commands/persist
2356 """
2357 return self.execute_command("PERSIST", name)
2359 def pexpire(
2360 self,
2361 name: KeyT,
2362 time: ExpiryT,
2363 nx: bool = False,
2364 xx: bool = False,
2365 gt: bool = False,
2366 lt: bool = False,
2367 ) -> ResponseT:
2368 """
2369 Set an expire flag on key ``name`` for ``time`` milliseconds
2370 with given ``option``. ``time`` can be represented by an
2371 integer or a Python timedelta object.
2373 Valid options are:
2374 NX -> Set expiry only when the key has no expiry
2375 XX -> Set expiry only when the key has an existing expiry
2376 GT -> Set expiry only when the new expiry is greater than current one
2377 LT -> Set expiry only when the new expiry is less than current one
2379 For more information, see https://redis.io/commands/pexpire
2380 """
2381 if isinstance(time, datetime.timedelta):
2382 time = int(time.total_seconds() * 1000)
2384 exp_option = list()
2385 if nx:
2386 exp_option.append("NX")
2387 if xx:
2388 exp_option.append("XX")
2389 if gt:
2390 exp_option.append("GT")
2391 if lt:
2392 exp_option.append("LT")
2393 return self.execute_command("PEXPIRE", name, time, *exp_option)
2395 def pexpireat(
2396 self,
2397 name: KeyT,
2398 when: AbsExpiryT,
2399 nx: bool = False,
2400 xx: bool = False,
2401 gt: bool = False,
2402 lt: bool = False,
2403 ) -> ResponseT:
2404 """
2405 Set an expire flag on key ``name`` with given ``option``. ``when``
2406 can be represented as an integer representing unix time in
2407 milliseconds (unix time * 1000) or a Python datetime object.
2409 Valid options are:
2410 NX -> Set expiry only when the key has no expiry
2411 XX -> Set expiry only when the key has an existing expiry
2412 GT -> Set expiry only when the new expiry is greater than current one
2413 LT -> Set expiry only when the new expiry is less than current one
2415 For more information, see https://redis.io/commands/pexpireat
2416 """
2417 if isinstance(when, datetime.datetime):
2418 when = int(when.timestamp() * 1000)
2419 exp_option = list()
2420 if nx:
2421 exp_option.append("NX")
2422 if xx:
2423 exp_option.append("XX")
2424 if gt:
2425 exp_option.append("GT")
2426 if lt:
2427 exp_option.append("LT")
2428 return self.execute_command("PEXPIREAT", name, when, *exp_option)
2430 def pexpiretime(self, key: str) -> int:
2431 """
2432 Returns the absolute Unix timestamp (since January 1, 1970) in milliseconds
2433 at which the given key will expire.
2435 For more information, see https://redis.io/commands/pexpiretime
2436 """
2437 return self.execute_command("PEXPIRETIME", key)
2439 def psetex(self, name: KeyT, time_ms: ExpiryT, value: EncodableT):
2440 """
2441 Set the value of key ``name`` to ``value`` that expires in ``time_ms``
2442 milliseconds. ``time_ms`` can be represented by an integer or a Python
2443 timedelta object
2445 For more information, see https://redis.io/commands/psetex
2446 """
2447 if isinstance(time_ms, datetime.timedelta):
2448 time_ms = int(time_ms.total_seconds() * 1000)
2449 return self.execute_command("PSETEX", name, time_ms, value)
2451 def pttl(self, name: KeyT) -> ResponseT:
2452 """
2453 Returns the number of milliseconds until the key ``name`` will expire
2455 For more information, see https://redis.io/commands/pttl
2456 """
2457 return self.execute_command("PTTL", name)
2459 def hrandfield(
2460 self, key: str, count: Optional[int] = None, withvalues: bool = False
2461 ) -> ResponseT:
2462 """
2463 Return a random field from the hash value stored at key.
2465 count: if the argument is positive, return an array of distinct fields.
2466 If called with a negative count, the behavior changes and the command
2467 is allowed to return the same field multiple times. In this case,
2468 the number of returned fields is the absolute value of the
2469 specified count.
2470 withvalues: The optional WITHVALUES modifier changes the reply so it
2471 includes the respective values of the randomly selected hash fields.
2473 For more information, see https://redis.io/commands/hrandfield
2474 """
2475 params = []
2476 if count is not None:
2477 params.append(count)
2478 if withvalues:
2479 params.append("WITHVALUES")
2481 return self.execute_command("HRANDFIELD", key, *params)
2483 def randomkey(self, **kwargs) -> ResponseT:
2484 """
2485 Returns the name of a random key
2487 For more information, see https://redis.io/commands/randomkey
2488 """
2489 return self.execute_command("RANDOMKEY", **kwargs)
2491 def rename(self, src: KeyT, dst: KeyT) -> ResponseT:
2492 """
2493 Rename key ``src`` to ``dst``
2495 For more information, see https://redis.io/commands/rename
2496 """
2497 return self.execute_command("RENAME", src, dst)
2499 def renamenx(self, src: KeyT, dst: KeyT):
2500 """
2501 Rename key ``src`` to ``dst`` if ``dst`` doesn't already exist
2503 For more information, see https://redis.io/commands/renamenx
2504 """
2505 return self.execute_command("RENAMENX", src, dst)
2507 def restore(
2508 self,
2509 name: KeyT,
2510 ttl: float,
2511 value: EncodableT,
2512 replace: bool = False,
2513 absttl: bool = False,
2514 idletime: Optional[int] = None,
2515 frequency: Optional[int] = None,
2516 ) -> ResponseT:
2517 """
2518 Create a key using the provided serialized value, previously obtained
2519 using DUMP.
2521 ``replace`` allows an existing key on ``name`` to be overridden. If
2522 it's not specified an error is raised on collision.
2524 ``absttl`` if True, specified ``ttl`` should represent an absolute Unix
2525 timestamp in milliseconds in which the key will expire. (Redis 5.0 or
2526 greater).
2528 ``idletime`` Used for eviction, this is the number of seconds the
2529 key must be idle, prior to execution.
2531 ``frequency`` Used for eviction, this is the frequency counter of
2532 the object stored at the key, prior to execution.
2534 For more information, see https://redis.io/commands/restore
2535 """
2536 params = [name, ttl, value]
2537 if replace:
2538 params.append("REPLACE")
2539 if absttl:
2540 params.append("ABSTTL")
2541 if idletime is not None:
2542 params.append("IDLETIME")
2543 try:
2544 params.append(int(idletime))
2545 except ValueError:
2546 raise DataError("idletimemust be an integer")
2548 if frequency is not None:
2549 params.append("FREQ")
2550 try:
2551 params.append(int(frequency))
2552 except ValueError:
2553 raise DataError("frequency must be an integer")
2555 return self.execute_command("RESTORE", *params)
2557 @experimental_args(["ifeq", "ifne", "ifdeq", "ifdne"])
2558 def set(
2559 self,
2560 name: KeyT,
2561 value: EncodableT,
2562 ex: Optional[ExpiryT] = None,
2563 px: Optional[ExpiryT] = None,
2564 nx: bool = False,
2565 xx: bool = False,
2566 keepttl: bool = False,
2567 get: bool = False,
2568 exat: Optional[AbsExpiryT] = None,
2569 pxat: Optional[AbsExpiryT] = None,
2570 ifeq: Optional[Union[bytes, str]] = None,
2571 ifne: Optional[Union[bytes, str]] = None,
2572 ifdeq: Optional[str] = None, # hex digest of current value
2573 ifdne: Optional[str] = None, # hex digest of current value
2574 ) -> ResponseT:
2575 """
2576 Set the value at key ``name`` to ``value``
2578 Warning:
2579 **Experimental** since 7.1.
2580 The usage of the arguments ``ifeq``, ``ifne``, ``ifdeq``, and ``ifdne``
2581 is experimental. The API or returned results when those parameters are used
2582 may change based on feedback.
2584 ``ex`` sets an expire flag on key ``name`` for ``ex`` seconds.
2586 ``px`` sets an expire flag on key ``name`` for ``px`` milliseconds.
2588 ``nx`` if set to True, set the value at key ``name`` to ``value`` only
2589 if it does not exist.
2591 ``xx`` if set to True, set the value at key ``name`` to ``value`` only
2592 if it already exists.
2594 ``keepttl`` if True, retain the time to live associated with the key.
2595 (Available since Redis 6.0)
2597 ``get`` if True, set the value at key ``name`` to ``value`` and return
2598 the old value stored at key, or None if the key did not exist.
2599 (Available since Redis 6.2)
2601 ``exat`` sets an expire flag on key ``name`` for ``ex`` seconds,
2602 specified in unix time.
2604 ``pxat`` sets an expire flag on key ``name`` for ``ex`` milliseconds,
2605 specified in unix time.
2607 ``ifeq`` set the value at key ``name`` to ``value`` only if the current
2608 value exactly matches the argument.
2609 If key doesn’t exist - it won’t be created.
2610 (Requires Redis 8.4 or greater)
2612 ``ifne`` set the value at key ``name`` to ``value`` only if the current
2613 value does not exactly match the argument.
2614 If key doesn’t exist - it will be created.
2615 (Requires Redis 8.4 or greater)
2617 ``ifdeq`` set the value at key ``name`` to ``value`` only if the current
2618 value XXH3 hex digest exactly matches the argument.
2619 If key doesn’t exist - it won’t be created.
2620 (Requires Redis 8.4 or greater)
2622 ``ifdne`` set the value at key ``name`` to ``value`` only if the current
2623 value XXH3 hex digest does not exactly match the argument.
2624 If key doesn’t exist - it will be created.
2625 (Requires Redis 8.4 or greater)
2627 For more information, see https://redis.io/commands/set
2628 """
2630 if not at_most_one_value_set((ex, px, exat, pxat, keepttl)):
2631 raise DataError(
2632 "``ex``, ``px``, ``exat``, ``pxat``, "
2633 "and ``keepttl`` are mutually exclusive."
2634 )
2636 # Enforce mutual exclusivity among all conditional switches.
2637 if not at_most_one_value_set((nx, xx, ifeq, ifne, ifdeq, ifdne)):
2638 raise DataError(
2639 "``nx``, ``xx``, ``ifeq``, ``ifne``, ``ifdeq``, ``ifdne`` are mutually exclusive."
2640 )
2642 pieces: list[EncodableT] = [name, value]
2643 options = {}
2645 # Conditional modifier (exactly one at most)
2646 if nx:
2647 pieces.append("NX")
2648 elif xx:
2649 pieces.append("XX")
2650 elif ifeq is not None:
2651 pieces.extend(("IFEQ", ifeq))
2652 elif ifne is not None:
2653 pieces.extend(("IFNE", ifne))
2654 elif ifdeq is not None:
2655 pieces.extend(("IFDEQ", ifdeq))
2656 elif ifdne is not None:
2657 pieces.extend(("IFDNE", ifdne))
2659 if get:
2660 pieces.append("GET")
2661 options["get"] = True
2663 pieces.extend(extract_expire_flags(ex, px, exat, pxat))
2665 if keepttl:
2666 pieces.append("KEEPTTL")
2668 return self.execute_command("SET", *pieces, **options)
2670 def __setitem__(self, name: KeyT, value: EncodableT):
2671 self.set(name, value)
2673 def setbit(self, name: KeyT, offset: int, value: int) -> ResponseT:
2674 """
2675 Flag the ``offset`` in ``name`` as ``value``. Returns an integer
2676 indicating the previous value of ``offset``.
2678 For more information, see https://redis.io/commands/setbit
2679 """
2680 value = value and 1 or 0
2681 return self.execute_command("SETBIT", name, offset, value)
2683 def setex(self, name: KeyT, time: ExpiryT, value: EncodableT) -> ResponseT:
2684 """
2685 Set the value of key ``name`` to ``value`` that expires in ``time``
2686 seconds. ``time`` can be represented by an integer or a Python
2687 timedelta object.
2689 For more information, see https://redis.io/commands/setex
2690 """
2691 if isinstance(time, datetime.timedelta):
2692 time = int(time.total_seconds())
2693 return self.execute_command("SETEX", name, time, value)
2695 def setnx(self, name: KeyT, value: EncodableT) -> ResponseT:
2696 """
2697 Set the value of key ``name`` to ``value`` if key doesn't exist
2699 For more information, see https://redis.io/commands/setnx
2700 """
2701 return self.execute_command("SETNX", name, value)
2703 def setrange(self, name: KeyT, offset: int, value: EncodableT) -> ResponseT:
2704 """
2705 Overwrite bytes in the value of ``name`` starting at ``offset`` with
2706 ``value``. If ``offset`` plus the length of ``value`` exceeds the
2707 length of the original value, the new value will be larger than before.
2708 If ``offset`` exceeds the length of the original value, null bytes
2709 will be used to pad between the end of the previous value and the start
2710 of what's being injected.
2712 Returns the length of the new string.
2714 For more information, see https://redis.io/commands/setrange
2715 """
2716 return self.execute_command("SETRANGE", name, offset, value)
2718 def stralgo(
2719 self,
2720 algo: Literal["LCS"],
2721 value1: KeyT,
2722 value2: KeyT,
2723 specific_argument: Union[Literal["strings"], Literal["keys"]] = "strings",
2724 len: bool = False,
2725 idx: bool = False,
2726 minmatchlen: Optional[int] = None,
2727 withmatchlen: bool = False,
2728 **kwargs,
2729 ) -> ResponseT:
2730 """
2731 Implements complex algorithms that operate on strings.
2732 Right now the only algorithm implemented is the LCS algorithm
2733 (longest common substring). However new algorithms could be
2734 implemented in the future.
2736 ``algo`` Right now must be LCS
2737 ``value1`` and ``value2`` Can be two strings or two keys
2738 ``specific_argument`` Specifying if the arguments to the algorithm
2739 will be keys or strings. strings is the default.
2740 ``len`` Returns just the len of the match.
2741 ``idx`` Returns the match positions in each string.
2742 ``minmatchlen`` Restrict the list of matches to the ones of a given
2743 minimal length. Can be provided only when ``idx`` set to True.
2744 ``withmatchlen`` Returns the matches with the len of the match.
2745 Can be provided only when ``idx`` set to True.
2747 For more information, see https://redis.io/commands/stralgo
2748 """
2749 # check validity
2750 supported_algo = ["LCS"]
2751 if algo not in supported_algo:
2752 supported_algos_str = ", ".join(supported_algo)
2753 raise DataError(f"The supported algorithms are: {supported_algos_str}")
2754 if specific_argument not in ["keys", "strings"]:
2755 raise DataError("specific_argument can be only keys or strings")
2756 if len and idx:
2757 raise DataError("len and idx cannot be provided together.")
2759 pieces: list[EncodableT] = [algo, specific_argument.upper(), value1, value2]
2760 if len:
2761 pieces.append(b"LEN")
2762 if idx:
2763 pieces.append(b"IDX")
2764 try:
2765 int(minmatchlen)
2766 pieces.extend([b"MINMATCHLEN", minmatchlen])
2767 except TypeError:
2768 pass
2769 if withmatchlen:
2770 pieces.append(b"WITHMATCHLEN")
2772 return self.execute_command(
2773 "STRALGO",
2774 *pieces,
2775 len=len,
2776 idx=idx,
2777 minmatchlen=minmatchlen,
2778 withmatchlen=withmatchlen,
2779 **kwargs,
2780 )
2782 def strlen(self, name: KeyT) -> ResponseT:
2783 """
2784 Return the number of bytes stored in the value of ``name``
2786 For more information, see https://redis.io/commands/strlen
2787 """
2788 return self.execute_command("STRLEN", name, keys=[name])
2790 def substr(self, name: KeyT, start: int, end: int = -1) -> ResponseT:
2791 """
2792 Return a substring of the string at key ``name``. ``start`` and ``end``
2793 are 0-based integers specifying the portion of the string to return.
2794 """
2795 return self.execute_command("SUBSTR", name, start, end, keys=[name])
2797 def touch(self, *args: KeyT) -> ResponseT:
2798 """
2799 Alters the last access time of a key(s) ``*args``. A key is ignored
2800 if it does not exist.
2802 For more information, see https://redis.io/commands/touch
2803 """
2804 return self.execute_command("TOUCH", *args)
2806 def ttl(self, name: KeyT) -> ResponseT:
2807 """
2808 Returns the number of seconds until the key ``name`` will expire
2810 For more information, see https://redis.io/commands/ttl
2811 """
2812 return self.execute_command("TTL", name)
2814 def type(self, name: KeyT) -> ResponseT:
2815 """
2816 Returns the type of key ``name``
2818 For more information, see https://redis.io/commands/type
2819 """
2820 return self.execute_command("TYPE", name, keys=[name])
2822 def watch(self, *names: KeyT) -> None:
2823 """
2824 Watches the values at keys ``names``, or None if the key doesn't exist
2826 For more information, see https://redis.io/commands/watch
2827 """
2828 warnings.warn(DeprecationWarning("Call WATCH from a Pipeline object"))
2830 def unwatch(self) -> None:
2831 """
2832 Unwatches all previously watched keys for a transaction
2834 For more information, see https://redis.io/commands/unwatch
2835 """
2836 warnings.warn(DeprecationWarning("Call UNWATCH from a Pipeline object"))
2838 def unlink(self, *names: KeyT) -> ResponseT:
2839 """
2840 Unlink one or more keys specified by ``names``
2842 For more information, see https://redis.io/commands/unlink
2843 """
2844 return self.execute_command("UNLINK", *names)
2846 def lcs(
2847 self,
2848 key1: str,
2849 key2: str,
2850 len: Optional[bool] = False,
2851 idx: Optional[bool] = False,
2852 minmatchlen: Optional[int] = 0,
2853 withmatchlen: Optional[bool] = False,
2854 ) -> Union[str, int, list]:
2855 """
2856 Find the longest common subsequence between ``key1`` and ``key2``.
2857 If ``len`` is true the length of the match will will be returned.
2858 If ``idx`` is true the match position in each strings will be returned.
2859 ``minmatchlen`` restrict the list of matches to the ones of
2860 the given ``minmatchlen``.
2861 If ``withmatchlen`` the length of the match also will be returned.
2862 For more information, see https://redis.io/commands/lcs
2863 """
2864 pieces = [key1, key2]
2865 if len:
2866 pieces.append("LEN")
2867 if idx:
2868 pieces.append("IDX")
2869 if minmatchlen != 0:
2870 pieces.extend(["MINMATCHLEN", minmatchlen])
2871 if withmatchlen:
2872 pieces.append("WITHMATCHLEN")
2873 return self.execute_command("LCS", *pieces, keys=[key1, key2])
2876class AsyncBasicKeyCommands(BasicKeyCommands):
2877 def __delitem__(self, name: KeyT):
2878 raise TypeError("Async Redis client does not support class deletion")
2880 def __contains__(self, name: KeyT):
2881 raise TypeError("Async Redis client does not support class inclusion")
2883 def __getitem__(self, name: KeyT):
2884 raise TypeError("Async Redis client does not support class retrieval")
2886 def __setitem__(self, name: KeyT, value: EncodableT):
2887 raise TypeError("Async Redis client does not support class assignment")
2889 async def watch(self, *names: KeyT) -> None:
2890 return super().watch(*names)
2892 async def unwatch(self) -> None:
2893 return super().unwatch()
2896class ListCommands(CommandsProtocol):
2897 """
2898 Redis commands for List data type.
2899 see: https://redis.io/topics/data-types#lists
2900 """
2902 def blpop(
2903 self, keys: KeysT, timeout: Optional[Number] = 0
2904 ) -> Union[Awaitable[list], list]:
2905 """
2906 LPOP a value off of the first non-empty list
2907 named in ``keys``.
2909 If none of the lists in ``keys`` has a value to LPOP, then block
2910 for ``timeout`` seconds, or until a value gets pushed on to one
2911 of the lists.
2913 If timeout is 0, then block indefinitely.
2915 For more information, see https://redis.io/commands/blpop
2916 """
2917 if timeout is None:
2918 timeout = 0
2919 keys = list_or_args(keys, None)
2920 keys.append(timeout)
2921 return self.execute_command("BLPOP", *keys)
2923 def brpop(
2924 self, keys: KeysT, timeout: Optional[Number] = 0
2925 ) -> Union[Awaitable[list], list]:
2926 """
2927 RPOP a value off of the first non-empty list
2928 named in ``keys``.
2930 If none of the lists in ``keys`` has a value to RPOP, then block
2931 for ``timeout`` seconds, or until a value gets pushed on to one
2932 of the lists.
2934 If timeout is 0, then block indefinitely.
2936 For more information, see https://redis.io/commands/brpop
2937 """
2938 if timeout is None:
2939 timeout = 0
2940 keys = list_or_args(keys, None)
2941 keys.append(timeout)
2942 return self.execute_command("BRPOP", *keys)
2944 def brpoplpush(
2945 self, src: KeyT, dst: KeyT, timeout: Optional[Number] = 0
2946 ) -> Union[Awaitable[Optional[str]], Optional[str]]:
2947 """
2948 Pop a value off the tail of ``src``, push it on the head of ``dst``
2949 and then return it.
2951 This command blocks until a value is in ``src`` or until ``timeout``
2952 seconds elapse, whichever is first. A ``timeout`` value of 0 blocks
2953 forever.
2955 For more information, see https://redis.io/commands/brpoplpush
2956 """
2957 if timeout is None:
2958 timeout = 0
2959 return self.execute_command("BRPOPLPUSH", src, dst, timeout)
2961 def blmpop(
2962 self,
2963 timeout: float,
2964 numkeys: int,
2965 *args: str,
2966 direction: str,
2967 count: Optional[int] = 1,
2968 ) -> Optional[list]:
2969 """
2970 Pop ``count`` values (default 1) from first non-empty in the list
2971 of provided key names.
2973 When all lists are empty this command blocks the connection until another
2974 client pushes to it or until the timeout, timeout of 0 blocks indefinitely
2976 For more information, see https://redis.io/commands/blmpop
2977 """
2978 cmd_args = [timeout, numkeys, *args, direction, "COUNT", count]
2980 return self.execute_command("BLMPOP", *cmd_args)
2982 def lmpop(
2983 self,
2984 num_keys: int,
2985 *args: str,
2986 direction: str,
2987 count: Optional[int] = 1,
2988 ) -> Union[Awaitable[list], list]:
2989 """
2990 Pop ``count`` values (default 1) first non-empty list key from the list
2991 of args provided key names.
2993 For more information, see https://redis.io/commands/lmpop
2994 """
2995 cmd_args = [num_keys] + list(args) + [direction]
2996 if count != 1:
2997 cmd_args.extend(["COUNT", count])
2999 return self.execute_command("LMPOP", *cmd_args)
3001 def lindex(
3002 self, name: KeyT, index: int
3003 ) -> Union[Awaitable[Optional[str]], Optional[str]]:
3004 """
3005 Return the item from list ``name`` at position ``index``
3007 Negative indexes are supported and will return an item at the
3008 end of the list
3010 For more information, see https://redis.io/commands/lindex
3011 """
3012 return self.execute_command("LINDEX", name, index, keys=[name])
3014 def linsert(
3015 self, name: KeyT, where: str, refvalue: str, value: str
3016 ) -> Union[Awaitable[int], int]:
3017 """
3018 Insert ``value`` in list ``name`` either immediately before or after
3019 [``where``] ``refvalue``
3021 Returns the new length of the list on success or -1 if ``refvalue``
3022 is not in the list.
3024 For more information, see https://redis.io/commands/linsert
3025 """
3026 return self.execute_command("LINSERT", name, where, refvalue, value)
3028 def llen(self, name: KeyT) -> Union[Awaitable[int], int]:
3029 """
3030 Return the length of the list ``name``
3032 For more information, see https://redis.io/commands/llen
3033 """
3034 return self.execute_command("LLEN", name, keys=[name])
3036 def lpop(
3037 self,
3038 name: KeyT,
3039 count: Optional[int] = None,
3040 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]:
3041 """
3042 Removes and returns the first elements of the list ``name``.
3044 By default, the command pops a single element from the beginning of
3045 the list. When provided with the optional ``count`` argument, the reply
3046 will consist of up to count elements, depending on the list's length.
3048 For more information, see https://redis.io/commands/lpop
3049 """
3050 if count is not None:
3051 return self.execute_command("LPOP", name, count)
3052 else:
3053 return self.execute_command("LPOP", name)
3055 def lpush(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
3056 """
3057 Push ``values`` onto the head of the list ``name``
3059 For more information, see https://redis.io/commands/lpush
3060 """
3061 return self.execute_command("LPUSH", name, *values)
3063 def lpushx(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
3064 """
3065 Push ``value`` onto the head of the list ``name`` if ``name`` exists
3067 For more information, see https://redis.io/commands/lpushx
3068 """
3069 return self.execute_command("LPUSHX", name, *values)
3071 def lrange(self, name: KeyT, start: int, end: int) -> Union[Awaitable[list], list]:
3072 """
3073 Return a slice of the list ``name`` between
3074 position ``start`` and ``end``
3076 ``start`` and ``end`` can be negative numbers just like
3077 Python slicing notation
3079 For more information, see https://redis.io/commands/lrange
3080 """
3081 return self.execute_command("LRANGE", name, start, end, keys=[name])
3083 def lrem(self, name: KeyT, count: int, value: str) -> Union[Awaitable[int], int]:
3084 """
3085 Remove the first ``count`` occurrences of elements equal to ``value``
3086 from the list stored at ``name``.
3088 The count argument influences the operation in the following ways:
3089 count > 0: Remove elements equal to value moving from head to tail.
3090 count < 0: Remove elements equal to value moving from tail to head.
3091 count = 0: Remove all elements equal to value.
3093 For more information, see https://redis.io/commands/lrem
3094 """
3095 return self.execute_command("LREM", name, count, value)
3097 def lset(self, name: KeyT, index: int, value: str) -> Union[Awaitable[str], str]:
3098 """
3099 Set element at ``index`` of list ``name`` to ``value``
3101 For more information, see https://redis.io/commands/lset
3102 """
3103 return self.execute_command("LSET", name, index, value)
3105 def ltrim(self, name: KeyT, start: int, end: int) -> Union[Awaitable[str], str]:
3106 """
3107 Trim the list ``name``, removing all values not within the slice
3108 between ``start`` and ``end``
3110 ``start`` and ``end`` can be negative numbers just like
3111 Python slicing notation
3113 For more information, see https://redis.io/commands/ltrim
3114 """
3115 return self.execute_command("LTRIM", name, start, end)
3117 def rpop(
3118 self,
3119 name: KeyT,
3120 count: Optional[int] = None,
3121 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]:
3122 """
3123 Removes and returns the last elements of the list ``name``.
3125 By default, the command pops a single element from the end of the list.
3126 When provided with the optional ``count`` argument, the reply will
3127 consist of up to count elements, depending on the list's length.
3129 For more information, see https://redis.io/commands/rpop
3130 """
3131 if count is not None:
3132 return self.execute_command("RPOP", name, count)
3133 else:
3134 return self.execute_command("RPOP", name)
3136 def rpoplpush(self, src: KeyT, dst: KeyT) -> Union[Awaitable[str], str]:
3137 """
3138 RPOP a value off of the ``src`` list and atomically LPUSH it
3139 on to the ``dst`` list. Returns the value.
3141 For more information, see https://redis.io/commands/rpoplpush
3142 """
3143 return self.execute_command("RPOPLPUSH", src, dst)
3145 def rpush(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
3146 """
3147 Push ``values`` onto the tail of the list ``name``
3149 For more information, see https://redis.io/commands/rpush
3150 """
3151 return self.execute_command("RPUSH", name, *values)
3153 def rpushx(self, name: KeyT, *values: str) -> Union[Awaitable[int], int]:
3154 """
3155 Push ``value`` onto the tail of the list ``name`` if ``name`` exists
3157 For more information, see https://redis.io/commands/rpushx
3158 """
3159 return self.execute_command("RPUSHX", name, *values)
3161 def lpos(
3162 self,
3163 name: KeyT,
3164 value: str,
3165 rank: Optional[int] = None,
3166 count: Optional[int] = None,
3167 maxlen: Optional[int] = None,
3168 ) -> Union[str, List, None]:
3169 """
3170 Get position of ``value`` within the list ``name``
3172 If specified, ``rank`` indicates the "rank" of the first element to
3173 return in case there are multiple copies of ``value`` in the list.
3174 By default, LPOS returns the position of the first occurrence of
3175 ``value`` in the list. When ``rank`` 2, LPOS returns the position of
3176 the second ``value`` in the list. If ``rank`` is negative, LPOS
3177 searches the list in reverse. For example, -1 would return the
3178 position of the last occurrence of ``value`` and -2 would return the
3179 position of the next to last occurrence of ``value``.
3181 If specified, ``count`` indicates that LPOS should return a list of
3182 up to ``count`` positions. A ``count`` of 2 would return a list of
3183 up to 2 positions. A ``count`` of 0 returns a list of all positions
3184 matching ``value``. When ``count`` is specified and but ``value``
3185 does not exist in the list, an empty list is returned.
3187 If specified, ``maxlen`` indicates the maximum number of list
3188 elements to scan. A ``maxlen`` of 1000 will only return the
3189 position(s) of items within the first 1000 entries in the list.
3190 A ``maxlen`` of 0 (the default) will scan the entire list.
3192 For more information, see https://redis.io/commands/lpos
3193 """
3194 pieces: list[EncodableT] = [name, value]
3195 if rank is not None:
3196 pieces.extend(["RANK", rank])
3198 if count is not None:
3199 pieces.extend(["COUNT", count])
3201 if maxlen is not None:
3202 pieces.extend(["MAXLEN", maxlen])
3204 return self.execute_command("LPOS", *pieces, keys=[name])
3206 def sort(
3207 self,
3208 name: KeyT,
3209 start: Optional[int] = None,
3210 num: Optional[int] = None,
3211 by: Optional[str] = None,
3212 get: Optional[List[str]] = None,
3213 desc: bool = False,
3214 alpha: bool = False,
3215 store: Optional[str] = None,
3216 groups: Optional[bool] = False,
3217 ) -> Union[List, int]:
3218 """
3219 Sort and return the list, set or sorted set at ``name``.
3221 ``start`` and ``num`` allow for paging through the sorted data
3223 ``by`` allows using an external key to weight and sort the items.
3224 Use an "*" to indicate where in the key the item value is located
3226 ``get`` allows for returning items from external keys rather than the
3227 sorted data itself. Use an "*" to indicate where in the key
3228 the item value is located
3230 ``desc`` allows for reversing the sort
3232 ``alpha`` allows for sorting lexicographically rather than numerically
3234 ``store`` allows for storing the result of the sort into
3235 the key ``store``
3237 ``groups`` if set to True and if ``get`` contains at least two
3238 elements, sort will return a list of tuples, each containing the
3239 values fetched from the arguments to ``get``.
3241 For more information, see https://redis.io/commands/sort
3242 """
3243 if (start is not None and num is None) or (num is not None and start is None):
3244 raise DataError("``start`` and ``num`` must both be specified")
3246 pieces: list[EncodableT] = [name]
3247 if by is not None:
3248 pieces.extend([b"BY", by])
3249 if start is not None and num is not None:
3250 pieces.extend([b"LIMIT", start, num])
3251 if get is not None:
3252 # If get is a string assume we want to get a single value.
3253 # Otherwise assume it's an interable and we want to get multiple
3254 # values. We can't just iterate blindly because strings are
3255 # iterable.
3256 if isinstance(get, (bytes, str)):
3257 pieces.extend([b"GET", get])
3258 else:
3259 for g in get:
3260 pieces.extend([b"GET", g])
3261 if desc:
3262 pieces.append(b"DESC")
3263 if alpha:
3264 pieces.append(b"ALPHA")
3265 if store is not None:
3266 pieces.extend([b"STORE", store])
3267 if groups:
3268 if not get or isinstance(get, (bytes, str)) or len(get) < 2:
3269 raise DataError(
3270 'when using "groups" the "get" argument '
3271 "must be specified and contain at least "
3272 "two keys"
3273 )
3275 options = {"groups": len(get) if groups else None}
3276 options["keys"] = [name]
3277 return self.execute_command("SORT", *pieces, **options)
3279 def sort_ro(
3280 self,
3281 key: str,
3282 start: Optional[int] = None,
3283 num: Optional[int] = None,
3284 by: Optional[str] = None,
3285 get: Optional[List[str]] = None,
3286 desc: bool = False,
3287 alpha: bool = False,
3288 ) -> list:
3289 """
3290 Returns the elements contained in the list, set or sorted set at key.
3291 (read-only variant of the SORT command)
3293 ``start`` and ``num`` allow for paging through the sorted data
3295 ``by`` allows using an external key to weight and sort the items.
3296 Use an "*" to indicate where in the key the item value is located
3298 ``get`` allows for returning items from external keys rather than the
3299 sorted data itself. Use an "*" to indicate where in the key
3300 the item value is located
3302 ``desc`` allows for reversing the sort
3304 ``alpha`` allows for sorting lexicographically rather than numerically
3306 For more information, see https://redis.io/commands/sort_ro
3307 """
3308 return self.sort(
3309 key, start=start, num=num, by=by, get=get, desc=desc, alpha=alpha
3310 )
3313AsyncListCommands = ListCommands
3316class ScanCommands(CommandsProtocol):
3317 """
3318 Redis SCAN commands.
3319 see: https://redis.io/commands/scan
3320 """
3322 def scan(
3323 self,
3324 cursor: int = 0,
3325 match: Union[PatternT, None] = None,
3326 count: Optional[int] = None,
3327 _type: Optional[str] = None,
3328 **kwargs,
3329 ) -> ResponseT:
3330 """
3331 Incrementally return lists of key names. Also return a cursor
3332 indicating the scan position.
3334 ``match`` allows for filtering the keys by pattern
3336 ``count`` provides a hint to Redis about the number of keys to
3337 return per batch.
3339 ``_type`` filters the returned values by a particular Redis type.
3340 Stock Redis instances allow for the following types:
3341 HASH, LIST, SET, STREAM, STRING, ZSET
3342 Additionally, Redis modules can expose other types as well.
3344 For more information, see https://redis.io/commands/scan
3345 """
3346 pieces: list[EncodableT] = [cursor]
3347 if match is not None:
3348 pieces.extend([b"MATCH", match])
3349 if count is not None:
3350 pieces.extend([b"COUNT", count])
3351 if _type is not None:
3352 pieces.extend([b"TYPE", _type])
3353 return self.execute_command("SCAN", *pieces, **kwargs)
3355 def scan_iter(
3356 self,
3357 match: Union[PatternT, None] = None,
3358 count: Optional[int] = None,
3359 _type: Optional[str] = None,
3360 **kwargs,
3361 ) -> Iterator:
3362 """
3363 Make an iterator using the SCAN command so that the client doesn't
3364 need to remember the cursor position.
3366 ``match`` allows for filtering the keys by pattern
3368 ``count`` provides a hint to Redis about the number of keys to
3369 return per batch.
3371 ``_type`` filters the returned values by a particular Redis type.
3372 Stock Redis instances allow for the following types:
3373 HASH, LIST, SET, STREAM, STRING, ZSET
3374 Additionally, Redis modules can expose other types as well.
3375 """
3376 cursor = "0"
3377 while cursor != 0:
3378 cursor, data = self.scan(
3379 cursor=cursor, match=match, count=count, _type=_type, **kwargs
3380 )
3381 yield from data
3383 def sscan(
3384 self,
3385 name: KeyT,
3386 cursor: int = 0,
3387 match: Union[PatternT, None] = None,
3388 count: Optional[int] = None,
3389 ) -> ResponseT:
3390 """
3391 Incrementally return lists of elements in a set. Also return a cursor
3392 indicating the scan position.
3394 ``match`` allows for filtering the keys by pattern
3396 ``count`` allows for hint the minimum number of returns
3398 For more information, see https://redis.io/commands/sscan
3399 """
3400 pieces: list[EncodableT] = [name, cursor]
3401 if match is not None:
3402 pieces.extend([b"MATCH", match])
3403 if count is not None:
3404 pieces.extend([b"COUNT", count])
3405 return self.execute_command("SSCAN", *pieces)
3407 def sscan_iter(
3408 self,
3409 name: KeyT,
3410 match: Union[PatternT, None] = None,
3411 count: Optional[int] = None,
3412 ) -> Iterator:
3413 """
3414 Make an iterator using the SSCAN command so that the client doesn't
3415 need to remember the cursor position.
3417 ``match`` allows for filtering the keys by pattern
3419 ``count`` allows for hint the minimum number of returns
3420 """
3421 cursor = "0"
3422 while cursor != 0:
3423 cursor, data = self.sscan(name, cursor=cursor, match=match, count=count)
3424 yield from data
3426 def hscan(
3427 self,
3428 name: KeyT,
3429 cursor: int = 0,
3430 match: Union[PatternT, None] = None,
3431 count: Optional[int] = None,
3432 no_values: Union[bool, None] = None,
3433 ) -> ResponseT:
3434 """
3435 Incrementally return key/value slices in a hash. Also return a cursor
3436 indicating the scan position.
3438 ``match`` allows for filtering the keys by pattern
3440 ``count`` allows for hint the minimum number of returns
3442 ``no_values`` indicates to return only the keys, without values.
3444 For more information, see https://redis.io/commands/hscan
3445 """
3446 pieces: list[EncodableT] = [name, cursor]
3447 if match is not None:
3448 pieces.extend([b"MATCH", match])
3449 if count is not None:
3450 pieces.extend([b"COUNT", count])
3451 if no_values is not None:
3452 pieces.extend([b"NOVALUES"])
3453 return self.execute_command("HSCAN", *pieces, no_values=no_values)
3455 def hscan_iter(
3456 self,
3457 name: str,
3458 match: Union[PatternT, None] = None,
3459 count: Optional[int] = None,
3460 no_values: Union[bool, None] = None,
3461 ) -> Iterator:
3462 """
3463 Make an iterator using the HSCAN command so that the client doesn't
3464 need to remember the cursor position.
3466 ``match`` allows for filtering the keys by pattern
3468 ``count`` allows for hint the minimum number of returns
3470 ``no_values`` indicates to return only the keys, without values
3471 """
3472 cursor = "0"
3473 while cursor != 0:
3474 cursor, data = self.hscan(
3475 name, cursor=cursor, match=match, count=count, no_values=no_values
3476 )
3477 if no_values:
3478 yield from data
3479 else:
3480 yield from data.items()
3482 def zscan(
3483 self,
3484 name: KeyT,
3485 cursor: int = 0,
3486 match: Union[PatternT, None] = None,
3487 count: Optional[int] = None,
3488 score_cast_func: Union[type, Callable] = float,
3489 ) -> ResponseT:
3490 """
3491 Incrementally return lists of elements in a sorted set. Also return a
3492 cursor indicating the scan position.
3494 ``match`` allows for filtering the keys by pattern
3496 ``count`` allows for hint the minimum number of returns
3498 ``score_cast_func`` a callable used to cast the score return value
3500 For more information, see https://redis.io/commands/zscan
3501 """
3502 pieces = [name, cursor]
3503 if match is not None:
3504 pieces.extend([b"MATCH", match])
3505 if count is not None:
3506 pieces.extend([b"COUNT", count])
3507 options = {"score_cast_func": score_cast_func}
3508 return self.execute_command("ZSCAN", *pieces, **options)
3510 def zscan_iter(
3511 self,
3512 name: KeyT,
3513 match: Union[PatternT, None] = None,
3514 count: Optional[int] = None,
3515 score_cast_func: Union[type, Callable] = float,
3516 ) -> Iterator:
3517 """
3518 Make an iterator using the ZSCAN command so that the client doesn't
3519 need to remember the cursor position.
3521 ``match`` allows for filtering the keys by pattern
3523 ``count`` allows for hint the minimum number of returns
3525 ``score_cast_func`` a callable used to cast the score return value
3526 """
3527 cursor = "0"
3528 while cursor != 0:
3529 cursor, data = self.zscan(
3530 name,
3531 cursor=cursor,
3532 match=match,
3533 count=count,
3534 score_cast_func=score_cast_func,
3535 )
3536 yield from data
3539class AsyncScanCommands(ScanCommands):
3540 async def scan_iter(
3541 self,
3542 match: Union[PatternT, None] = None,
3543 count: Optional[int] = None,
3544 _type: Optional[str] = None,
3545 **kwargs,
3546 ) -> AsyncIterator:
3547 """
3548 Make an iterator using the SCAN command so that the client doesn't
3549 need to remember the cursor position.
3551 ``match`` allows for filtering the keys by pattern
3553 ``count`` provides a hint to Redis about the number of keys to
3554 return per batch.
3556 ``_type`` filters the returned values by a particular Redis type.
3557 Stock Redis instances allow for the following types:
3558 HASH, LIST, SET, STREAM, STRING, ZSET
3559 Additionally, Redis modules can expose other types as well.
3560 """
3561 cursor = "0"
3562 while cursor != 0:
3563 cursor, data = await self.scan(
3564 cursor=cursor, match=match, count=count, _type=_type, **kwargs
3565 )
3566 for d in data:
3567 yield d
3569 async def sscan_iter(
3570 self,
3571 name: KeyT,
3572 match: Union[PatternT, None] = None,
3573 count: Optional[int] = None,
3574 ) -> AsyncIterator:
3575 """
3576 Make an iterator using the SSCAN command so that the client doesn't
3577 need to remember the cursor position.
3579 ``match`` allows for filtering the keys by pattern
3581 ``count`` allows for hint the minimum number of returns
3582 """
3583 cursor = "0"
3584 while cursor != 0:
3585 cursor, data = await self.sscan(
3586 name, cursor=cursor, match=match, count=count
3587 )
3588 for d in data:
3589 yield d
3591 async def hscan_iter(
3592 self,
3593 name: str,
3594 match: Union[PatternT, None] = None,
3595 count: Optional[int] = None,
3596 no_values: Union[bool, None] = None,
3597 ) -> AsyncIterator:
3598 """
3599 Make an iterator using the HSCAN command so that the client doesn't
3600 need to remember the cursor position.
3602 ``match`` allows for filtering the keys by pattern
3604 ``count`` allows for hint the minimum number of returns
3606 ``no_values`` indicates to return only the keys, without values
3607 """
3608 cursor = "0"
3609 while cursor != 0:
3610 cursor, data = await self.hscan(
3611 name, cursor=cursor, match=match, count=count, no_values=no_values
3612 )
3613 if no_values:
3614 for it in data:
3615 yield it
3616 else:
3617 for it in data.items():
3618 yield it
3620 async def zscan_iter(
3621 self,
3622 name: KeyT,
3623 match: Union[PatternT, None] = None,
3624 count: Optional[int] = None,
3625 score_cast_func: Union[type, Callable] = float,
3626 ) -> AsyncIterator:
3627 """
3628 Make an iterator using the ZSCAN command so that the client doesn't
3629 need to remember the cursor position.
3631 ``match`` allows for filtering the keys by pattern
3633 ``count`` allows for hint the minimum number of returns
3635 ``score_cast_func`` a callable used to cast the score return value
3636 """
3637 cursor = "0"
3638 while cursor != 0:
3639 cursor, data = await self.zscan(
3640 name,
3641 cursor=cursor,
3642 match=match,
3643 count=count,
3644 score_cast_func=score_cast_func,
3645 )
3646 for d in data:
3647 yield d
3650class SetCommands(CommandsProtocol):
3651 """
3652 Redis commands for Set data type.
3653 see: https://redis.io/topics/data-types#sets
3654 """
3656 def sadd(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
3657 """
3658 Add ``value(s)`` to set ``name``
3660 For more information, see https://redis.io/commands/sadd
3661 """
3662 return self.execute_command("SADD", name, *values)
3664 def scard(self, name: KeyT) -> Union[Awaitable[int], int]:
3665 """
3666 Return the number of elements in set ``name``
3668 For more information, see https://redis.io/commands/scard
3669 """
3670 return self.execute_command("SCARD", name, keys=[name])
3672 def sdiff(self, keys: List, *args: List) -> Union[Awaitable[list], list]:
3673 """
3674 Return the difference of sets specified by ``keys``
3676 For more information, see https://redis.io/commands/sdiff
3677 """
3678 args = list_or_args(keys, args)
3679 return self.execute_command("SDIFF", *args, keys=args)
3681 def sdiffstore(
3682 self, dest: str, keys: List, *args: List
3683 ) -> Union[Awaitable[int], int]:
3684 """
3685 Store the difference of sets specified by ``keys`` into a new
3686 set named ``dest``. Returns the number of keys in the new set.
3688 For more information, see https://redis.io/commands/sdiffstore
3689 """
3690 args = list_or_args(keys, args)
3691 return self.execute_command("SDIFFSTORE", dest, *args)
3693 def sinter(self, keys: List, *args: List) -> Union[Awaitable[list], list]:
3694 """
3695 Return the intersection of sets specified by ``keys``
3697 For more information, see https://redis.io/commands/sinter
3698 """
3699 args = list_or_args(keys, args)
3700 return self.execute_command("SINTER", *args, keys=args)
3702 def sintercard(
3703 self, numkeys: int, keys: List[KeyT], limit: int = 0
3704 ) -> Union[Awaitable[int], int]:
3705 """
3706 Return the cardinality of the intersect of multiple sets specified by ``keys``.
3708 When LIMIT provided (defaults to 0 and means unlimited), if the intersection
3709 cardinality reaches limit partway through the computation, the algorithm will
3710 exit and yield limit as the cardinality
3712 For more information, see https://redis.io/commands/sintercard
3713 """
3714 args = [numkeys, *keys, "LIMIT", limit]
3715 return self.execute_command("SINTERCARD", *args, keys=keys)
3717 def sinterstore(
3718 self, dest: KeyT, keys: List, *args: List
3719 ) -> Union[Awaitable[int], int]:
3720 """
3721 Store the intersection of sets specified by ``keys`` into a new
3722 set named ``dest``. Returns the number of keys in the new set.
3724 For more information, see https://redis.io/commands/sinterstore
3725 """
3726 args = list_or_args(keys, args)
3727 return self.execute_command("SINTERSTORE", dest, *args)
3729 def sismember(
3730 self, name: KeyT, value: str
3731 ) -> Union[Awaitable[Union[Literal[0], Literal[1]]], Union[Literal[0], Literal[1]]]:
3732 """
3733 Return whether ``value`` is a member of set ``name``:
3734 - 1 if the value is a member of the set.
3735 - 0 if the value is not a member of the set or if key does not exist.
3737 For more information, see https://redis.io/commands/sismember
3738 """
3739 return self.execute_command("SISMEMBER", name, value, keys=[name])
3741 def smembers(self, name: KeyT) -> Union[Awaitable[Set], Set]:
3742 """
3743 Return all members of the set ``name``
3745 For more information, see https://redis.io/commands/smembers
3746 """
3747 return self.execute_command("SMEMBERS", name, keys=[name])
3749 def smismember(
3750 self, name: KeyT, values: List, *args: List
3751 ) -> Union[
3752 Awaitable[List[Union[Literal[0], Literal[1]]]],
3753 List[Union[Literal[0], Literal[1]]],
3754 ]:
3755 """
3756 Return whether each value in ``values`` is a member of the set ``name``
3757 as a list of ``int`` in the order of ``values``:
3758 - 1 if the value is a member of the set.
3759 - 0 if the value is not a member of the set or if key does not exist.
3761 For more information, see https://redis.io/commands/smismember
3762 """
3763 args = list_or_args(values, args)
3764 return self.execute_command("SMISMEMBER", name, *args, keys=[name])
3766 def smove(self, src: KeyT, dst: KeyT, value: str) -> Union[Awaitable[bool], bool]:
3767 """
3768 Move ``value`` from set ``src`` to set ``dst`` atomically
3770 For more information, see https://redis.io/commands/smove
3771 """
3772 return self.execute_command("SMOVE", src, dst, value)
3774 def spop(
3775 self, name: KeyT, count: Optional[int] = None
3776 ) -> Union[Awaitable[Union[str, List, None]], str, List, None]:
3777 """
3778 Remove and return a random member of set ``name``
3780 For more information, see https://redis.io/commands/spop
3781 """
3782 args = (count is not None) and [count] or []
3783 return self.execute_command("SPOP", name, *args)
3785 def srandmember(
3786 self, name: KeyT, number: Optional[int] = None
3787 ) -> Union[Awaitable[Union[str, List, None]], str, List, None]:
3788 """
3789 If ``number`` is None, returns a random member of set ``name``.
3791 If ``number`` is supplied, returns a list of ``number`` random
3792 members of set ``name``. Note this is only available when running
3793 Redis 2.6+.
3795 For more information, see https://redis.io/commands/srandmember
3796 """
3797 args = (number is not None) and [number] or []
3798 return self.execute_command("SRANDMEMBER", name, *args)
3800 def srem(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
3801 """
3802 Remove ``values`` from set ``name``
3804 For more information, see https://redis.io/commands/srem
3805 """
3806 return self.execute_command("SREM", name, *values)
3808 def sunion(self, keys: List, *args: List) -> Union[Awaitable[List], List]:
3809 """
3810 Return the union of sets specified by ``keys``
3812 For more information, see https://redis.io/commands/sunion
3813 """
3814 args = list_or_args(keys, args)
3815 return self.execute_command("SUNION", *args, keys=args)
3817 def sunionstore(
3818 self, dest: KeyT, keys: List, *args: List
3819 ) -> Union[Awaitable[int], int]:
3820 """
3821 Store the union of sets specified by ``keys`` into a new
3822 set named ``dest``. Returns the number of keys in the new set.
3824 For more information, see https://redis.io/commands/sunionstore
3825 """
3826 args = list_or_args(keys, args)
3827 return self.execute_command("SUNIONSTORE", dest, *args)
3830AsyncSetCommands = SetCommands
3833class StreamCommands(CommandsProtocol):
3834 """
3835 Redis commands for Stream data type.
3836 see: https://redis.io/topics/streams-intro
3837 """
3839 def xack(self, name: KeyT, groupname: GroupT, *ids: StreamIdT) -> ResponseT:
3840 """
3841 Acknowledges the successful processing of one or more messages.
3843 Args:
3844 name: name of the stream.
3845 groupname: name of the consumer group.
3846 *ids: message ids to acknowledge.
3848 For more information, see https://redis.io/commands/xack
3849 """
3850 return self.execute_command("XACK", name, groupname, *ids)
3852 def xackdel(
3853 self,
3854 name: KeyT,
3855 groupname: GroupT,
3856 *ids: StreamIdT,
3857 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF",
3858 ) -> ResponseT:
3859 """
3860 Combines the functionality of XACK and XDEL. Acknowledges the specified
3861 message IDs in the given consumer group and simultaneously attempts to
3862 delete the corresponding entries from the stream.
3863 """
3864 if not ids:
3865 raise DataError("XACKDEL requires at least one message ID")
3867 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
3868 raise DataError("XACKDEL ref_policy must be one of: KEEPREF, DELREF, ACKED")
3870 pieces = [name, groupname, ref_policy, "IDS", len(ids)]
3871 pieces.extend(ids)
3872 return self.execute_command("XACKDEL", *pieces)
3874 def xadd(
3875 self,
3876 name: KeyT,
3877 fields: Dict[FieldT, EncodableT],
3878 id: StreamIdT = "*",
3879 maxlen: Optional[int] = None,
3880 approximate: bool = True,
3881 nomkstream: bool = False,
3882 minid: Union[StreamIdT, None] = None,
3883 limit: Optional[int] = None,
3884 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None,
3885 idmpauto: Optional[str] = None,
3886 idmp: Optional[tuple[str, bytes]] = None,
3887 ) -> ResponseT:
3888 """
3889 Add to a stream.
3890 name: name of the stream
3891 fields: dict of field/value pairs to insert into the stream
3892 id: Location to insert this record. By default it is appended.
3893 maxlen: truncate old stream members beyond this size.
3894 Can't be specified with minid.
3895 approximate: actual stream length may be slightly more than maxlen
3896 nomkstream: When set to true, do not make a stream
3897 minid: the minimum id in the stream to query.
3898 Can't be specified with maxlen.
3899 limit: specifies the maximum number of entries to retrieve
3900 ref_policy: optional reference policy for consumer groups when trimming:
3901 - KEEPREF (default): When trimming, preserves references in consumer groups' PEL
3902 - DELREF: When trimming, removes all references from consumer groups' PEL
3903 - ACKED: When trimming, only removes entries acknowledged by all consumer groups
3904 idmpauto: Producer ID for automatic idempotent ID calculation.
3905 Automatically calculates an idempotent ID based on entry content to prevent
3906 duplicate entries. Can only be used with id='*'. Creates an IDMP map if it
3907 doesn't exist yet. The producer ID must be unique per producer and consistent
3908 across restarts.
3909 idmp: Tuple of (producer_id, idempotent_id) for explicit idempotent ID.
3910 Uses a specific idempotent ID to prevent duplicate entries. Can only be used
3911 with id='*'. The producer ID must be unique per producer and consistent across
3912 restarts. The idempotent ID must be unique per message and per producer.
3913 Shorter idempotent IDs require less memory and allow faster processing.
3914 Creates an IDMP map if it doesn't exist yet.
3916 For more information, see https://redis.io/commands/xadd
3917 """
3918 pieces: list[EncodableT] = []
3919 if maxlen is not None and minid is not None:
3920 raise DataError("Only one of ```maxlen``` or ```minid``` may be specified")
3922 if idmpauto is not None and idmp is not None:
3923 raise DataError("Only one of ```idmpauto``` or ```idmp``` may be specified")
3925 if (idmpauto is not None or idmp is not None) and id != "*":
3926 raise DataError("IDMPAUTO and IDMP can only be used with id='*'")
3928 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
3929 raise DataError("XADD ref_policy must be one of: KEEPREF, DELREF, ACKED")
3931 if nomkstream:
3932 pieces.append(b"NOMKSTREAM")
3933 if ref_policy is not None:
3934 pieces.append(ref_policy)
3935 if idmpauto is not None:
3936 pieces.extend([b"IDMPAUTO", idmpauto])
3937 if idmp is not None:
3938 if not isinstance(idmp, tuple) or len(idmp) != 2:
3939 raise DataError(
3940 "XADD idmp must be a tuple of (producer_id, idempotent_id)"
3941 )
3942 pieces.extend([b"IDMP", idmp[0], idmp[1]])
3943 if maxlen is not None:
3944 if not isinstance(maxlen, int) or maxlen < 0:
3945 raise DataError("XADD maxlen must be non-negative integer")
3946 pieces.append(b"MAXLEN")
3947 if approximate:
3948 pieces.append(b"~")
3949 pieces.append(str(maxlen))
3950 if minid is not None:
3951 pieces.append(b"MINID")
3952 if approximate:
3953 pieces.append(b"~")
3954 pieces.append(minid)
3955 if limit is not None:
3956 pieces.extend([b"LIMIT", limit])
3957 pieces.append(id)
3958 if not isinstance(fields, dict) or len(fields) == 0:
3959 raise DataError("XADD fields must be a non-empty dict")
3960 for pair in fields.items():
3961 pieces.extend(pair)
3962 return self.execute_command("XADD", name, *pieces)
3964 def xcfgset(
3965 self,
3966 name: KeyT,
3967 idmp_duration: Optional[int] = None,
3968 idmp_maxsize: Optional[int] = None,
3969 ) -> ResponseT:
3970 """
3971 Configure the idempotency parameters for a stream's IDMP map.
3973 Sets how long Redis remembers each idempotent ID (iid) and the maximum
3974 number of iids to track. This command clears the existing IDMP map
3975 (Redis forgets all previously stored iids), but only if the configuration
3976 value actually changes.
3978 Args:
3979 name: The name of the stream.
3980 idmp_duration: How long Redis remembers each iid in seconds.
3981 Default: 100 seconds (or value set by stream-idmp-duration config).
3982 Minimum: 1 second, Maximum: 300 seconds.
3983 Redis won't forget an iid for this duration (unless maxsize is reached).
3984 Should accommodate application crash recovery time.
3985 idmp_maxsize: Maximum number of iids Redis remembers per producer ID (pid).
3986 Default: 100 iids (or value set by stream-idmp-maxsize config).
3987 Minimum: 1 iid, Maximum: 1,000,000 (1M) iids.
3988 Should be set to: mark-delay [in msec] × (messages/msec) + margin.
3989 Example: 10K msgs/sec (10 msgs/msec), 80 msec mark-delay
3990 → maxsize = 10 × 80 + margin = 1000 iids.
3992 Returns:
3993 OK on success.
3995 For more information, see https://redis.io/commands/xcfgset
3996 """
3997 if idmp_duration is None and idmp_maxsize is None:
3998 raise DataError(
3999 "XCFGSET requires at least one of idmp_duration or idmp_maxsize"
4000 )
4002 pieces: list[EncodableT] = []
4004 if idmp_duration is not None:
4005 if (
4006 not isinstance(idmp_duration, int)
4007 or idmp_duration < 1
4008 or idmp_duration > 300
4009 ):
4010 raise DataError(
4011 "XCFGSET idmp_duration must be an integer between 1 and 300"
4012 )
4013 pieces.extend([b"IDMP-DURATION", idmp_duration])
4015 if idmp_maxsize is not None:
4016 if (
4017 not isinstance(idmp_maxsize, int)
4018 or idmp_maxsize < 1
4019 or idmp_maxsize > 1000000
4020 ):
4021 raise DataError(
4022 "XCFGSET idmp_maxsize must be an integer between 1 and 1,000,000"
4023 )
4024 pieces.extend([b"IDMP-MAXSIZE", idmp_maxsize])
4026 return self.execute_command("XCFGSET", name, *pieces)
4028 def xautoclaim(
4029 self,
4030 name: KeyT,
4031 groupname: GroupT,
4032 consumername: ConsumerT,
4033 min_idle_time: int,
4034 start_id: StreamIdT = "0-0",
4035 count: Optional[int] = None,
4036 justid: bool = False,
4037 ) -> ResponseT:
4038 """
4039 Transfers ownership of pending stream entries that match the specified
4040 criteria. Conceptually, equivalent to calling XPENDING and then XCLAIM,
4041 but provides a more straightforward way to deal with message delivery
4042 failures via SCAN-like semantics.
4043 name: name of the stream.
4044 groupname: name of the consumer group.
4045 consumername: name of a consumer that claims the message.
4046 min_idle_time: filter messages that were idle less than this amount of
4047 milliseconds.
4048 start_id: filter messages with equal or greater ID.
4049 count: optional integer, upper limit of the number of entries that the
4050 command attempts to claim. Set to 100 by default.
4051 justid: optional boolean, false by default. Return just an array of IDs
4052 of messages successfully claimed, without returning the actual message
4054 For more information, see https://redis.io/commands/xautoclaim
4055 """
4056 try:
4057 if int(min_idle_time) < 0:
4058 raise DataError(
4059 "XAUTOCLAIM min_idle_time must be a nonnegative integer"
4060 )
4061 except TypeError:
4062 pass
4064 kwargs = {}
4065 pieces = [name, groupname, consumername, min_idle_time, start_id]
4067 try:
4068 if int(count) < 0:
4069 raise DataError("XPENDING count must be a integer >= 0")
4070 pieces.extend([b"COUNT", count])
4071 except TypeError:
4072 pass
4073 if justid:
4074 pieces.append(b"JUSTID")
4075 kwargs["parse_justid"] = True
4077 return self.execute_command("XAUTOCLAIM", *pieces, **kwargs)
4079 def xclaim(
4080 self,
4081 name: KeyT,
4082 groupname: GroupT,
4083 consumername: ConsumerT,
4084 min_idle_time: int,
4085 message_ids: Union[List[StreamIdT], Tuple[StreamIdT]],
4086 idle: Optional[int] = None,
4087 time: Optional[int] = None,
4088 retrycount: Optional[int] = None,
4089 force: bool = False,
4090 justid: bool = False,
4091 ) -> ResponseT:
4092 """
4093 Changes the ownership of a pending message.
4095 name: name of the stream.
4097 groupname: name of the consumer group.
4099 consumername: name of a consumer that claims the message.
4101 min_idle_time: filter messages that were idle less than this amount of
4102 milliseconds
4104 message_ids: non-empty list or tuple of message IDs to claim
4106 idle: optional. Set the idle time (last time it was delivered) of the
4107 message in ms
4109 time: optional integer. This is the same as idle but instead of a
4110 relative amount of milliseconds, it sets the idle time to a specific
4111 Unix time (in milliseconds).
4113 retrycount: optional integer. set the retry counter to the specified
4114 value. This counter is incremented every time a message is delivered
4115 again.
4117 force: optional boolean, false by default. Creates the pending message
4118 entry in the PEL even if certain specified IDs are not already in the
4119 PEL assigned to a different client.
4121 justid: optional boolean, false by default. Return just an array of IDs
4122 of messages successfully claimed, without returning the actual message
4124 For more information, see https://redis.io/commands/xclaim
4125 """
4126 if not isinstance(min_idle_time, int) or min_idle_time < 0:
4127 raise DataError("XCLAIM min_idle_time must be a non negative integer")
4128 if not isinstance(message_ids, (list, tuple)) or not message_ids:
4129 raise DataError(
4130 "XCLAIM message_ids must be a non empty list or "
4131 "tuple of message IDs to claim"
4132 )
4134 kwargs = {}
4135 pieces: list[EncodableT] = [name, groupname, consumername, str(min_idle_time)]
4136 pieces.extend(list(message_ids))
4138 if idle is not None:
4139 if not isinstance(idle, int):
4140 raise DataError("XCLAIM idle must be an integer")
4141 pieces.extend((b"IDLE", str(idle)))
4142 if time is not None:
4143 if not isinstance(time, int):
4144 raise DataError("XCLAIM time must be an integer")
4145 pieces.extend((b"TIME", str(time)))
4146 if retrycount is not None:
4147 if not isinstance(retrycount, int):
4148 raise DataError("XCLAIM retrycount must be an integer")
4149 pieces.extend((b"RETRYCOUNT", str(retrycount)))
4151 if force:
4152 if not isinstance(force, bool):
4153 raise DataError("XCLAIM force must be a boolean")
4154 pieces.append(b"FORCE")
4155 if justid:
4156 if not isinstance(justid, bool):
4157 raise DataError("XCLAIM justid must be a boolean")
4158 pieces.append(b"JUSTID")
4159 kwargs["parse_justid"] = True
4160 return self.execute_command("XCLAIM", *pieces, **kwargs)
4162 def xdel(self, name: KeyT, *ids: StreamIdT) -> ResponseT:
4163 """
4164 Deletes one or more messages from a stream.
4166 Args:
4167 name: name of the stream.
4168 *ids: message ids to delete.
4170 For more information, see https://redis.io/commands/xdel
4171 """
4172 return self.execute_command("XDEL", name, *ids)
4174 def xdelex(
4175 self,
4176 name: KeyT,
4177 *ids: StreamIdT,
4178 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF",
4179 ) -> ResponseT:
4180 """
4181 Extended version of XDEL that provides more control over how message entries
4182 are deleted concerning consumer groups.
4183 """
4184 if not ids:
4185 raise DataError("XDELEX requires at least one message ID")
4187 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
4188 raise DataError("XDELEX ref_policy must be one of: KEEPREF, DELREF, ACKED")
4190 pieces = [name, ref_policy, "IDS", len(ids)]
4191 pieces.extend(ids)
4192 return self.execute_command("XDELEX", *pieces)
4194 def xgroup_create(
4195 self,
4196 name: KeyT,
4197 groupname: GroupT,
4198 id: StreamIdT = "$",
4199 mkstream: bool = False,
4200 entries_read: Optional[int] = None,
4201 ) -> ResponseT:
4202 """
4203 Create a new consumer group associated with a stream.
4204 name: name of the stream.
4205 groupname: name of the consumer group.
4206 id: ID of the last item in the stream to consider already delivered.
4208 For more information, see https://redis.io/commands/xgroup-create
4209 """
4210 pieces: list[EncodableT] = ["XGROUP CREATE", name, groupname, id]
4211 if mkstream:
4212 pieces.append(b"MKSTREAM")
4213 if entries_read is not None:
4214 pieces.extend(["ENTRIESREAD", entries_read])
4216 return self.execute_command(*pieces)
4218 def xgroup_delconsumer(
4219 self, name: KeyT, groupname: GroupT, consumername: ConsumerT
4220 ) -> ResponseT:
4221 """
4222 Remove a specific consumer from a consumer group.
4223 Returns the number of pending messages that the consumer had before it
4224 was deleted.
4225 name: name of the stream.
4226 groupname: name of the consumer group.
4227 consumername: name of consumer to delete
4229 For more information, see https://redis.io/commands/xgroup-delconsumer
4230 """
4231 return self.execute_command("XGROUP DELCONSUMER", name, groupname, consumername)
4233 def xgroup_destroy(self, name: KeyT, groupname: GroupT) -> ResponseT:
4234 """
4235 Destroy a consumer group.
4236 name: name of the stream.
4237 groupname: name of the consumer group.
4239 For more information, see https://redis.io/commands/xgroup-destroy
4240 """
4241 return self.execute_command("XGROUP DESTROY", name, groupname)
4243 def xgroup_createconsumer(
4244 self, name: KeyT, groupname: GroupT, consumername: ConsumerT
4245 ) -> ResponseT:
4246 """
4247 Consumers in a consumer group are auto-created every time a new
4248 consumer name is mentioned by some command.
4249 They can be explicitly created by using this command.
4250 name: name of the stream.
4251 groupname: name of the consumer group.
4252 consumername: name of consumer to create.
4254 See: https://redis.io/commands/xgroup-createconsumer
4255 """
4256 return self.execute_command(
4257 "XGROUP CREATECONSUMER", name, groupname, consumername
4258 )
4260 def xgroup_setid(
4261 self,
4262 name: KeyT,
4263 groupname: GroupT,
4264 id: StreamIdT,
4265 entries_read: Optional[int] = None,
4266 ) -> ResponseT:
4267 """
4268 Set the consumer group last delivered ID to something else.
4269 name: name of the stream.
4270 groupname: name of the consumer group.
4271 id: ID of the last item in the stream to consider already delivered.
4273 For more information, see https://redis.io/commands/xgroup-setid
4274 """
4275 pieces = [name, groupname, id]
4276 if entries_read is not None:
4277 pieces.extend(["ENTRIESREAD", entries_read])
4278 return self.execute_command("XGROUP SETID", *pieces)
4280 def xinfo_consumers(self, name: KeyT, groupname: GroupT) -> ResponseT:
4281 """
4282 Returns general information about the consumers in the group.
4283 name: name of the stream.
4284 groupname: name of the consumer group.
4286 For more information, see https://redis.io/commands/xinfo-consumers
4287 """
4288 return self.execute_command("XINFO CONSUMERS", name, groupname)
4290 def xinfo_groups(self, name: KeyT) -> ResponseT:
4291 """
4292 Returns general information about the consumer groups of the stream.
4293 name: name of the stream.
4295 For more information, see https://redis.io/commands/xinfo-groups
4296 """
4297 return self.execute_command("XINFO GROUPS", name)
4299 def xinfo_stream(self, name: KeyT, full: bool = False) -> ResponseT:
4300 """
4301 Returns general information about the stream.
4302 name: name of the stream.
4303 full: optional boolean, false by default. Return full summary
4305 For more information, see https://redis.io/commands/xinfo-stream
4306 """
4307 pieces = [name]
4308 options = {}
4309 if full:
4310 pieces.append(b"FULL")
4311 options = {"full": full}
4312 return self.execute_command("XINFO STREAM", *pieces, **options)
4314 def xlen(self, name: KeyT) -> ResponseT:
4315 """
4316 Returns the number of elements in a given stream.
4318 For more information, see https://redis.io/commands/xlen
4319 """
4320 return self.execute_command("XLEN", name, keys=[name])
4322 def xpending(self, name: KeyT, groupname: GroupT) -> ResponseT:
4323 """
4324 Returns information about pending messages of a group.
4325 name: name of the stream.
4326 groupname: name of the consumer group.
4328 For more information, see https://redis.io/commands/xpending
4329 """
4330 return self.execute_command("XPENDING", name, groupname, keys=[name])
4332 def xpending_range(
4333 self,
4334 name: KeyT,
4335 groupname: GroupT,
4336 min: StreamIdT,
4337 max: StreamIdT,
4338 count: int,
4339 consumername: Union[ConsumerT, None] = None,
4340 idle: Optional[int] = None,
4341 ) -> ResponseT:
4342 """
4343 Returns information about pending messages, in a range.
4345 name: name of the stream.
4346 groupname: name of the consumer group.
4347 idle: available from version 6.2. filter entries by their
4348 idle-time, given in milliseconds (optional).
4349 min: minimum stream ID.
4350 max: maximum stream ID.
4351 count: number of messages to return
4352 consumername: name of a consumer to filter by (optional).
4353 """
4354 if {min, max, count} == {None}:
4355 if idle is not None or consumername is not None:
4356 raise DataError(
4357 "if XPENDING is provided with idle time"
4358 " or consumername, it must be provided"
4359 " with min, max and count parameters"
4360 )
4361 return self.xpending(name, groupname)
4363 pieces = [name, groupname]
4364 if min is None or max is None or count is None:
4365 raise DataError(
4366 "XPENDING must be provided with min, max "
4367 "and count parameters, or none of them."
4368 )
4369 # idle
4370 try:
4371 if int(idle) < 0:
4372 raise DataError("XPENDING idle must be a integer >= 0")
4373 pieces.extend(["IDLE", idle])
4374 except TypeError:
4375 pass
4376 # count
4377 try:
4378 if int(count) < 0:
4379 raise DataError("XPENDING count must be a integer >= 0")
4380 pieces.extend([min, max, count])
4381 except TypeError:
4382 pass
4383 # consumername
4384 if consumername:
4385 pieces.append(consumername)
4387 return self.execute_command("XPENDING", *pieces, parse_detail=True)
4389 def xrange(
4390 self,
4391 name: KeyT,
4392 min: StreamIdT = "-",
4393 max: StreamIdT = "+",
4394 count: Optional[int] = None,
4395 ) -> ResponseT:
4396 """
4397 Read stream values within an interval.
4399 name: name of the stream.
4401 start: first stream ID. defaults to '-',
4402 meaning the earliest available.
4404 finish: last stream ID. defaults to '+',
4405 meaning the latest available.
4407 count: if set, only return this many items, beginning with the
4408 earliest available.
4410 For more information, see https://redis.io/commands/xrange
4411 """
4412 pieces = [min, max]
4413 if count is not None:
4414 if not isinstance(count, int) or count < 1:
4415 raise DataError("XRANGE count must be a positive integer")
4416 pieces.append(b"COUNT")
4417 pieces.append(str(count))
4419 return self.execute_command("XRANGE", name, *pieces, keys=[name])
4421 def xread(
4422 self,
4423 streams: Dict[KeyT, StreamIdT],
4424 count: Optional[int] = None,
4425 block: Optional[int] = None,
4426 ) -> ResponseT:
4427 """
4428 Block and monitor multiple streams for new data.
4430 streams: a dict of stream names to stream IDs, where
4431 IDs indicate the last ID already seen.
4433 count: if set, only return this many items, beginning with the
4434 earliest available.
4436 block: number of milliseconds to wait, if nothing already present.
4438 For more information, see https://redis.io/commands/xread
4439 """
4440 pieces = []
4441 if block is not None:
4442 if not isinstance(block, int) or block < 0:
4443 raise DataError("XREAD block must be a non-negative integer")
4444 pieces.append(b"BLOCK")
4445 pieces.append(str(block))
4446 if count is not None:
4447 if not isinstance(count, int) or count < 1:
4448 raise DataError("XREAD count must be a positive integer")
4449 pieces.append(b"COUNT")
4450 pieces.append(str(count))
4451 if not isinstance(streams, dict) or len(streams) == 0:
4452 raise DataError("XREAD streams must be a non empty dict")
4453 pieces.append(b"STREAMS")
4454 keys, values = zip(*streams.items())
4455 pieces.extend(keys)
4456 pieces.extend(values)
4457 response = self.execute_command("XREAD", *pieces, keys=keys)
4459 if inspect.iscoroutine(response):
4460 # Async client - wrap in coroutine that awaits and records
4461 async def _record_and_return():
4462 actual_response = await response
4464 await async_record_streaming_lag(response=actual_response)
4465 return actual_response
4467 return _record_and_return()
4468 else:
4469 # Sync client
4470 record_streaming_lag_from_response(response=response)
4471 return response
4473 def xreadgroup(
4474 self,
4475 groupname: str,
4476 consumername: str,
4477 streams: Dict[KeyT, StreamIdT],
4478 count: Optional[int] = None,
4479 block: Optional[int] = None,
4480 noack: bool = False,
4481 claim_min_idle_time: Optional[int] = None,
4482 ) -> ResponseT:
4483 """
4484 Read from a stream via a consumer group.
4486 groupname: name of the consumer group.
4488 consumername: name of the requesting consumer.
4490 streams: a dict of stream names to stream IDs, where
4491 IDs indicate the last ID already seen.
4493 count: if set, only return this many items, beginning with the
4494 earliest available.
4496 block: number of milliseconds to wait, if nothing already present.
4497 noack: do not add messages to the PEL
4499 claim_min_idle_time: accepts an integer type and represents a
4500 time interval in milliseconds
4502 For more information, see https://redis.io/commands/xreadgroup
4503 """
4504 options = {}
4505 pieces: list[EncodableT] = [b"GROUP", groupname, consumername]
4506 if count is not None:
4507 if not isinstance(count, int) or count < 1:
4508 raise DataError("XREADGROUP count must be a positive integer")
4509 pieces.append(b"COUNT")
4510 pieces.append(str(count))
4511 if block is not None:
4512 if not isinstance(block, int) or block < 0:
4513 raise DataError("XREADGROUP block must be a non-negative integer")
4514 pieces.append(b"BLOCK")
4515 pieces.append(str(block))
4516 if noack:
4517 pieces.append(b"NOACK")
4518 if claim_min_idle_time is not None:
4519 if not isinstance(claim_min_idle_time, int) or claim_min_idle_time < 0:
4520 raise DataError(
4521 "XREADGROUP claim_min_idle_time must be a non-negative integer"
4522 )
4523 pieces.append(b"CLAIM")
4524 pieces.append(claim_min_idle_time)
4525 options["claim_min_idle_time"] = claim_min_idle_time
4526 if not isinstance(streams, dict) or len(streams) == 0:
4527 raise DataError("XREADGROUP streams must be a non empty dict")
4528 pieces.append(b"STREAMS")
4529 pieces.extend(streams.keys())
4530 pieces.extend(streams.values())
4531 response = self.execute_command("XREADGROUP", *pieces, **options)
4533 if inspect.iscoroutine(response):
4534 # Async client - wrap in coroutine that awaits and records
4535 async def _record_and_return():
4536 actual_response = await response
4538 await async_record_streaming_lag(
4539 response=actual_response,
4540 consumer_group=groupname,
4541 )
4542 return actual_response
4544 return _record_and_return()
4545 else:
4546 # Sync client
4547 record_streaming_lag_from_response(
4548 response=response,
4549 consumer_group=groupname,
4550 )
4551 return response
4553 def xrevrange(
4554 self,
4555 name: KeyT,
4556 max: StreamIdT = "+",
4557 min: StreamIdT = "-",
4558 count: Optional[int] = None,
4559 ) -> ResponseT:
4560 """
4561 Read stream values within an interval, in reverse order.
4563 name: name of the stream
4565 start: first stream ID. defaults to '+',
4566 meaning the latest available.
4568 finish: last stream ID. defaults to '-',
4569 meaning the earliest available.
4571 count: if set, only return this many items, beginning with the
4572 latest available.
4574 For more information, see https://redis.io/commands/xrevrange
4575 """
4576 pieces: list[EncodableT] = [max, min]
4577 if count is not None:
4578 if not isinstance(count, int) or count < 1:
4579 raise DataError("XREVRANGE count must be a positive integer")
4580 pieces.append(b"COUNT")
4581 pieces.append(str(count))
4583 return self.execute_command("XREVRANGE", name, *pieces, keys=[name])
4585 def xtrim(
4586 self,
4587 name: KeyT,
4588 maxlen: Optional[int] = None,
4589 approximate: bool = True,
4590 minid: Union[StreamIdT, None] = None,
4591 limit: Optional[int] = None,
4592 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None,
4593 ) -> ResponseT:
4594 """
4595 Trims old messages from a stream.
4596 name: name of the stream.
4597 maxlen: truncate old stream messages beyond this size
4598 Can't be specified with minid.
4599 approximate: actual stream length may be slightly more than maxlen
4600 minid: the minimum id in the stream to query
4601 Can't be specified with maxlen.
4602 limit: specifies the maximum number of entries to retrieve
4603 ref_policy: optional reference policy for consumer groups:
4604 - KEEPREF (default): Trims entries but preserves references in consumer groups' PEL
4605 - DELREF: Trims entries and removes all references from consumer groups' PEL
4606 - ACKED: Only trims entries that were read and acknowledged by all consumer groups
4608 For more information, see https://redis.io/commands/xtrim
4609 """
4610 pieces: list[EncodableT] = []
4611 if maxlen is not None and minid is not None:
4612 raise DataError("Only one of ``maxlen`` or ``minid`` may be specified")
4614 if maxlen is None and minid is None:
4615 raise DataError("One of ``maxlen`` or ``minid`` must be specified")
4617 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
4618 raise DataError("XTRIM ref_policy must be one of: KEEPREF, DELREF, ACKED")
4620 if maxlen is not None:
4621 pieces.append(b"MAXLEN")
4622 if minid is not None:
4623 pieces.append(b"MINID")
4624 if approximate:
4625 pieces.append(b"~")
4626 if maxlen is not None:
4627 pieces.append(maxlen)
4628 if minid is not None:
4629 pieces.append(minid)
4630 if limit is not None:
4631 pieces.append(b"LIMIT")
4632 pieces.append(limit)
4633 if ref_policy is not None:
4634 pieces.append(ref_policy)
4636 return self.execute_command("XTRIM", name, *pieces)
4639AsyncStreamCommands = StreamCommands
4642class SortedSetCommands(CommandsProtocol):
4643 """
4644 Redis commands for Sorted Sets data type.
4645 see: https://redis.io/topics/data-types-intro#redis-sorted-sets
4646 """
4648 def zadd(
4649 self,
4650 name: KeyT,
4651 mapping: Mapping[AnyKeyT, EncodableT],
4652 nx: bool = False,
4653 xx: bool = False,
4654 ch: bool = False,
4655 incr: bool = False,
4656 gt: bool = False,
4657 lt: bool = False,
4658 ) -> ResponseT:
4659 """
4660 Set any number of element-name, score pairs to the key ``name``. Pairs
4661 are specified as a dict of element-names keys to score values.
4663 ``nx`` forces ZADD to only create new elements and not to update
4664 scores for elements that already exist.
4666 ``xx`` forces ZADD to only update scores of elements that already
4667 exist. New elements will not be added.
4669 ``ch`` modifies the return value to be the numbers of elements changed.
4670 Changed elements include new elements that were added and elements
4671 whose scores changed.
4673 ``incr`` modifies ZADD to behave like ZINCRBY. In this mode only a
4674 single element/score pair can be specified and the score is the amount
4675 the existing score will be incremented by. When using this mode the
4676 return value of ZADD will be the new score of the element.
4678 ``lt`` only updates existing elements if the new score is less than
4679 the current score. This flag doesn't prevent adding new elements.
4681 ``gt`` only updates existing elements if the new score is greater than
4682 the current score. This flag doesn't prevent adding new elements.
4684 The return value of ZADD varies based on the mode specified. With no
4685 options, ZADD returns the number of new elements added to the sorted
4686 set.
4688 ``nx``, ``lt``, and ``gt`` are mutually exclusive options.
4690 See: https://redis.io/commands/ZADD
4691 """
4692 if not mapping:
4693 raise DataError("ZADD requires at least one element/score pair")
4694 if nx and xx:
4695 raise DataError("ZADD allows either 'nx' or 'xx', not both")
4696 if gt and lt:
4697 raise DataError("ZADD allows either 'gt' or 'lt', not both")
4698 if incr and len(mapping) != 1:
4699 raise DataError(
4700 "ZADD option 'incr' only works when passing a single element/score pair"
4701 )
4702 if nx and (gt or lt):
4703 raise DataError("Only one of 'nx', 'lt', or 'gr' may be defined.")
4705 pieces: list[EncodableT] = []
4706 options = {}
4707 if nx:
4708 pieces.append(b"NX")
4709 if xx:
4710 pieces.append(b"XX")
4711 if ch:
4712 pieces.append(b"CH")
4713 if incr:
4714 pieces.append(b"INCR")
4715 options["as_score"] = True
4716 if gt:
4717 pieces.append(b"GT")
4718 if lt:
4719 pieces.append(b"LT")
4720 for pair in mapping.items():
4721 pieces.append(pair[1])
4722 pieces.append(pair[0])
4723 return self.execute_command("ZADD", name, *pieces, **options)
4725 def zcard(self, name: KeyT) -> ResponseT:
4726 """
4727 Return the number of elements in the sorted set ``name``
4729 For more information, see https://redis.io/commands/zcard
4730 """
4731 return self.execute_command("ZCARD", name, keys=[name])
4733 def zcount(self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT) -> ResponseT:
4734 """
4735 Returns the number of elements in the sorted set at key ``name`` with
4736 a score between ``min`` and ``max``.
4738 For more information, see https://redis.io/commands/zcount
4739 """
4740 return self.execute_command("ZCOUNT", name, min, max, keys=[name])
4742 def zdiff(self, keys: KeysT, withscores: bool = False) -> ResponseT:
4743 """
4744 Returns the difference between the first and all successive input
4745 sorted sets provided in ``keys``.
4747 For more information, see https://redis.io/commands/zdiff
4748 """
4749 pieces = [len(keys), *keys]
4750 if withscores:
4751 pieces.append("WITHSCORES")
4752 return self.execute_command("ZDIFF", *pieces, keys=keys)
4754 def zdiffstore(self, dest: KeyT, keys: KeysT) -> ResponseT:
4755 """
4756 Computes the difference between the first and all successive input
4757 sorted sets provided in ``keys`` and stores the result in ``dest``.
4759 For more information, see https://redis.io/commands/zdiffstore
4760 """
4761 pieces = [len(keys), *keys]
4762 return self.execute_command("ZDIFFSTORE", dest, *pieces)
4764 def zincrby(self, name: KeyT, amount: float, value: EncodableT) -> ResponseT:
4765 """
4766 Increment the score of ``value`` in sorted set ``name`` by ``amount``
4768 For more information, see https://redis.io/commands/zincrby
4769 """
4770 return self.execute_command("ZINCRBY", name, amount, value)
4772 def zinter(
4773 self, keys: KeysT, aggregate: Optional[str] = None, withscores: bool = False
4774 ) -> ResponseT:
4775 """
4776 Return the intersect of multiple sorted sets specified by ``keys``.
4777 With the ``aggregate`` option, it is possible to specify how the
4778 results of the union are aggregated. This option defaults to SUM,
4779 where the score of an element is summed across the inputs where it
4780 exists. When this option is set to either MIN or MAX, the resulting
4781 set will contain the minimum or maximum score of an element across
4782 the inputs where it exists.
4784 For more information, see https://redis.io/commands/zinter
4785 """
4786 return self._zaggregate("ZINTER", None, keys, aggregate, withscores=withscores)
4788 def zinterstore(
4789 self,
4790 dest: KeyT,
4791 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
4792 aggregate: Optional[str] = None,
4793 ) -> ResponseT:
4794 """
4795 Intersect multiple sorted sets specified by ``keys`` into a new
4796 sorted set, ``dest``. Scores in the destination will be aggregated
4797 based on the ``aggregate``. This option defaults to SUM, where the
4798 score of an element is summed across the inputs where it exists.
4799 When this option is set to either MIN or MAX, the resulting set will
4800 contain the minimum or maximum score of an element across the inputs
4801 where it exists.
4803 For more information, see https://redis.io/commands/zinterstore
4804 """
4805 return self._zaggregate("ZINTERSTORE", dest, keys, aggregate)
4807 def zintercard(
4808 self, numkeys: int, keys: List[str], limit: int = 0
4809 ) -> Union[Awaitable[int], int]:
4810 """
4811 Return the cardinality of the intersect of multiple sorted sets
4812 specified by ``keys``.
4813 When LIMIT provided (defaults to 0 and means unlimited), if the intersection
4814 cardinality reaches limit partway through the computation, the algorithm will
4815 exit and yield limit as the cardinality
4817 For more information, see https://redis.io/commands/zintercard
4818 """
4819 args = [numkeys, *keys, "LIMIT", limit]
4820 return self.execute_command("ZINTERCARD", *args, keys=keys)
4822 def zlexcount(self, name, min, max):
4823 """
4824 Return the number of items in the sorted set ``name`` between the
4825 lexicographical range ``min`` and ``max``.
4827 For more information, see https://redis.io/commands/zlexcount
4828 """
4829 return self.execute_command("ZLEXCOUNT", name, min, max, keys=[name])
4831 def zpopmax(self, name: KeyT, count: Optional[int] = None) -> ResponseT:
4832 """
4833 Remove and return up to ``count`` members with the highest scores
4834 from the sorted set ``name``.
4836 For more information, see https://redis.io/commands/zpopmax
4837 """
4838 args = (count is not None) and [count] or []
4839 options = {"withscores": True}
4840 return self.execute_command("ZPOPMAX", name, *args, **options)
4842 def zpopmin(self, name: KeyT, count: Optional[int] = None) -> ResponseT:
4843 """
4844 Remove and return up to ``count`` members with the lowest scores
4845 from the sorted set ``name``.
4847 For more information, see https://redis.io/commands/zpopmin
4848 """
4849 args = (count is not None) and [count] or []
4850 options = {"withscores": True}
4851 return self.execute_command("ZPOPMIN", name, *args, **options)
4853 def zrandmember(
4854 self, key: KeyT, count: Optional[int] = None, withscores: bool = False
4855 ) -> ResponseT:
4856 """
4857 Return a random element from the sorted set value stored at key.
4859 ``count`` if the argument is positive, return an array of distinct
4860 fields. If called with a negative count, the behavior changes and
4861 the command is allowed to return the same field multiple times.
4862 In this case, the number of returned fields is the absolute value
4863 of the specified count.
4865 ``withscores`` The optional WITHSCORES modifier changes the reply so it
4866 includes the respective scores of the randomly selected elements from
4867 the sorted set.
4869 For more information, see https://redis.io/commands/zrandmember
4870 """
4871 params = []
4872 if count is not None:
4873 params.append(count)
4874 if withscores:
4875 params.append("WITHSCORES")
4877 return self.execute_command("ZRANDMEMBER", key, *params)
4879 def bzpopmax(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT:
4880 """
4881 ZPOPMAX a value off of the first non-empty sorted set
4882 named in the ``keys`` list.
4884 If none of the sorted sets in ``keys`` has a value to ZPOPMAX,
4885 then block for ``timeout`` seconds, or until a member gets added
4886 to one of the sorted sets.
4888 If timeout is 0, then block indefinitely.
4890 For more information, see https://redis.io/commands/bzpopmax
4891 """
4892 if timeout is None:
4893 timeout = 0
4894 keys = list_or_args(keys, None)
4895 keys.append(timeout)
4896 return self.execute_command("BZPOPMAX", *keys)
4898 def bzpopmin(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT:
4899 """
4900 ZPOPMIN a value off of the first non-empty sorted set
4901 named in the ``keys`` list.
4903 If none of the sorted sets in ``keys`` has a value to ZPOPMIN,
4904 then block for ``timeout`` seconds, or until a member gets added
4905 to one of the sorted sets.
4907 If timeout is 0, then block indefinitely.
4909 For more information, see https://redis.io/commands/bzpopmin
4910 """
4911 if timeout is None:
4912 timeout = 0
4913 keys: list[EncodableT] = list_or_args(keys, None)
4914 keys.append(timeout)
4915 return self.execute_command("BZPOPMIN", *keys)
4917 def zmpop(
4918 self,
4919 num_keys: int,
4920 keys: List[str],
4921 min: Optional[bool] = False,
4922 max: Optional[bool] = False,
4923 count: Optional[int] = 1,
4924 ) -> Union[Awaitable[list], list]:
4925 """
4926 Pop ``count`` values (default 1) off of the first non-empty sorted set
4927 named in the ``keys`` list.
4928 For more information, see https://redis.io/commands/zmpop
4929 """
4930 args = [num_keys] + keys
4931 if (min and max) or (not min and not max):
4932 raise DataError
4933 elif min:
4934 args.append("MIN")
4935 else:
4936 args.append("MAX")
4937 if count != 1:
4938 args.extend(["COUNT", count])
4940 return self.execute_command("ZMPOP", *args)
4942 def bzmpop(
4943 self,
4944 timeout: float,
4945 numkeys: int,
4946 keys: List[str],
4947 min: Optional[bool] = False,
4948 max: Optional[bool] = False,
4949 count: Optional[int] = 1,
4950 ) -> Optional[list]:
4951 """
4952 Pop ``count`` values (default 1) off of the first non-empty sorted set
4953 named in the ``keys`` list.
4955 If none of the sorted sets in ``keys`` has a value to pop,
4956 then block for ``timeout`` seconds, or until a member gets added
4957 to one of the sorted sets.
4959 If timeout is 0, then block indefinitely.
4961 For more information, see https://redis.io/commands/bzmpop
4962 """
4963 args = [timeout, numkeys, *keys]
4964 if (min and max) or (not min and not max):
4965 raise DataError("Either min or max, but not both must be set")
4966 elif min:
4967 args.append("MIN")
4968 else:
4969 args.append("MAX")
4970 args.extend(["COUNT", count])
4972 return self.execute_command("BZMPOP", *args)
4974 def _zrange(
4975 self,
4976 command,
4977 dest: Union[KeyT, None],
4978 name: KeyT,
4979 start: EncodableT,
4980 end: EncodableT,
4981 desc: bool = False,
4982 byscore: bool = False,
4983 bylex: bool = False,
4984 withscores: bool = False,
4985 score_cast_func: Union[type, Callable, None] = float,
4986 offset: Optional[int] = None,
4987 num: Optional[int] = None,
4988 ) -> ResponseT:
4989 if byscore and bylex:
4990 raise DataError("``byscore`` and ``bylex`` can not be specified together.")
4991 if (offset is not None and num is None) or (num is not None and offset is None):
4992 raise DataError("``offset`` and ``num`` must both be specified.")
4993 if bylex and withscores:
4994 raise DataError(
4995 "``withscores`` not supported in combination with ``bylex``."
4996 )
4997 pieces = [command]
4998 if dest:
4999 pieces.append(dest)
5000 pieces.extend([name, start, end])
5001 if byscore:
5002 pieces.append("BYSCORE")
5003 if bylex:
5004 pieces.append("BYLEX")
5005 if desc:
5006 pieces.append("REV")
5007 if offset is not None and num is not None:
5008 pieces.extend(["LIMIT", offset, num])
5009 if withscores:
5010 pieces.append("WITHSCORES")
5011 options = {"withscores": withscores, "score_cast_func": score_cast_func}
5012 options["keys"] = [name]
5013 return self.execute_command(*pieces, **options)
5015 def zrange(
5016 self,
5017 name: KeyT,
5018 start: EncodableT,
5019 end: EncodableT,
5020 desc: bool = False,
5021 withscores: bool = False,
5022 score_cast_func: Union[type, Callable] = float,
5023 byscore: bool = False,
5024 bylex: bool = False,
5025 offset: Optional[int] = None,
5026 num: Optional[int] = None,
5027 ) -> ResponseT:
5028 """
5029 Return a range of values from sorted set ``name`` between
5030 ``start`` and ``end`` sorted in ascending order.
5032 ``start`` and ``end`` can be negative, indicating the end of the range.
5034 ``desc`` a boolean indicating whether to sort the results in reversed
5035 order.
5037 ``withscores`` indicates to return the scores along with the values.
5038 The return type is a list of (value, score) pairs.
5040 ``score_cast_func`` a callable used to cast the score return value.
5042 ``byscore`` when set to True, returns the range of elements from the
5043 sorted set having scores equal or between ``start`` and ``end``.
5045 ``bylex`` when set to True, returns the range of elements from the
5046 sorted set between the ``start`` and ``end`` lexicographical closed
5047 range intervals.
5048 Valid ``start`` and ``end`` must start with ( or [, in order to specify
5049 whether the range interval is exclusive or inclusive, respectively.
5051 ``offset`` and ``num`` are specified, then return a slice of the range.
5052 Can't be provided when using ``bylex``.
5054 For more information, see https://redis.io/commands/zrange
5055 """
5056 # Need to support ``desc`` also when using old redis version
5057 # because it was supported in 3.5.3 (of redis-py)
5058 if not byscore and not bylex and (offset is None and num is None) and desc:
5059 return self.zrevrange(name, start, end, withscores, score_cast_func)
5061 return self._zrange(
5062 "ZRANGE",
5063 None,
5064 name,
5065 start,
5066 end,
5067 desc,
5068 byscore,
5069 bylex,
5070 withscores,
5071 score_cast_func,
5072 offset,
5073 num,
5074 )
5076 def zrevrange(
5077 self,
5078 name: KeyT,
5079 start: int,
5080 end: int,
5081 withscores: bool = False,
5082 score_cast_func: Union[type, Callable] = float,
5083 ) -> ResponseT:
5084 """
5085 Return a range of values from sorted set ``name`` between
5086 ``start`` and ``end`` sorted in descending order.
5088 ``start`` and ``end`` can be negative, indicating the end of the range.
5090 ``withscores`` indicates to return the scores along with the values
5091 The return type is a list of (value, score) pairs
5093 ``score_cast_func`` a callable used to cast the score return value
5095 For more information, see https://redis.io/commands/zrevrange
5096 """
5097 pieces = ["ZREVRANGE", name, start, end]
5098 if withscores:
5099 pieces.append(b"WITHSCORES")
5100 options = {"withscores": withscores, "score_cast_func": score_cast_func}
5101 options["keys"] = name
5102 return self.execute_command(*pieces, **options)
5104 def zrangestore(
5105 self,
5106 dest: KeyT,
5107 name: KeyT,
5108 start: EncodableT,
5109 end: EncodableT,
5110 byscore: bool = False,
5111 bylex: bool = False,
5112 desc: bool = False,
5113 offset: Optional[int] = None,
5114 num: Optional[int] = None,
5115 ) -> ResponseT:
5116 """
5117 Stores in ``dest`` the result of a range of values from sorted set
5118 ``name`` between ``start`` and ``end`` sorted in ascending order.
5120 ``start`` and ``end`` can be negative, indicating the end of the range.
5122 ``byscore`` when set to True, returns the range of elements from the
5123 sorted set having scores equal or between ``start`` and ``end``.
5125 ``bylex`` when set to True, returns the range of elements from the
5126 sorted set between the ``start`` and ``end`` lexicographical closed
5127 range intervals.
5128 Valid ``start`` and ``end`` must start with ( or [, in order to specify
5129 whether the range interval is exclusive or inclusive, respectively.
5131 ``desc`` a boolean indicating whether to sort the results in reversed
5132 order.
5134 ``offset`` and ``num`` are specified, then return a slice of the range.
5135 Can't be provided when using ``bylex``.
5137 For more information, see https://redis.io/commands/zrangestore
5138 """
5139 return self._zrange(
5140 "ZRANGESTORE",
5141 dest,
5142 name,
5143 start,
5144 end,
5145 desc,
5146 byscore,
5147 bylex,
5148 False,
5149 None,
5150 offset,
5151 num,
5152 )
5154 def zrangebylex(
5155 self,
5156 name: KeyT,
5157 min: EncodableT,
5158 max: EncodableT,
5159 start: Optional[int] = None,
5160 num: Optional[int] = None,
5161 ) -> ResponseT:
5162 """
5163 Return the lexicographical range of values from sorted set ``name``
5164 between ``min`` and ``max``.
5166 If ``start`` and ``num`` are specified, then return a slice of the
5167 range.
5169 For more information, see https://redis.io/commands/zrangebylex
5170 """
5171 if (start is not None and num is None) or (num is not None and start is None):
5172 raise DataError("``start`` and ``num`` must both be specified")
5173 pieces = ["ZRANGEBYLEX", name, min, max]
5174 if start is not None and num is not None:
5175 pieces.extend([b"LIMIT", start, num])
5176 return self.execute_command(*pieces, keys=[name])
5178 def zrevrangebylex(
5179 self,
5180 name: KeyT,
5181 max: EncodableT,
5182 min: EncodableT,
5183 start: Optional[int] = None,
5184 num: Optional[int] = None,
5185 ) -> ResponseT:
5186 """
5187 Return the reversed lexicographical range of values from sorted set
5188 ``name`` between ``max`` and ``min``.
5190 If ``start`` and ``num`` are specified, then return a slice of the
5191 range.
5193 For more information, see https://redis.io/commands/zrevrangebylex
5194 """
5195 if (start is not None and num is None) or (num is not None and start is None):
5196 raise DataError("``start`` and ``num`` must both be specified")
5197 pieces = ["ZREVRANGEBYLEX", name, max, min]
5198 if start is not None and num is not None:
5199 pieces.extend(["LIMIT", start, num])
5200 return self.execute_command(*pieces, keys=[name])
5202 def zrangebyscore(
5203 self,
5204 name: KeyT,
5205 min: ZScoreBoundT,
5206 max: ZScoreBoundT,
5207 start: Optional[int] = None,
5208 num: Optional[int] = None,
5209 withscores: bool = False,
5210 score_cast_func: Union[type, Callable] = float,
5211 ) -> ResponseT:
5212 """
5213 Return a range of values from the sorted set ``name`` with scores
5214 between ``min`` and ``max``.
5216 If ``start`` and ``num`` are specified, then return a slice
5217 of the range.
5219 ``withscores`` indicates to return the scores along with the values.
5220 The return type is a list of (value, score) pairs
5222 `score_cast_func`` a callable used to cast the score return value
5224 For more information, see https://redis.io/commands/zrangebyscore
5225 """
5226 if (start is not None and num is None) or (num is not None and start is None):
5227 raise DataError("``start`` and ``num`` must both be specified")
5228 pieces = ["ZRANGEBYSCORE", name, min, max]
5229 if start is not None and num is not None:
5230 pieces.extend(["LIMIT", start, num])
5231 if withscores:
5232 pieces.append("WITHSCORES")
5233 options = {"withscores": withscores, "score_cast_func": score_cast_func}
5234 options["keys"] = [name]
5235 return self.execute_command(*pieces, **options)
5237 def zrevrangebyscore(
5238 self,
5239 name: KeyT,
5240 max: ZScoreBoundT,
5241 min: ZScoreBoundT,
5242 start: Optional[int] = None,
5243 num: Optional[int] = None,
5244 withscores: bool = False,
5245 score_cast_func: Union[type, Callable] = float,
5246 ):
5247 """
5248 Return a range of values from the sorted set ``name`` with scores
5249 between ``min`` and ``max`` in descending order.
5251 If ``start`` and ``num`` are specified, then return a slice
5252 of the range.
5254 ``withscores`` indicates to return the scores along with the values.
5255 The return type is a list of (value, score) pairs
5257 ``score_cast_func`` a callable used to cast the score return value
5259 For more information, see https://redis.io/commands/zrevrangebyscore
5260 """
5261 if (start is not None and num is None) or (num is not None and start is None):
5262 raise DataError("``start`` and ``num`` must both be specified")
5263 pieces = ["ZREVRANGEBYSCORE", name, max, min]
5264 if start is not None and num is not None:
5265 pieces.extend(["LIMIT", start, num])
5266 if withscores:
5267 pieces.append("WITHSCORES")
5268 options = {"withscores": withscores, "score_cast_func": score_cast_func}
5269 options["keys"] = [name]
5270 return self.execute_command(*pieces, **options)
5272 def zrank(
5273 self,
5274 name: KeyT,
5275 value: EncodableT,
5276 withscore: bool = False,
5277 score_cast_func: Union[type, Callable] = float,
5278 ) -> ResponseT:
5279 """
5280 Returns a 0-based value indicating the rank of ``value`` in sorted set
5281 ``name``.
5282 The optional WITHSCORE argument supplements the command's
5283 reply with the score of the element returned.
5285 ``score_cast_func`` a callable used to cast the score return value
5287 For more information, see https://redis.io/commands/zrank
5288 """
5289 pieces = ["ZRANK", name, value]
5290 if withscore:
5291 pieces.append("WITHSCORE")
5293 options = {"withscore": withscore, "score_cast_func": score_cast_func}
5295 return self.execute_command(*pieces, **options)
5297 def zrem(self, name: KeyT, *values: FieldT) -> ResponseT:
5298 """
5299 Remove member ``values`` from sorted set ``name``
5301 For more information, see https://redis.io/commands/zrem
5302 """
5303 return self.execute_command("ZREM", name, *values)
5305 def zremrangebylex(self, name: KeyT, min: EncodableT, max: EncodableT) -> ResponseT:
5306 """
5307 Remove all elements in the sorted set ``name`` between the
5308 lexicographical range specified by ``min`` and ``max``.
5310 Returns the number of elements removed.
5312 For more information, see https://redis.io/commands/zremrangebylex
5313 """
5314 return self.execute_command("ZREMRANGEBYLEX", name, min, max)
5316 def zremrangebyrank(self, name: KeyT, min: int, max: int) -> ResponseT:
5317 """
5318 Remove all elements in the sorted set ``name`` with ranks between
5319 ``min`` and ``max``. Values are 0-based, ordered from smallest score
5320 to largest. Values can be negative indicating the highest scores.
5321 Returns the number of elements removed
5323 For more information, see https://redis.io/commands/zremrangebyrank
5324 """
5325 return self.execute_command("ZREMRANGEBYRANK", name, min, max)
5327 def zremrangebyscore(
5328 self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT
5329 ) -> ResponseT:
5330 """
5331 Remove all elements in the sorted set ``name`` with scores
5332 between ``min`` and ``max``. Returns the number of elements removed.
5334 For more information, see https://redis.io/commands/zremrangebyscore
5335 """
5336 return self.execute_command("ZREMRANGEBYSCORE", name, min, max)
5338 def zrevrank(
5339 self,
5340 name: KeyT,
5341 value: EncodableT,
5342 withscore: bool = False,
5343 score_cast_func: Union[type, Callable] = float,
5344 ) -> ResponseT:
5345 """
5346 Returns a 0-based value indicating the descending rank of
5347 ``value`` in sorted set ``name``.
5348 The optional ``withscore`` argument supplements the command's
5349 reply with the score of the element returned.
5351 ``score_cast_func`` a callable used to cast the score return value
5353 For more information, see https://redis.io/commands/zrevrank
5354 """
5355 pieces = ["ZREVRANK", name, value]
5356 if withscore:
5357 pieces.append("WITHSCORE")
5359 options = {"withscore": withscore, "score_cast_func": score_cast_func}
5361 return self.execute_command(*pieces, **options)
5363 def zscore(self, name: KeyT, value: EncodableT) -> ResponseT:
5364 """
5365 Return the score of element ``value`` in sorted set ``name``
5367 For more information, see https://redis.io/commands/zscore
5368 """
5369 return self.execute_command("ZSCORE", name, value, keys=[name])
5371 def zunion(
5372 self,
5373 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
5374 aggregate: Optional[str] = None,
5375 withscores: bool = False,
5376 score_cast_func: Union[type, Callable] = float,
5377 ) -> ResponseT:
5378 """
5379 Return the union of multiple sorted sets specified by ``keys``.
5380 ``keys`` can be provided as dictionary of keys and their weights.
5381 Scores will be aggregated based on the ``aggregate``, or SUM if
5382 none is provided.
5384 ``score_cast_func`` a callable used to cast the score return value
5386 For more information, see https://redis.io/commands/zunion
5387 """
5388 return self._zaggregate(
5389 "ZUNION",
5390 None,
5391 keys,
5392 aggregate,
5393 withscores=withscores,
5394 score_cast_func=score_cast_func,
5395 )
5397 def zunionstore(
5398 self,
5399 dest: KeyT,
5400 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
5401 aggregate: Optional[str] = None,
5402 ) -> ResponseT:
5403 """
5404 Union multiple sorted sets specified by ``keys`` into
5405 a new sorted set, ``dest``. Scores in the destination will be
5406 aggregated based on the ``aggregate``, or SUM if none is provided.
5408 For more information, see https://redis.io/commands/zunionstore
5409 """
5410 return self._zaggregate("ZUNIONSTORE", dest, keys, aggregate)
5412 def zmscore(self, key: KeyT, members: List[str]) -> ResponseT:
5413 """
5414 Returns the scores associated with the specified members
5415 in the sorted set stored at key.
5416 ``members`` should be a list of the member name.
5417 Return type is a list of score.
5418 If the member does not exist, a None will be returned
5419 in corresponding position.
5421 For more information, see https://redis.io/commands/zmscore
5422 """
5423 if not members:
5424 raise DataError("ZMSCORE members must be a non-empty list")
5425 pieces = [key] + members
5426 return self.execute_command("ZMSCORE", *pieces, keys=[key])
5428 def _zaggregate(
5429 self,
5430 command: str,
5431 dest: Union[KeyT, None],
5432 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
5433 aggregate: Optional[str] = None,
5434 **options,
5435 ) -> ResponseT:
5436 pieces: list[EncodableT] = [command]
5437 if dest is not None:
5438 pieces.append(dest)
5439 pieces.append(len(keys))
5440 if isinstance(keys, dict):
5441 keys, weights = keys.keys(), keys.values()
5442 else:
5443 weights = None
5444 pieces.extend(keys)
5445 if weights:
5446 pieces.append(b"WEIGHTS")
5447 pieces.extend(weights)
5448 if aggregate:
5449 if aggregate.upper() in ["SUM", "MIN", "MAX"]:
5450 pieces.append(b"AGGREGATE")
5451 pieces.append(aggregate)
5452 else:
5453 raise DataError("aggregate can be sum, min or max.")
5454 if options.get("withscores", False):
5455 pieces.append(b"WITHSCORES")
5456 options["keys"] = keys
5457 return self.execute_command(*pieces, **options)
5460AsyncSortedSetCommands = SortedSetCommands
5463class HyperlogCommands(CommandsProtocol):
5464 """
5465 Redis commands of HyperLogLogs data type.
5466 see: https://redis.io/topics/data-types-intro#hyperloglogs
5467 """
5469 def pfadd(self, name: KeyT, *values: FieldT) -> ResponseT:
5470 """
5471 Adds the specified elements to the specified HyperLogLog.
5473 For more information, see https://redis.io/commands/pfadd
5474 """
5475 return self.execute_command("PFADD", name, *values)
5477 def pfcount(self, *sources: KeyT) -> ResponseT:
5478 """
5479 Return the approximated cardinality of
5480 the set observed by the HyperLogLog at key(s).
5482 For more information, see https://redis.io/commands/pfcount
5483 """
5484 return self.execute_command("PFCOUNT", *sources)
5486 def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT:
5487 """
5488 Merge N different HyperLogLogs into a single one.
5490 For more information, see https://redis.io/commands/pfmerge
5491 """
5492 return self.execute_command("PFMERGE", dest, *sources)
5495AsyncHyperlogCommands = HyperlogCommands
5498class HashDataPersistOptions(Enum):
5499 # set the value for each provided key to each
5500 # provided value only if all do not already exist.
5501 FNX = "FNX"
5503 # set the value for each provided key to each
5504 # provided value only if all already exist.
5505 FXX = "FXX"
5508class HashCommands(CommandsProtocol):
5509 """
5510 Redis commands for Hash data type.
5511 see: https://redis.io/topics/data-types-intro#redis-hashes
5512 """
5514 def hdel(self, name: str, *keys: str) -> Union[Awaitable[int], int]:
5515 """
5516 Delete ``keys`` from hash ``name``
5518 For more information, see https://redis.io/commands/hdel
5519 """
5520 return self.execute_command("HDEL", name, *keys)
5522 def hexists(self, name: str, key: str) -> Union[Awaitable[bool], bool]:
5523 """
5524 Returns a boolean indicating if ``key`` exists within hash ``name``
5526 For more information, see https://redis.io/commands/hexists
5527 """
5528 return self.execute_command("HEXISTS", name, key, keys=[name])
5530 def hget(
5531 self, name: str, key: str
5532 ) -> Union[Awaitable[Optional[str]], Optional[str]]:
5533 """
5534 Return the value of ``key`` within the hash ``name``
5536 For more information, see https://redis.io/commands/hget
5537 """
5538 return self.execute_command("HGET", name, key, keys=[name])
5540 def hgetall(self, name: str) -> Union[Awaitable[dict], dict]:
5541 """
5542 Return a Python dict of the hash's name/value pairs
5544 For more information, see https://redis.io/commands/hgetall
5545 """
5546 return self.execute_command("HGETALL", name, keys=[name])
5548 def hgetdel(
5549 self, name: str, *keys: str
5550 ) -> Union[
5551 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]]
5552 ]:
5553 """
5554 Return the value of ``key`` within the hash ``name`` and
5555 delete the field in the hash.
5556 This command is similar to HGET, except for the fact that it also deletes
5557 the key on success from the hash with the provided ```name```.
5559 Available since Redis 8.0
5560 For more information, see https://redis.io/commands/hgetdel
5561 """
5562 if len(keys) == 0:
5563 raise DataError("'hgetdel' should have at least one key provided")
5565 return self.execute_command("HGETDEL", name, "FIELDS", len(keys), *keys)
5567 def hgetex(
5568 self,
5569 name: KeyT,
5570 *keys: str,
5571 ex: Optional[ExpiryT] = None,
5572 px: Optional[ExpiryT] = None,
5573 exat: Optional[AbsExpiryT] = None,
5574 pxat: Optional[AbsExpiryT] = None,
5575 persist: bool = False,
5576 ) -> Union[
5577 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]]
5578 ]:
5579 """
5580 Return the values of ``key`` and ``keys`` within the hash ``name``
5581 and optionally set their expiration.
5583 ``ex`` sets an expire flag on ``kyes`` for ``ex`` seconds.
5585 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds.
5587 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds,
5588 specified in unix time.
5590 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds,
5591 specified in unix time.
5593 ``persist`` remove the time to live associated with the ``keys``.
5595 Available since Redis 8.0
5596 For more information, see https://redis.io/commands/hgetex
5597 """
5598 if not keys:
5599 raise DataError("'hgetex' should have at least one key provided")
5601 if not at_most_one_value_set((ex, px, exat, pxat, persist)):
5602 raise DataError(
5603 "``ex``, ``px``, ``exat``, ``pxat``, "
5604 "and ``persist`` are mutually exclusive."
5605 )
5607 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat)
5609 if persist:
5610 exp_options.append("PERSIST")
5612 return self.execute_command(
5613 "HGETEX",
5614 name,
5615 *exp_options,
5616 "FIELDS",
5617 len(keys),
5618 *keys,
5619 )
5621 def hincrby(
5622 self, name: str, key: str, amount: int = 1
5623 ) -> Union[Awaitable[int], int]:
5624 """
5625 Increment the value of ``key`` in hash ``name`` by ``amount``
5627 For more information, see https://redis.io/commands/hincrby
5628 """
5629 return self.execute_command("HINCRBY", name, key, amount)
5631 def hincrbyfloat(
5632 self, name: str, key: str, amount: float = 1.0
5633 ) -> Union[Awaitable[float], float]:
5634 """
5635 Increment the value of ``key`` in hash ``name`` by floating ``amount``
5637 For more information, see https://redis.io/commands/hincrbyfloat
5638 """
5639 return self.execute_command("HINCRBYFLOAT", name, key, amount)
5641 def hkeys(self, name: str) -> Union[Awaitable[List], List]:
5642 """
5643 Return the list of keys within hash ``name``
5645 For more information, see https://redis.io/commands/hkeys
5646 """
5647 return self.execute_command("HKEYS", name, keys=[name])
5649 def hlen(self, name: str) -> Union[Awaitable[int], int]:
5650 """
5651 Return the number of elements in hash ``name``
5653 For more information, see https://redis.io/commands/hlen
5654 """
5655 return self.execute_command("HLEN", name, keys=[name])
5657 def hset(
5658 self,
5659 name: str,
5660 key: Optional[str] = None,
5661 value: Optional[str] = None,
5662 mapping: Optional[dict] = None,
5663 items: Optional[list] = None,
5664 ) -> Union[Awaitable[int], int]:
5665 """
5666 Set ``key`` to ``value`` within hash ``name``,
5667 ``mapping`` accepts a dict of key/value pairs that will be
5668 added to hash ``name``.
5669 ``items`` accepts a list of key/value pairs that will be
5670 added to hash ``name``.
5671 Returns the number of fields that were added.
5673 For more information, see https://redis.io/commands/hset
5674 """
5676 if key is None and not mapping and not items:
5677 raise DataError("'hset' with no key value pairs")
5679 pieces = []
5680 if items:
5681 pieces.extend(items)
5682 if key is not None:
5683 pieces.extend((key, value))
5684 if mapping:
5685 for pair in mapping.items():
5686 pieces.extend(pair)
5688 return self.execute_command("HSET", name, *pieces)
5690 def hsetex(
5691 self,
5692 name: str,
5693 key: Optional[str] = None,
5694 value: Optional[str] = None,
5695 mapping: Optional[dict] = None,
5696 items: Optional[list] = None,
5697 ex: Optional[ExpiryT] = None,
5698 px: Optional[ExpiryT] = None,
5699 exat: Optional[AbsExpiryT] = None,
5700 pxat: Optional[AbsExpiryT] = None,
5701 data_persist_option: Optional[HashDataPersistOptions] = None,
5702 keepttl: bool = False,
5703 ) -> Union[Awaitable[int], int]:
5704 """
5705 Set ``key`` to ``value`` within hash ``name``
5707 ``mapping`` accepts a dict of key/value pairs that will be
5708 added to hash ``name``.
5710 ``items`` accepts a list of key/value pairs that will be
5711 added to hash ``name``.
5713 ``ex`` sets an expire flag on ``keys`` for ``ex`` seconds.
5715 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds.
5717 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds,
5718 specified in unix time.
5720 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds,
5721 specified in unix time.
5723 ``data_persist_option`` can be set to ``FNX`` or ``FXX`` to control the
5724 behavior of the command.
5725 ``FNX`` will set the value for each provided key to each
5726 provided value only if all do not already exist.
5727 ``FXX`` will set the value for each provided key to each
5728 provided value only if all already exist.
5730 ``keepttl`` if True, retain the time to live associated with the keys.
5732 Returns the number of fields that were added.
5734 Available since Redis 8.0
5735 For more information, see https://redis.io/commands/hsetex
5736 """
5737 if key is None and not mapping and not items:
5738 raise DataError("'hsetex' with no key value pairs")
5740 if items and len(items) % 2 != 0:
5741 raise DataError(
5742 "'hsetex' with odd number of items. "
5743 "'items' must contain a list of key/value pairs."
5744 )
5746 if not at_most_one_value_set((ex, px, exat, pxat, keepttl)):
5747 raise DataError(
5748 "``ex``, ``px``, ``exat``, ``pxat``, "
5749 "and ``keepttl`` are mutually exclusive."
5750 )
5752 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat)
5753 if data_persist_option:
5754 exp_options.append(data_persist_option.value)
5756 if keepttl:
5757 exp_options.append("KEEPTTL")
5759 pieces = []
5760 if items:
5761 pieces.extend(items)
5762 if key is not None:
5763 pieces.extend((key, value))
5764 if mapping:
5765 for pair in mapping.items():
5766 pieces.extend(pair)
5768 return self.execute_command(
5769 "HSETEX", name, *exp_options, "FIELDS", int(len(pieces) / 2), *pieces
5770 )
5772 def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool]:
5773 """
5774 Set ``key`` to ``value`` within hash ``name`` if ``key`` does not
5775 exist. Returns 1 if HSETNX created a field, otherwise 0.
5777 For more information, see https://redis.io/commands/hsetnx
5778 """
5779 return self.execute_command("HSETNX", name, key, value)
5781 @deprecated_function(
5782 version="4.0.0",
5783 reason="Use 'hset' instead.",
5784 name="hmset",
5785 )
5786 def hmset(self, name: str, mapping: dict) -> Union[Awaitable[str], str]:
5787 """
5788 Set key to value within hash ``name`` for each corresponding
5789 key and value from the ``mapping`` dict.
5791 For more information, see https://redis.io/commands/hmset
5792 """
5793 if not mapping:
5794 raise DataError("'hmset' with 'mapping' of length 0")
5795 items = []
5796 for pair in mapping.items():
5797 items.extend(pair)
5798 return self.execute_command("HMSET", name, *items)
5800 def hmget(self, name: str, keys: List, *args: List) -> Union[Awaitable[List], List]:
5801 """
5802 Returns a list of values ordered identically to ``keys``
5804 For more information, see https://redis.io/commands/hmget
5805 """
5806 args = list_or_args(keys, args)
5807 return self.execute_command("HMGET", name, *args, keys=[name])
5809 def hvals(self, name: str) -> Union[Awaitable[List], List]:
5810 """
5811 Return the list of values within hash ``name``
5813 For more information, see https://redis.io/commands/hvals
5814 """
5815 return self.execute_command("HVALS", name, keys=[name])
5817 def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]:
5818 """
5819 Return the number of bytes stored in the value of ``key``
5820 within hash ``name``
5822 For more information, see https://redis.io/commands/hstrlen
5823 """
5824 return self.execute_command("HSTRLEN", name, key, keys=[name])
5826 def hexpire(
5827 self,
5828 name: KeyT,
5829 seconds: ExpiryT,
5830 *fields: str,
5831 nx: bool = False,
5832 xx: bool = False,
5833 gt: bool = False,
5834 lt: bool = False,
5835 ) -> ResponseT:
5836 """
5837 Sets or updates the expiration time for fields within a hash key, using relative
5838 time in seconds.
5840 If a field already has an expiration time, the behavior of the update can be
5841 controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5843 The return value provides detailed information about the outcome for each field.
5845 For more information, see https://redis.io/commands/hexpire
5847 Args:
5848 name: The name of the hash key.
5849 seconds: Expiration time in seconds, relative. Can be an integer, or a
5850 Python `timedelta` object.
5851 fields: List of fields within the hash to apply the expiration time to.
5852 nx: Set expiry only when the field has no expiry.
5853 xx: Set expiry only when the field has an existing expiry.
5854 gt: Set expiry only when the new expiry is greater than the current one.
5855 lt: Set expiry only when the new expiry is less than the current one.
5857 Returns:
5858 Returns a list which contains for each field in the request:
5859 - `-2` if the field does not exist, or if the key does not exist.
5860 - `0` if the specified NX | XX | GT | LT condition was not met.
5861 - `1` if the expiration time was set or updated.
5862 - `2` if the field was deleted because the specified expiration time is
5863 in the past.
5864 """
5865 conditions = [nx, xx, gt, lt]
5866 if sum(conditions) > 1:
5867 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5869 if isinstance(seconds, datetime.timedelta):
5870 seconds = int(seconds.total_seconds())
5872 options = []
5873 if nx:
5874 options.append("NX")
5875 if xx:
5876 options.append("XX")
5877 if gt:
5878 options.append("GT")
5879 if lt:
5880 options.append("LT")
5882 return self.execute_command(
5883 "HEXPIRE", name, seconds, *options, "FIELDS", len(fields), *fields
5884 )
5886 def hpexpire(
5887 self,
5888 name: KeyT,
5889 milliseconds: ExpiryT,
5890 *fields: str,
5891 nx: bool = False,
5892 xx: bool = False,
5893 gt: bool = False,
5894 lt: bool = False,
5895 ) -> ResponseT:
5896 """
5897 Sets or updates the expiration time for fields within a hash key, using relative
5898 time in milliseconds.
5900 If a field already has an expiration time, the behavior of the update can be
5901 controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5903 The return value provides detailed information about the outcome for each field.
5905 For more information, see https://redis.io/commands/hpexpire
5907 Args:
5908 name: The name of the hash key.
5909 milliseconds: Expiration time in milliseconds, relative. Can be an integer,
5910 or a Python `timedelta` object.
5911 fields: List of fields within the hash to apply the expiration time to.
5912 nx: Set expiry only when the field has no expiry.
5913 xx: Set expiry only when the field has an existing expiry.
5914 gt: Set expiry only when the new expiry is greater than the current one.
5915 lt: Set expiry only when the new expiry is less than the current one.
5917 Returns:
5918 Returns a list which contains for each field in the request:
5919 - `-2` if the field does not exist, or if the key does not exist.
5920 - `0` if the specified NX | XX | GT | LT condition was not met.
5921 - `1` if the expiration time was set or updated.
5922 - `2` if the field was deleted because the specified expiration time is
5923 in the past.
5924 """
5925 conditions = [nx, xx, gt, lt]
5926 if sum(conditions) > 1:
5927 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5929 if isinstance(milliseconds, datetime.timedelta):
5930 milliseconds = int(milliseconds.total_seconds() * 1000)
5932 options = []
5933 if nx:
5934 options.append("NX")
5935 if xx:
5936 options.append("XX")
5937 if gt:
5938 options.append("GT")
5939 if lt:
5940 options.append("LT")
5942 return self.execute_command(
5943 "HPEXPIRE", name, milliseconds, *options, "FIELDS", len(fields), *fields
5944 )
5946 def hexpireat(
5947 self,
5948 name: KeyT,
5949 unix_time_seconds: AbsExpiryT,
5950 *fields: str,
5951 nx: bool = False,
5952 xx: bool = False,
5953 gt: bool = False,
5954 lt: bool = False,
5955 ) -> ResponseT:
5956 """
5957 Sets or updates the expiration time for fields within a hash key, using an
5958 absolute Unix timestamp in seconds.
5960 If a field already has an expiration time, the behavior of the update can be
5961 controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5963 The return value provides detailed information about the outcome for each field.
5965 For more information, see https://redis.io/commands/hexpireat
5967 Args:
5968 name: The name of the hash key.
5969 unix_time_seconds: Expiration time as Unix timestamp in seconds. Can be an
5970 integer or a Python `datetime` object.
5971 fields: List of fields within the hash to apply the expiration time to.
5972 nx: Set expiry only when the field has no expiry.
5973 xx: Set expiry only when the field has an existing expiration time.
5974 gt: Set expiry only when the new expiry is greater than the current one.
5975 lt: Set expiry only when the new expiry is less than the current one.
5977 Returns:
5978 Returns a list which contains for each field in the request:
5979 - `-2` if the field does not exist, or if the key does not exist.
5980 - `0` if the specified NX | XX | GT | LT condition was not met.
5981 - `1` if the expiration time was set or updated.
5982 - `2` if the field was deleted because the specified expiration time is
5983 in the past.
5984 """
5985 conditions = [nx, xx, gt, lt]
5986 if sum(conditions) > 1:
5987 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5989 if isinstance(unix_time_seconds, datetime.datetime):
5990 unix_time_seconds = int(unix_time_seconds.timestamp())
5992 options = []
5993 if nx:
5994 options.append("NX")
5995 if xx:
5996 options.append("XX")
5997 if gt:
5998 options.append("GT")
5999 if lt:
6000 options.append("LT")
6002 return self.execute_command(
6003 "HEXPIREAT",
6004 name,
6005 unix_time_seconds,
6006 *options,
6007 "FIELDS",
6008 len(fields),
6009 *fields,
6010 )
6012 def hpexpireat(
6013 self,
6014 name: KeyT,
6015 unix_time_milliseconds: AbsExpiryT,
6016 *fields: str,
6017 nx: bool = False,
6018 xx: bool = False,
6019 gt: bool = False,
6020 lt: bool = False,
6021 ) -> ResponseT:
6022 """
6023 Sets or updates the expiration time for fields within a hash key, using an
6024 absolute Unix timestamp in milliseconds.
6026 If a field already has an expiration time, the behavior of the update can be
6027 controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
6029 The return value provides detailed information about the outcome for each field.
6031 For more information, see https://redis.io/commands/hpexpireat
6033 Args:
6034 name: The name of the hash key.
6035 unix_time_milliseconds: Expiration time as Unix timestamp in milliseconds.
6036 Can be an integer or a Python `datetime` object.
6037 fields: List of fields within the hash to apply the expiry.
6038 nx: Set expiry only when the field has no expiry.
6039 xx: Set expiry only when the field has an existing expiry.
6040 gt: Set expiry only when the new expiry is greater than the current one.
6041 lt: Set expiry only when the new expiry is less than the current one.
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 - `0` if the specified NX | XX | GT | LT condition was not met.
6047 - `1` if the expiration time was set or updated.
6048 - `2` if the field was deleted because the specified expiration time is
6049 in the past.
6050 """
6051 conditions = [nx, xx, gt, lt]
6052 if sum(conditions) > 1:
6053 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
6055 if isinstance(unix_time_milliseconds, datetime.datetime):
6056 unix_time_milliseconds = int(unix_time_milliseconds.timestamp() * 1000)
6058 options = []
6059 if nx:
6060 options.append("NX")
6061 if xx:
6062 options.append("XX")
6063 if gt:
6064 options.append("GT")
6065 if lt:
6066 options.append("LT")
6068 return self.execute_command(
6069 "HPEXPIREAT",
6070 name,
6071 unix_time_milliseconds,
6072 *options,
6073 "FIELDS",
6074 len(fields),
6075 *fields,
6076 )
6078 def hpersist(self, name: KeyT, *fields: str) -> ResponseT:
6079 """
6080 Removes the expiration time for each specified field in a hash.
6082 For more information, see https://redis.io/commands/hpersist
6084 Args:
6085 name: The name of the hash key.
6086 fields: A list of fields within the hash from which to remove the
6087 expiration time.
6089 Returns:
6090 Returns a list which contains for each field in the request:
6091 - `-2` if the field does not exist, or if the key does not exist.
6092 - `-1` if the field exists but has no associated expiration time.
6093 - `1` if the expiration time was successfully removed from the field.
6094 """
6095 return self.execute_command("HPERSIST", name, "FIELDS", len(fields), *fields)
6097 def hexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
6098 """
6099 Returns the expiration times of hash fields as Unix timestamps in seconds.
6101 For more information, see https://redis.io/commands/hexpiretime
6103 Args:
6104 key: The hash key.
6105 fields: A list of fields within the hash for which to get the expiration
6106 time.
6108 Returns:
6109 Returns a list which contains for each field in the request:
6110 - `-2` if the field does not exist, or if the key does not exist.
6111 - `-1` if the field exists but has no associated expire time.
6112 - A positive integer representing the expiration Unix timestamp in
6113 seconds, if the field has an associated expiration time.
6114 """
6115 return self.execute_command(
6116 "HEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key]
6117 )
6119 def hpexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
6120 """
6121 Returns the expiration times of hash fields as Unix timestamps in milliseconds.
6123 For more information, see https://redis.io/commands/hpexpiretime
6125 Args:
6126 key: The hash key.
6127 fields: A list of fields within the hash for which to get the expiration
6128 time.
6130 Returns:
6131 Returns a list which contains for each field in the request:
6132 - `-2` if the field does not exist, or if the key does not exist.
6133 - `-1` if the field exists but has no associated expire time.
6134 - A positive integer representing the expiration Unix timestamp in
6135 milliseconds, if the field has an associated expiration time.
6136 """
6137 return self.execute_command(
6138 "HPEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key]
6139 )
6141 def httl(self, key: KeyT, *fields: str) -> ResponseT:
6142 """
6143 Returns the TTL (Time To Live) in seconds for each specified field within a hash
6144 key.
6146 For more information, see https://redis.io/commands/httl
6148 Args:
6149 key: The hash key.
6150 fields: A list of fields within the hash for which to get the TTL.
6152 Returns:
6153 Returns a list which contains for each field in the request:
6154 - `-2` if the field does not exist, or if the key does not exist.
6155 - `-1` if the field exists but has no associated expire time.
6156 - A positive integer representing the TTL in seconds if the field has
6157 an associated expiration time.
6158 """
6159 return self.execute_command(
6160 "HTTL", key, "FIELDS", len(fields), *fields, keys=[key]
6161 )
6163 def hpttl(self, key: KeyT, *fields: str) -> ResponseT:
6164 """
6165 Returns the TTL (Time To Live) in milliseconds for each specified field within a
6166 hash key.
6168 For more information, see https://redis.io/commands/hpttl
6170 Args:
6171 key: The hash key.
6172 fields: A list of fields within the hash for which to get the TTL.
6174 Returns:
6175 Returns a list which contains for each field in the request:
6176 - `-2` if the field does not exist, or if the key does not exist.
6177 - `-1` if the field exists but has no associated expire time.
6178 - A positive integer representing the TTL in milliseconds if the field
6179 has an associated expiration time.
6180 """
6181 return self.execute_command(
6182 "HPTTL", key, "FIELDS", len(fields), *fields, keys=[key]
6183 )
6186AsyncHashCommands = HashCommands
6189class Script:
6190 """
6191 An executable Lua script object returned by ``register_script``
6192 """
6194 def __init__(self, registered_client: "redis.client.Redis", script: ScriptTextT):
6195 self.registered_client = registered_client
6196 self.script = script
6197 # Precalculate and store the SHA1 hex digest of the script.
6199 if isinstance(script, str):
6200 # We need the encoding from the client in order to generate an
6201 # accurate byte representation of the script
6202 encoder = self.get_encoder()
6203 script = encoder.encode(script)
6204 self.sha = hashlib.sha1(script).hexdigest()
6206 def __call__(
6207 self,
6208 keys: Union[Sequence[KeyT], None] = None,
6209 args: Union[Iterable[EncodableT], None] = None,
6210 client: Union["redis.client.Redis", None] = None,
6211 ):
6212 """Execute the script, passing any required ``args``"""
6213 keys = keys or []
6214 args = args or []
6215 if client is None:
6216 client = self.registered_client
6217 args = tuple(keys) + tuple(args)
6218 # make sure the Redis server knows about the script
6219 from redis.client import Pipeline
6221 if isinstance(client, Pipeline):
6222 # Make sure the pipeline can register the script before executing.
6223 client.scripts.add(self)
6224 try:
6225 return client.evalsha(self.sha, len(keys), *args)
6226 except NoScriptError:
6227 # Maybe the client is pointed to a different server than the client
6228 # that created this instance?
6229 # Overwrite the sha just in case there was a discrepancy.
6230 self.sha = client.script_load(self.script)
6231 return client.evalsha(self.sha, len(keys), *args)
6233 def get_encoder(self):
6234 """Get the encoder to encode string scripts into bytes."""
6235 try:
6236 return self.registered_client.get_encoder()
6237 except AttributeError:
6238 # DEPRECATED
6239 # In version <=4.1.2, this was the code we used to get the encoder.
6240 # However, after 4.1.2 we added support for scripting in clustered
6241 # redis. ClusteredRedis doesn't have a `.connection_pool` attribute
6242 # so we changed the Script class to use
6243 # `self.registered_client.get_encoder` (see above).
6244 # However, that is technically a breaking change, as consumers who
6245 # use Scripts directly might inject a `registered_client` that
6246 # doesn't have a `.get_encoder` field. This try/except prevents us
6247 # from breaking backward-compatibility. Ideally, it would be
6248 # removed in the next major release.
6249 return self.registered_client.connection_pool.get_encoder()
6252class AsyncScript:
6253 """
6254 An executable Lua script object returned by ``register_script``
6255 """
6257 def __init__(
6258 self,
6259 registered_client: "redis.asyncio.client.Redis",
6260 script: ScriptTextT,
6261 ):
6262 self.registered_client = registered_client
6263 self.script = script
6264 # Precalculate and store the SHA1 hex digest of the script.
6266 if isinstance(script, str):
6267 # We need the encoding from the client in order to generate an
6268 # accurate byte representation of the script
6269 try:
6270 encoder = registered_client.connection_pool.get_encoder()
6271 except AttributeError:
6272 # Cluster
6273 encoder = registered_client.get_encoder()
6274 script = encoder.encode(script)
6275 self.sha = hashlib.sha1(script).hexdigest()
6277 async def __call__(
6278 self,
6279 keys: Union[Sequence[KeyT], None] = None,
6280 args: Union[Iterable[EncodableT], None] = None,
6281 client: Union["redis.asyncio.client.Redis", None] = None,
6282 ):
6283 """Execute the script, passing any required ``args``"""
6284 keys = keys or []
6285 args = args or []
6286 if client is None:
6287 client = self.registered_client
6288 args = tuple(keys) + tuple(args)
6289 # make sure the Redis server knows about the script
6290 from redis.asyncio.client import Pipeline
6292 if isinstance(client, Pipeline):
6293 # Make sure the pipeline can register the script before executing.
6294 client.scripts.add(self)
6295 try:
6296 return await client.evalsha(self.sha, len(keys), *args)
6297 except NoScriptError:
6298 # Maybe the client is pointed to a different server than the client
6299 # that created this instance?
6300 # Overwrite the sha just in case there was a discrepancy.
6301 self.sha = await client.script_load(self.script)
6302 return await client.evalsha(self.sha, len(keys), *args)
6305class PubSubCommands(CommandsProtocol):
6306 """
6307 Redis PubSub commands.
6308 see https://redis.io/topics/pubsub
6309 """
6311 def publish(self, channel: ChannelT, message: EncodableT, **kwargs) -> ResponseT:
6312 """
6313 Publish ``message`` on ``channel``.
6314 Returns the number of subscribers the message was delivered to.
6316 For more information, see https://redis.io/commands/publish
6317 """
6318 response = self.execute_command("PUBLISH", channel, message, **kwargs)
6319 record_pubsub_message(
6320 direction=PubSubDirection.PUBLISH,
6321 channel=str_if_bytes(channel),
6322 )
6323 return response
6325 def spublish(self, shard_channel: ChannelT, message: EncodableT) -> ResponseT:
6326 """
6327 Posts a message to the given shard channel.
6328 Returns the number of clients that received the message
6330 For more information, see https://redis.io/commands/spublish
6331 """
6332 response = self.execute_command("SPUBLISH", shard_channel, message)
6333 record_pubsub_message(
6334 direction=PubSubDirection.PUBLISH,
6335 channel=str_if_bytes(shard_channel),
6336 sharded=True,
6337 )
6338 return response
6340 def pubsub_channels(self, pattern: PatternT = "*", **kwargs) -> ResponseT:
6341 """
6342 Return a list of channels that have at least one subscriber
6344 For more information, see https://redis.io/commands/pubsub-channels
6345 """
6346 return self.execute_command("PUBSUB CHANNELS", pattern, **kwargs)
6348 def pubsub_shardchannels(self, pattern: PatternT = "*", **kwargs) -> ResponseT:
6349 """
6350 Return a list of shard_channels that have at least one subscriber
6352 For more information, see https://redis.io/commands/pubsub-shardchannels
6353 """
6354 return self.execute_command("PUBSUB SHARDCHANNELS", pattern, **kwargs)
6356 def pubsub_numpat(self, **kwargs) -> ResponseT:
6357 """
6358 Returns the number of subscriptions to patterns
6360 For more information, see https://redis.io/commands/pubsub-numpat
6361 """
6362 return self.execute_command("PUBSUB NUMPAT", **kwargs)
6364 def pubsub_numsub(self, *args: ChannelT, **kwargs) -> ResponseT:
6365 """
6366 Return a list of (channel, number of subscribers) tuples
6367 for each channel given in ``*args``
6369 For more information, see https://redis.io/commands/pubsub-numsub
6370 """
6371 return self.execute_command("PUBSUB NUMSUB", *args, **kwargs)
6373 def pubsub_shardnumsub(self, *args: ChannelT, **kwargs) -> ResponseT:
6374 """
6375 Return a list of (shard_channel, number of subscribers) tuples
6376 for each channel given in ``*args``
6378 For more information, see https://redis.io/commands/pubsub-shardnumsub
6379 """
6380 return self.execute_command("PUBSUB SHARDNUMSUB", *args, **kwargs)
6383AsyncPubSubCommands = PubSubCommands
6386class ScriptCommands(CommandsProtocol):
6387 """
6388 Redis Lua script commands. see:
6389 https://redis.io/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/
6390 """
6392 def _eval(
6393 self,
6394 command: str,
6395 script: str,
6396 numkeys: int,
6397 *keys_and_args: Union[KeyT, EncodableT],
6398 ) -> Union[Awaitable[str], str]:
6399 return self.execute_command(command, script, numkeys, *keys_and_args)
6401 def eval(
6402 self, script: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT]
6403 ) -> Union[Awaitable[str], str]:
6404 """
6405 Execute the Lua ``script``, specifying the ``numkeys`` the script
6406 will touch and the key names and argument values in ``keys_and_args``.
6407 Returns the result of the script.
6409 In practice, use the object returned by ``register_script``. This
6410 function exists purely for Redis API completion.
6412 For more information, see https://redis.io/commands/eval
6413 """
6414 return self._eval("EVAL", script, numkeys, *keys_and_args)
6416 def eval_ro(
6417 self, script: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT]
6418 ) -> Union[Awaitable[str], str]:
6419 """
6420 The read-only variant of the EVAL command
6422 Execute the read-only Lua ``script`` specifying the ``numkeys`` the script
6423 will touch and the key names and argument values in ``keys_and_args``.
6424 Returns the result of the script.
6426 For more information, see https://redis.io/commands/eval_ro
6427 """
6428 return self._eval("EVAL_RO", script, numkeys, *keys_and_args)
6430 def _evalsha(
6431 self,
6432 command: str,
6433 sha: str,
6434 numkeys: int,
6435 *keys_and_args: Union[KeyT, EncodableT],
6436 ) -> Union[Awaitable[str], str]:
6437 return self.execute_command(command, sha, numkeys, *keys_and_args)
6439 def evalsha(
6440 self, sha: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT]
6441 ) -> Union[Awaitable[str], str]:
6442 """
6443 Use the ``sha`` to execute a Lua script already registered via EVAL
6444 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the
6445 key names and argument values in ``keys_and_args``. Returns the result
6446 of the script.
6448 In practice, use the object returned by ``register_script``. This
6449 function exists purely for Redis API completion.
6451 For more information, see https://redis.io/commands/evalsha
6452 """
6453 return self._evalsha("EVALSHA", sha, numkeys, *keys_and_args)
6455 def evalsha_ro(
6456 self, sha: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT]
6457 ) -> Union[Awaitable[str], str]:
6458 """
6459 The read-only variant of the EVALSHA command
6461 Use the ``sha`` to execute a read-only Lua script already registered via EVAL
6462 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the
6463 key names and argument values in ``keys_and_args``. Returns the result
6464 of the script.
6466 For more information, see https://redis.io/commands/evalsha_ro
6467 """
6468 return self._evalsha("EVALSHA_RO", sha, numkeys, *keys_and_args)
6470 def script_exists(self, *args: str) -> ResponseT:
6471 """
6472 Check if a script exists in the script cache by specifying the SHAs of
6473 each script as ``args``. Returns a list of boolean values indicating if
6474 if each already script exists in the cache_data.
6476 For more information, see https://redis.io/commands/script-exists
6477 """
6478 return self.execute_command("SCRIPT EXISTS", *args)
6480 def script_debug(self, *args) -> None:
6481 raise NotImplementedError(
6482 "SCRIPT DEBUG is intentionally not implemented in the client."
6483 )
6485 def script_flush(
6486 self, sync_type: Union[Literal["SYNC"], Literal["ASYNC"]] = None
6487 ) -> ResponseT:
6488 """Flush all scripts from the script cache_data.
6490 ``sync_type`` is by default SYNC (synchronous) but it can also be
6491 ASYNC.
6493 For more information, see https://redis.io/commands/script-flush
6494 """
6496 # Redis pre 6 had no sync_type.
6497 if sync_type not in ["SYNC", "ASYNC", None]:
6498 raise DataError(
6499 "SCRIPT FLUSH defaults to SYNC in redis > 6.2, or "
6500 "accepts SYNC/ASYNC. For older versions, "
6501 "of redis leave as None."
6502 )
6503 if sync_type is None:
6504 pieces = []
6505 else:
6506 pieces = [sync_type]
6507 return self.execute_command("SCRIPT FLUSH", *pieces)
6509 def script_kill(self) -> ResponseT:
6510 """
6511 Kill the currently executing Lua script
6513 For more information, see https://redis.io/commands/script-kill
6514 """
6515 return self.execute_command("SCRIPT KILL")
6517 def script_load(self, script: ScriptTextT) -> ResponseT:
6518 """
6519 Load a Lua ``script`` into the script cache_data. Returns the SHA.
6521 For more information, see https://redis.io/commands/script-load
6522 """
6523 return self.execute_command("SCRIPT LOAD", script)
6525 def register_script(self: "redis.client.Redis", script: ScriptTextT) -> Script:
6526 """
6527 Register a Lua ``script`` specifying the ``keys`` it will touch.
6528 Returns a Script object that is callable and hides the complexity of
6529 deal with scripts, keys, and shas. This is the preferred way to work
6530 with Lua scripts.
6531 """
6532 return Script(self, script)
6535class AsyncScriptCommands(ScriptCommands):
6536 async def script_debug(self, *args) -> None:
6537 return super().script_debug()
6539 def register_script(
6540 self: "redis.asyncio.client.Redis",
6541 script: ScriptTextT,
6542 ) -> AsyncScript:
6543 """
6544 Register a Lua ``script`` specifying the ``keys`` it will touch.
6545 Returns a Script object that is callable and hides the complexity of
6546 deal with scripts, keys, and shas. This is the preferred way to work
6547 with Lua scripts.
6548 """
6549 return AsyncScript(self, script)
6552class GeoCommands(CommandsProtocol):
6553 """
6554 Redis Geospatial commands.
6555 see: https://redis.com/redis-best-practices/indexing-patterns/geospatial/
6556 """
6558 def geoadd(
6559 self,
6560 name: KeyT,
6561 values: Sequence[EncodableT],
6562 nx: bool = False,
6563 xx: bool = False,
6564 ch: bool = False,
6565 ) -> ResponseT:
6566 """
6567 Add the specified geospatial items to the specified key identified
6568 by the ``name`` argument. The Geospatial items are given as ordered
6569 members of the ``values`` argument, each item or place is formed by
6570 the triad longitude, latitude and name.
6572 Note: You can use ZREM to remove elements.
6574 ``nx`` forces ZADD to only create new elements and not to update
6575 scores for elements that already exist.
6577 ``xx`` forces ZADD to only update scores of elements that already
6578 exist. New elements will not be added.
6580 ``ch`` modifies the return value to be the numbers of elements changed.
6581 Changed elements include new elements that were added and elements
6582 whose scores changed.
6584 For more information, see https://redis.io/commands/geoadd
6585 """
6586 if nx and xx:
6587 raise DataError("GEOADD allows either 'nx' or 'xx', not both")
6588 if len(values) % 3 != 0:
6589 raise DataError("GEOADD requires places with lon, lat and name values")
6590 pieces = [name]
6591 if nx:
6592 pieces.append("NX")
6593 if xx:
6594 pieces.append("XX")
6595 if ch:
6596 pieces.append("CH")
6597 pieces.extend(values)
6598 return self.execute_command("GEOADD", *pieces)
6600 def geodist(
6601 self, name: KeyT, place1: FieldT, place2: FieldT, unit: Optional[str] = None
6602 ) -> ResponseT:
6603 """
6604 Return the distance between ``place1`` and ``place2`` members of the
6605 ``name`` key.
6606 The units must be one of the following : m, km mi, ft. By default
6607 meters are used.
6609 For more information, see https://redis.io/commands/geodist
6610 """
6611 pieces: list[EncodableT] = [name, place1, place2]
6612 if unit and unit not in ("m", "km", "mi", "ft"):
6613 raise DataError("GEODIST invalid unit")
6614 elif unit:
6615 pieces.append(unit)
6616 return self.execute_command("GEODIST", *pieces, keys=[name])
6618 def geohash(self, name: KeyT, *values: FieldT) -> ResponseT:
6619 """
6620 Return the geo hash string for each item of ``values`` members of
6621 the specified key identified by the ``name`` argument.
6623 For more information, see https://redis.io/commands/geohash
6624 """
6625 return self.execute_command("GEOHASH", name, *values, keys=[name])
6627 def geopos(self, name: KeyT, *values: FieldT) -> ResponseT:
6628 """
6629 Return the positions of each item of ``values`` as members of
6630 the specified key identified by the ``name`` argument. Each position
6631 is represented by the pairs lon and lat.
6633 For more information, see https://redis.io/commands/geopos
6634 """
6635 return self.execute_command("GEOPOS", name, *values, keys=[name])
6637 def georadius(
6638 self,
6639 name: KeyT,
6640 longitude: float,
6641 latitude: float,
6642 radius: float,
6643 unit: Optional[str] = None,
6644 withdist: bool = False,
6645 withcoord: bool = False,
6646 withhash: bool = False,
6647 count: Optional[int] = None,
6648 sort: Optional[str] = None,
6649 store: Optional[KeyT] = None,
6650 store_dist: Optional[KeyT] = None,
6651 any: bool = False,
6652 ) -> ResponseT:
6653 """
6654 Return the members of the specified key identified by the
6655 ``name`` argument which are within the borders of the area specified
6656 with the ``latitude`` and ``longitude`` location and the maximum
6657 distance from the center specified by the ``radius`` value.
6659 The units must be one of the following : m, km mi, ft. By default
6661 ``withdist`` indicates to return the distances of each place.
6663 ``withcoord`` indicates to return the latitude and longitude of
6664 each place.
6666 ``withhash`` indicates to return the geohash string of each place.
6668 ``count`` indicates to return the number of elements up to N.
6670 ``sort`` indicates to return the places in a sorted way, ASC for
6671 nearest to fairest and DESC for fairest to nearest.
6673 ``store`` indicates to save the places names in a sorted set named
6674 with a specific key, each element of the destination sorted set is
6675 populated with the score got from the original geo sorted set.
6677 ``store_dist`` indicates to save the places names in a sorted set
6678 named with a specific key, instead of ``store`` the sorted set
6679 destination score is set with the distance.
6681 For more information, see https://redis.io/commands/georadius
6682 """
6683 return self._georadiusgeneric(
6684 "GEORADIUS",
6685 name,
6686 longitude,
6687 latitude,
6688 radius,
6689 unit=unit,
6690 withdist=withdist,
6691 withcoord=withcoord,
6692 withhash=withhash,
6693 count=count,
6694 sort=sort,
6695 store=store,
6696 store_dist=store_dist,
6697 any=any,
6698 )
6700 def georadiusbymember(
6701 self,
6702 name: KeyT,
6703 member: FieldT,
6704 radius: float,
6705 unit: Optional[str] = None,
6706 withdist: bool = False,
6707 withcoord: bool = False,
6708 withhash: bool = False,
6709 count: Optional[int] = None,
6710 sort: Optional[str] = None,
6711 store: Union[KeyT, None] = None,
6712 store_dist: Union[KeyT, None] = None,
6713 any: bool = False,
6714 ) -> ResponseT:
6715 """
6716 This command is exactly like ``georadius`` with the sole difference
6717 that instead of taking, as the center of the area to query, a longitude
6718 and latitude value, it takes the name of a member already existing
6719 inside the geospatial index represented by the sorted set.
6721 For more information, see https://redis.io/commands/georadiusbymember
6722 """
6723 return self._georadiusgeneric(
6724 "GEORADIUSBYMEMBER",
6725 name,
6726 member,
6727 radius,
6728 unit=unit,
6729 withdist=withdist,
6730 withcoord=withcoord,
6731 withhash=withhash,
6732 count=count,
6733 sort=sort,
6734 store=store,
6735 store_dist=store_dist,
6736 any=any,
6737 )
6739 def _georadiusgeneric(
6740 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None]
6741 ) -> ResponseT:
6742 pieces = list(args)
6743 if kwargs["unit"] and kwargs["unit"] not in ("m", "km", "mi", "ft"):
6744 raise DataError("GEORADIUS invalid unit")
6745 elif kwargs["unit"]:
6746 pieces.append(kwargs["unit"])
6747 else:
6748 pieces.append("m")
6750 if kwargs["any"] and kwargs["count"] is None:
6751 raise DataError("``any`` can't be provided without ``count``")
6753 for arg_name, byte_repr in (
6754 ("withdist", "WITHDIST"),
6755 ("withcoord", "WITHCOORD"),
6756 ("withhash", "WITHHASH"),
6757 ):
6758 if kwargs[arg_name]:
6759 pieces.append(byte_repr)
6761 if kwargs["count"] is not None:
6762 pieces.extend(["COUNT", kwargs["count"]])
6763 if kwargs["any"]:
6764 pieces.append("ANY")
6766 if kwargs["sort"]:
6767 if kwargs["sort"] == "ASC":
6768 pieces.append("ASC")
6769 elif kwargs["sort"] == "DESC":
6770 pieces.append("DESC")
6771 else:
6772 raise DataError("GEORADIUS invalid sort")
6774 if kwargs["store"] and kwargs["store_dist"]:
6775 raise DataError("GEORADIUS store and store_dist cant be set together")
6777 if kwargs["store"]:
6778 pieces.extend([b"STORE", kwargs["store"]])
6780 if kwargs["store_dist"]:
6781 pieces.extend([b"STOREDIST", kwargs["store_dist"]])
6783 return self.execute_command(command, *pieces, **kwargs)
6785 def geosearch(
6786 self,
6787 name: KeyT,
6788 member: Union[FieldT, None] = None,
6789 longitude: Union[float, None] = None,
6790 latitude: Union[float, None] = None,
6791 unit: str = "m",
6792 radius: Union[float, None] = None,
6793 width: Union[float, None] = None,
6794 height: Union[float, None] = None,
6795 sort: Optional[str] = None,
6796 count: Optional[int] = None,
6797 any: bool = False,
6798 withcoord: bool = False,
6799 withdist: bool = False,
6800 withhash: bool = False,
6801 ) -> ResponseT:
6802 """
6803 Return the members of specified key identified by the
6804 ``name`` argument, which are within the borders of the
6805 area specified by a given shape. This command extends the
6806 GEORADIUS command, so in addition to searching within circular
6807 areas, it supports searching within rectangular areas.
6809 This command should be used in place of the deprecated
6810 GEORADIUS and GEORADIUSBYMEMBER commands.
6812 ``member`` Use the position of the given existing
6813 member in the sorted set. Can't be given with ``longitude``
6814 and ``latitude``.
6816 ``longitude`` and ``latitude`` Use the position given by
6817 this coordinates. Can't be given with ``member``
6818 ``radius`` Similar to GEORADIUS, search inside circular
6819 area according the given radius. Can't be given with
6820 ``height`` and ``width``.
6821 ``height`` and ``width`` Search inside an axis-aligned
6822 rectangle, determined by the given height and width.
6823 Can't be given with ``radius``
6825 ``unit`` must be one of the following : m, km, mi, ft.
6826 `m` for meters (the default value), `km` for kilometers,
6827 `mi` for miles and `ft` for feet.
6829 ``sort`` indicates to return the places in a sorted way,
6830 ASC for nearest to furthest and DESC for furthest to nearest.
6832 ``count`` limit the results to the first count matching items.
6834 ``any`` is set to True, the command will return as soon as
6835 enough matches are found. Can't be provided without ``count``
6837 ``withdist`` indicates to return the distances of each place.
6838 ``withcoord`` indicates to return the latitude and longitude of
6839 each place.
6841 ``withhash`` indicates to return the geohash string of each place.
6843 For more information, see https://redis.io/commands/geosearch
6844 """
6846 return self._geosearchgeneric(
6847 "GEOSEARCH",
6848 name,
6849 member=member,
6850 longitude=longitude,
6851 latitude=latitude,
6852 unit=unit,
6853 radius=radius,
6854 width=width,
6855 height=height,
6856 sort=sort,
6857 count=count,
6858 any=any,
6859 withcoord=withcoord,
6860 withdist=withdist,
6861 withhash=withhash,
6862 store=None,
6863 store_dist=None,
6864 )
6866 def geosearchstore(
6867 self,
6868 dest: KeyT,
6869 name: KeyT,
6870 member: Optional[FieldT] = None,
6871 longitude: Optional[float] = None,
6872 latitude: Optional[float] = None,
6873 unit: str = "m",
6874 radius: Optional[float] = None,
6875 width: Optional[float] = None,
6876 height: Optional[float] = None,
6877 sort: Optional[str] = None,
6878 count: Optional[int] = None,
6879 any: bool = False,
6880 storedist: bool = False,
6881 ) -> ResponseT:
6882 """
6883 This command is like GEOSEARCH, but stores the result in
6884 ``dest``. By default, it stores the results in the destination
6885 sorted set with their geospatial information.
6886 if ``store_dist`` set to True, the command will stores the
6887 items in a sorted set populated with their distance from the
6888 center of the circle or box, as a floating-point number.
6890 For more information, see https://redis.io/commands/geosearchstore
6891 """
6892 return self._geosearchgeneric(
6893 "GEOSEARCHSTORE",
6894 dest,
6895 name,
6896 member=member,
6897 longitude=longitude,
6898 latitude=latitude,
6899 unit=unit,
6900 radius=radius,
6901 width=width,
6902 height=height,
6903 sort=sort,
6904 count=count,
6905 any=any,
6906 withcoord=None,
6907 withdist=None,
6908 withhash=None,
6909 store=None,
6910 store_dist=storedist,
6911 )
6913 def _geosearchgeneric(
6914 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None]
6915 ) -> ResponseT:
6916 pieces = list(args)
6918 # FROMMEMBER or FROMLONLAT
6919 if kwargs["member"] is None:
6920 if kwargs["longitude"] is None or kwargs["latitude"] is None:
6921 raise DataError("GEOSEARCH must have member or longitude and latitude")
6922 if kwargs["member"]:
6923 if kwargs["longitude"] or kwargs["latitude"]:
6924 raise DataError(
6925 "GEOSEARCH member and longitude or latitude cant be set together"
6926 )
6927 pieces.extend([b"FROMMEMBER", kwargs["member"]])
6928 if kwargs["longitude"] is not None and kwargs["latitude"] is not None:
6929 pieces.extend([b"FROMLONLAT", kwargs["longitude"], kwargs["latitude"]])
6931 # BYRADIUS or BYBOX
6932 if kwargs["radius"] is None:
6933 if kwargs["width"] is None or kwargs["height"] is None:
6934 raise DataError("GEOSEARCH must have radius or width and height")
6935 if kwargs["unit"] is None:
6936 raise DataError("GEOSEARCH must have unit")
6937 if kwargs["unit"].lower() not in ("m", "km", "mi", "ft"):
6938 raise DataError("GEOSEARCH invalid unit")
6939 if kwargs["radius"]:
6940 if kwargs["width"] or kwargs["height"]:
6941 raise DataError(
6942 "GEOSEARCH radius and width or height cant be set together"
6943 )
6944 pieces.extend([b"BYRADIUS", kwargs["radius"], kwargs["unit"]])
6945 if kwargs["width"] and kwargs["height"]:
6946 pieces.extend([b"BYBOX", kwargs["width"], kwargs["height"], kwargs["unit"]])
6948 # sort
6949 if kwargs["sort"]:
6950 if kwargs["sort"].upper() == "ASC":
6951 pieces.append(b"ASC")
6952 elif kwargs["sort"].upper() == "DESC":
6953 pieces.append(b"DESC")
6954 else:
6955 raise DataError("GEOSEARCH invalid sort")
6957 # count any
6958 if kwargs["count"]:
6959 pieces.extend([b"COUNT", kwargs["count"]])
6960 if kwargs["any"]:
6961 pieces.append(b"ANY")
6962 elif kwargs["any"]:
6963 raise DataError("GEOSEARCH ``any`` can't be provided without count")
6965 # other properties
6966 for arg_name, byte_repr in (
6967 ("withdist", b"WITHDIST"),
6968 ("withcoord", b"WITHCOORD"),
6969 ("withhash", b"WITHHASH"),
6970 ("store_dist", b"STOREDIST"),
6971 ):
6972 if kwargs[arg_name]:
6973 pieces.append(byte_repr)
6975 kwargs["keys"] = [args[0] if command == "GEOSEARCH" else args[1]]
6977 return self.execute_command(command, *pieces, **kwargs)
6980AsyncGeoCommands = GeoCommands
6983class ModuleCommands(CommandsProtocol):
6984 """
6985 Redis Module commands.
6986 see: https://redis.io/topics/modules-intro
6987 """
6989 def module_load(self, path, *args) -> ResponseT:
6990 """
6991 Loads the module from ``path``.
6992 Passes all ``*args`` to the module, during loading.
6993 Raises ``ModuleError`` if a module is not found at ``path``.
6995 For more information, see https://redis.io/commands/module-load
6996 """
6997 return self.execute_command("MODULE LOAD", path, *args)
6999 def module_loadex(
7000 self,
7001 path: str,
7002 options: Optional[List[str]] = None,
7003 args: Optional[List[str]] = None,
7004 ) -> ResponseT:
7005 """
7006 Loads a module from a dynamic library at runtime with configuration directives.
7008 For more information, see https://redis.io/commands/module-loadex
7009 """
7010 pieces = []
7011 if options is not None:
7012 pieces.append("CONFIG")
7013 pieces.extend(options)
7014 if args is not None:
7015 pieces.append("ARGS")
7016 pieces.extend(args)
7018 return self.execute_command("MODULE LOADEX", path, *pieces)
7020 def module_unload(self, name) -> ResponseT:
7021 """
7022 Unloads the module ``name``.
7023 Raises ``ModuleError`` if ``name`` is not in loaded modules.
7025 For more information, see https://redis.io/commands/module-unload
7026 """
7027 return self.execute_command("MODULE UNLOAD", name)
7029 def module_list(self) -> ResponseT:
7030 """
7031 Returns a list of dictionaries containing the name and version of
7032 all loaded modules.
7034 For more information, see https://redis.io/commands/module-list
7035 """
7036 return self.execute_command("MODULE LIST")
7038 def command_info(self) -> None:
7039 raise NotImplementedError(
7040 "COMMAND INFO is intentionally not implemented in the client."
7041 )
7043 def command_count(self) -> ResponseT:
7044 return self.execute_command("COMMAND COUNT")
7046 def command_getkeys(self, *args) -> ResponseT:
7047 return self.execute_command("COMMAND GETKEYS", *args)
7049 def command(self) -> ResponseT:
7050 return self.execute_command("COMMAND")
7053class AsyncModuleCommands(ModuleCommands):
7054 async def command_info(self) -> None:
7055 return super().command_info()
7058class ClusterCommands(CommandsProtocol):
7059 """
7060 Class for Redis Cluster commands
7061 """
7063 def cluster(self, cluster_arg, *args, **kwargs) -> ResponseT:
7064 return self.execute_command(f"CLUSTER {cluster_arg.upper()}", *args, **kwargs)
7066 def readwrite(self, **kwargs) -> ResponseT:
7067 """
7068 Disables read queries for a connection to a Redis Cluster slave node.
7070 For more information, see https://redis.io/commands/readwrite
7071 """
7072 return self.execute_command("READWRITE", **kwargs)
7074 def readonly(self, **kwargs) -> ResponseT:
7075 """
7076 Enables read queries for a connection to a Redis Cluster replica node.
7078 For more information, see https://redis.io/commands/readonly
7079 """
7080 return self.execute_command("READONLY", **kwargs)
7083AsyncClusterCommands = ClusterCommands
7086class FunctionCommands:
7087 """
7088 Redis Function commands
7089 """
7091 def function_load(
7092 self, code: str, replace: Optional[bool] = False
7093 ) -> Union[Awaitable[str], str]:
7094 """
7095 Load a library to Redis.
7096 :param code: the source code (must start with
7097 Shebang statement that provides a metadata about the library)
7098 :param replace: changes the behavior to overwrite the existing library
7099 with the new contents.
7100 Return the library name that was loaded.
7102 For more information, see https://redis.io/commands/function-load
7103 """
7104 pieces = ["REPLACE"] if replace else []
7105 pieces.append(code)
7106 return self.execute_command("FUNCTION LOAD", *pieces)
7108 def function_delete(self, library: str) -> Union[Awaitable[str], str]:
7109 """
7110 Delete the library called ``library`` and all its functions.
7112 For more information, see https://redis.io/commands/function-delete
7113 """
7114 return self.execute_command("FUNCTION DELETE", library)
7116 def function_flush(self, mode: str = "SYNC") -> Union[Awaitable[str], str]:
7117 """
7118 Deletes all the libraries.
7120 For more information, see https://redis.io/commands/function-flush
7121 """
7122 return self.execute_command("FUNCTION FLUSH", mode)
7124 def function_list(
7125 self, library: Optional[str] = "*", withcode: Optional[bool] = False
7126 ) -> Union[Awaitable[List], List]:
7127 """
7128 Return information about the functions and libraries.
7130 Args:
7132 library: specify a pattern for matching library names
7133 withcode: cause the server to include the libraries source implementation
7134 in the reply
7135 """
7136 args = ["LIBRARYNAME", library]
7137 if withcode:
7138 args.append("WITHCODE")
7139 return self.execute_command("FUNCTION LIST", *args)
7141 def _fcall(
7142 self, command: str, function, numkeys: int, *keys_and_args: Any
7143 ) -> Union[Awaitable[str], str]:
7144 return self.execute_command(command, function, numkeys, *keys_and_args)
7146 def fcall(
7147 self, function, numkeys: int, *keys_and_args: Any
7148 ) -> Union[Awaitable[str], str]:
7149 """
7150 Invoke a function.
7152 For more information, see https://redis.io/commands/fcall
7153 """
7154 return self._fcall("FCALL", function, numkeys, *keys_and_args)
7156 def fcall_ro(
7157 self, function, numkeys: int, *keys_and_args: Any
7158 ) -> Union[Awaitable[str], str]:
7159 """
7160 This is a read-only variant of the FCALL command that cannot
7161 execute commands that modify data.
7163 For more information, see https://redis.io/commands/fcall_ro
7164 """
7165 return self._fcall("FCALL_RO", function, numkeys, *keys_and_args)
7167 def function_dump(self) -> Union[Awaitable[str], str]:
7168 """
7169 Return the serialized payload of loaded libraries.
7171 For more information, see https://redis.io/commands/function-dump
7172 """
7173 from redis.client import NEVER_DECODE
7175 options = {}
7176 options[NEVER_DECODE] = []
7178 return self.execute_command("FUNCTION DUMP", **options)
7180 def function_restore(
7181 self, payload: str, policy: Optional[str] = "APPEND"
7182 ) -> Union[Awaitable[str], str]:
7183 """
7184 Restore libraries from the serialized ``payload``.
7185 You can use the optional policy argument to provide a policy
7186 for handling existing libraries.
7188 For more information, see https://redis.io/commands/function-restore
7189 """
7190 return self.execute_command("FUNCTION RESTORE", payload, policy)
7192 def function_kill(self) -> Union[Awaitable[str], str]:
7193 """
7194 Kill a function that is currently executing.
7196 For more information, see https://redis.io/commands/function-kill
7197 """
7198 return self.execute_command("FUNCTION KILL")
7200 def function_stats(self) -> Union[Awaitable[List], List]:
7201 """
7202 Return information about the function that's currently running
7203 and information about the available execution engines.
7205 For more information, see https://redis.io/commands/function-stats
7206 """
7207 return self.execute_command("FUNCTION STATS")
7210AsyncFunctionCommands = FunctionCommands
7213class DataAccessCommands(
7214 BasicKeyCommands,
7215 HyperlogCommands,
7216 HashCommands,
7217 GeoCommands,
7218 ListCommands,
7219 ScanCommands,
7220 SetCommands,
7221 StreamCommands,
7222 SortedSetCommands,
7223):
7224 """
7225 A class containing all of the implemented data access redis commands.
7226 This class is to be used as a mixin for synchronous Redis clients.
7227 """
7230class AsyncDataAccessCommands(
7231 AsyncBasicKeyCommands,
7232 AsyncHyperlogCommands,
7233 AsyncHashCommands,
7234 AsyncGeoCommands,
7235 AsyncListCommands,
7236 AsyncScanCommands,
7237 AsyncSetCommands,
7238 AsyncStreamCommands,
7239 AsyncSortedSetCommands,
7240):
7241 """
7242 A class containing all of the implemented data access redis commands.
7243 This class is to be used as a mixin for asynchronous Redis clients.
7244 """
7247class CoreCommands(
7248 ACLCommands,
7249 ClusterCommands,
7250 DataAccessCommands,
7251 ManagementCommands,
7252 ModuleCommands,
7253 PubSubCommands,
7254 ScriptCommands,
7255 FunctionCommands,
7256):
7257 """
7258 A class containing all of the implemented redis commands. This class is
7259 to be used as a mixin for synchronous Redis clients.
7260 """
7263class AsyncCoreCommands(
7264 AsyncACLCommands,
7265 AsyncClusterCommands,
7266 AsyncDataAccessCommands,
7267 AsyncManagementCommands,
7268 AsyncModuleCommands,
7269 AsyncPubSubCommands,
7270 AsyncScriptCommands,
7271 AsyncFunctionCommands,
7272):
7273 """
7274 A class containing all of the implemented redis commands. This class is
7275 to be used as a mixin for asynchronous Redis clients.
7276 """