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 warnings
6from enum import Enum
7from typing import (
8 TYPE_CHECKING,
9 Any,
10 AsyncIterator,
11 Awaitable,
12 Callable,
13 Dict,
14 Iterable,
15 Iterator,
16 List,
17 Literal,
18 Mapping,
19 Optional,
20 Sequence,
21 Set,
22 Tuple,
23 Union,
24)
26from redis.exceptions import ConnectionError, DataError, NoScriptError, RedisError
27from redis.typing import (
28 AbsExpiryT,
29 AnyKeyT,
30 BitfieldOffsetT,
31 ChannelT,
32 CommandsProtocol,
33 ConsumerT,
34 EncodableT,
35 ExpiryT,
36 FieldT,
37 GroupT,
38 KeysT,
39 KeyT,
40 Number,
41 PatternT,
42 ResponseT,
43 ScriptTextT,
44 StreamIdT,
45 TimeoutSecT,
46 ZScoreBoundT,
47)
48from redis.utils import (
49 deprecated_function,
50 experimental_args,
51 experimental_method,
52 extract_expire_flags,
53)
55from .helpers import at_most_one_value_set, list_or_args
57if TYPE_CHECKING:
58 import redis.asyncio.client
59 import redis.client
62class ACLCommands(CommandsProtocol):
63 """
64 Redis Access Control List (ACL) commands.
65 see: https://redis.io/topics/acl
66 """
68 def acl_cat(self, category: Optional[str] = None, **kwargs) -> ResponseT:
69 """
70 Returns a list of categories or commands within a category.
72 If ``category`` is not supplied, returns a list of all categories.
73 If ``category`` is supplied, returns a list of all commands within
74 that category.
76 For more information, see https://redis.io/commands/acl-cat
77 """
78 pieces: list[EncodableT] = [category] if category else []
79 return self.execute_command("ACL CAT", *pieces, **kwargs)
81 def acl_dryrun(self, username, *args, **kwargs):
82 """
83 Simulate the execution of a given command by a given ``username``.
85 For more information, see https://redis.io/commands/acl-dryrun
86 """
87 return self.execute_command("ACL DRYRUN", username, *args, **kwargs)
89 def acl_deluser(self, *username: str, **kwargs) -> ResponseT:
90 """
91 Delete the ACL for the specified ``username``\\s
93 For more information, see https://redis.io/commands/acl-deluser
94 """
95 return self.execute_command("ACL DELUSER", *username, **kwargs)
97 def acl_genpass(self, bits: Optional[int] = None, **kwargs) -> ResponseT:
98 """Generate a random password value.
99 If ``bits`` is supplied then use this number of bits, rounded to
100 the next multiple of 4.
101 See: https://redis.io/commands/acl-genpass
102 """
103 pieces = []
104 if bits is not None:
105 try:
106 b = int(bits)
107 if b < 0 or b > 4096:
108 raise ValueError
109 pieces.append(b)
110 except ValueError:
111 raise DataError(
112 "genpass optionally accepts a bits argument, between 0 and 4096."
113 )
114 return self.execute_command("ACL GENPASS", *pieces, **kwargs)
116 def acl_getuser(self, username: str, **kwargs) -> ResponseT:
117 """
118 Get the ACL details for the specified ``username``.
120 If ``username`` does not exist, return None
122 For more information, see https://redis.io/commands/acl-getuser
123 """
124 return self.execute_command("ACL GETUSER", username, **kwargs)
126 def acl_help(self, **kwargs) -> ResponseT:
127 """The ACL HELP command returns helpful text describing
128 the different subcommands.
130 For more information, see https://redis.io/commands/acl-help
131 """
132 return self.execute_command("ACL HELP", **kwargs)
134 def acl_list(self, **kwargs) -> ResponseT:
135 """
136 Return a list of all ACLs on the server
138 For more information, see https://redis.io/commands/acl-list
139 """
140 return self.execute_command("ACL LIST", **kwargs)
142 def acl_log(self, count: Optional[int] = None, **kwargs) -> ResponseT:
143 """
144 Get ACL logs as a list.
145 :param int count: Get logs[0:count].
146 :rtype: List.
148 For more information, see https://redis.io/commands/acl-log
149 """
150 args = []
151 if count is not None:
152 if not isinstance(count, int):
153 raise DataError("ACL LOG count must be an integer")
154 args.append(count)
156 return self.execute_command("ACL LOG", *args, **kwargs)
158 def acl_log_reset(self, **kwargs) -> ResponseT:
159 """
160 Reset ACL logs.
161 :rtype: Boolean.
163 For more information, see https://redis.io/commands/acl-log
164 """
165 args = [b"RESET"]
166 return self.execute_command("ACL LOG", *args, **kwargs)
168 def acl_load(self, **kwargs) -> ResponseT:
169 """
170 Load ACL rules from the configured ``aclfile``.
172 Note that the server must be configured with the ``aclfile``
173 directive to be able to load ACL rules from an aclfile.
175 For more information, see https://redis.io/commands/acl-load
176 """
177 return self.execute_command("ACL LOAD", **kwargs)
179 def acl_save(self, **kwargs) -> ResponseT:
180 """
181 Save ACL rules to the configured ``aclfile``.
183 Note that the server must be configured with the ``aclfile``
184 directive to be able to save ACL rules to an aclfile.
186 For more information, see https://redis.io/commands/acl-save
187 """
188 return self.execute_command("ACL SAVE", **kwargs)
190 def acl_setuser(
191 self,
192 username: str,
193 enabled: bool = False,
194 nopass: bool = False,
195 passwords: Optional[Union[str, Iterable[str]]] = None,
196 hashed_passwords: Optional[Union[str, Iterable[str]]] = None,
197 categories: Optional[Iterable[str]] = None,
198 commands: Optional[Iterable[str]] = None,
199 keys: Optional[Iterable[KeyT]] = None,
200 channels: Optional[Iterable[ChannelT]] = None,
201 selectors: Optional[Iterable[Tuple[str, KeyT]]] = None,
202 reset: bool = False,
203 reset_keys: bool = False,
204 reset_channels: bool = False,
205 reset_passwords: bool = False,
206 **kwargs,
207 ) -> ResponseT:
208 """
209 Create or update an ACL user.
211 Create or update the ACL for `username`. If the user already exists,
212 the existing ACL is completely overwritten and replaced with the
213 specified values.
215 For more information, see https://redis.io/commands/acl-setuser
217 Args:
218 username: The name of the user whose ACL is to be created or updated.
219 enabled: Indicates whether the user should be allowed to authenticate.
220 Defaults to `False`.
221 nopass: Indicates whether the user can authenticate without a password.
222 This cannot be `True` if `passwords` are also specified.
223 passwords: A list of plain text passwords to add to or remove from the user.
224 Each password must be prefixed with a '+' to add or a '-' to
225 remove. For convenience, a single prefixed string can be used
226 when adding or removing a single password.
227 hashed_passwords: A list of SHA-256 hashed passwords to add to or remove
228 from the user. Each hashed password must be prefixed with
229 a '+' to add or a '-' to remove. For convenience, a single
230 prefixed string can be used when adding or removing a
231 single password.
232 categories: A list of strings representing category permissions. Each string
233 must be prefixed with either a '+' to add the category
234 permission or a '-' to remove the category permission.
235 commands: A list of strings representing command permissions. Each string
236 must be prefixed with either a '+' to add the command permission
237 or a '-' to remove the command permission.
238 keys: A list of key patterns to grant the user access to. Key patterns allow
239 ``'*'`` to support wildcard matching. For example, ``'*'`` grants
240 access to all keys while ``'cache:*'`` grants access to all keys that
241 are prefixed with ``cache:``.
242 `keys` should not be prefixed with a ``'~'``.
243 reset: Indicates whether the user should be fully reset prior to applying
244 the new ACL. Setting this to `True` will remove all existing
245 passwords, flags, and privileges from the user and then apply the
246 specified rules. If `False`, the user's existing passwords, flags,
247 and privileges will be kept and any new specified rules will be
248 applied on top.
249 reset_keys: Indicates whether the user's key permissions should be reset
250 prior to applying any new key permissions specified in `keys`.
251 If `False`, the user's existing key permissions will be kept and
252 any new specified key permissions will be applied on top.
253 reset_channels: Indicates whether the user's channel permissions should be
254 reset prior to applying any new channel permissions
255 specified in `channels`. If `False`, the user's existing
256 channel permissions will be kept and any new specified
257 channel permissions will be applied on top.
258 reset_passwords: Indicates whether to remove all existing passwords and the
259 `nopass` flag from the user prior to applying any new
260 passwords specified in `passwords` or `hashed_passwords`.
261 If `False`, the user's existing passwords and `nopass`
262 status will be kept and any new specified passwords or
263 hashed passwords will be applied on top.
264 """
265 encoder = self.get_encoder()
266 pieces: List[EncodableT] = [username]
268 if reset:
269 pieces.append(b"reset")
271 if reset_keys:
272 pieces.append(b"resetkeys")
274 if reset_channels:
275 pieces.append(b"resetchannels")
277 if reset_passwords:
278 pieces.append(b"resetpass")
280 if enabled:
281 pieces.append(b"on")
282 else:
283 pieces.append(b"off")
285 if (passwords or hashed_passwords) and nopass:
286 raise DataError(
287 "Cannot set 'nopass' and supply 'passwords' or 'hashed_passwords'"
288 )
290 if passwords:
291 # as most users will have only one password, allow remove_passwords
292 # to be specified as a simple string or a list
293 passwords = list_or_args(passwords, [])
294 for i, password in enumerate(passwords):
295 password = encoder.encode(password)
296 if password.startswith(b"+"):
297 pieces.append(b">%s" % password[1:])
298 elif password.startswith(b"-"):
299 pieces.append(b"<%s" % password[1:])
300 else:
301 raise DataError(
302 f"Password {i} must be prefixed with a "
303 f'"+" to add or a "-" to remove'
304 )
306 if hashed_passwords:
307 # as most users will have only one password, allow remove_passwords
308 # to be specified as a simple string or a list
309 hashed_passwords = list_or_args(hashed_passwords, [])
310 for i, hashed_password in enumerate(hashed_passwords):
311 hashed_password = encoder.encode(hashed_password)
312 if hashed_password.startswith(b"+"):
313 pieces.append(b"#%s" % hashed_password[1:])
314 elif hashed_password.startswith(b"-"):
315 pieces.append(b"!%s" % hashed_password[1:])
316 else:
317 raise DataError(
318 f"Hashed password {i} must be prefixed with a "
319 f'"+" to add or a "-" to remove'
320 )
322 if nopass:
323 pieces.append(b"nopass")
325 if categories:
326 for category in categories:
327 category = encoder.encode(category)
328 # categories can be prefixed with one of (+@, +, -@, -)
329 if category.startswith(b"+@"):
330 pieces.append(category)
331 elif category.startswith(b"+"):
332 pieces.append(b"+@%s" % category[1:])
333 elif category.startswith(b"-@"):
334 pieces.append(category)
335 elif category.startswith(b"-"):
336 pieces.append(b"-@%s" % category[1:])
337 else:
338 raise DataError(
339 f'Category "{encoder.decode(category, force=True)}" '
340 'must be prefixed with "+" or "-"'
341 )
342 if commands:
343 for cmd in commands:
344 cmd = encoder.encode(cmd)
345 if not cmd.startswith(b"+") and not cmd.startswith(b"-"):
346 raise DataError(
347 f'Command "{encoder.decode(cmd, force=True)}" '
348 'must be prefixed with "+" or "-"'
349 )
350 pieces.append(cmd)
352 if keys:
353 for key in keys:
354 key = encoder.encode(key)
355 if not key.startswith(b"%") and not key.startswith(b"~"):
356 key = b"~%s" % key
357 pieces.append(key)
359 if channels:
360 for channel in channels:
361 channel = encoder.encode(channel)
362 pieces.append(b"&%s" % channel)
364 if selectors:
365 for cmd, key in selectors:
366 cmd = encoder.encode(cmd)
367 if not cmd.startswith(b"+") and not cmd.startswith(b"-"):
368 raise DataError(
369 f'Command "{encoder.decode(cmd, force=True)}" '
370 'must be prefixed with "+" or "-"'
371 )
373 key = encoder.encode(key)
374 if not key.startswith(b"%") and not key.startswith(b"~"):
375 key = b"~%s" % key
377 pieces.append(b"(%s %s)" % (cmd, key))
379 return self.execute_command("ACL SETUSER", *pieces, **kwargs)
381 def acl_users(self, **kwargs) -> ResponseT:
382 """Returns a list of all registered users on the server.
384 For more information, see https://redis.io/commands/acl-users
385 """
386 return self.execute_command("ACL USERS", **kwargs)
388 def acl_whoami(self, **kwargs) -> ResponseT:
389 """Get the username for the current connection
391 For more information, see https://redis.io/commands/acl-whoami
392 """
393 return self.execute_command("ACL WHOAMI", **kwargs)
396AsyncACLCommands = ACLCommands
399class ManagementCommands(CommandsProtocol):
400 """
401 Redis management commands
402 """
404 def auth(self, password: str, username: Optional[str] = None, **kwargs):
405 """
406 Authenticates the user. If you do not pass username, Redis will try to
407 authenticate for the "default" user. If you do pass username, it will
408 authenticate for the given user.
409 For more information, see https://redis.io/commands/auth
410 """
411 pieces = []
412 if username is not None:
413 pieces.append(username)
414 pieces.append(password)
415 return self.execute_command("AUTH", *pieces, **kwargs)
417 def bgrewriteaof(self, **kwargs):
418 """Tell the Redis server to rewrite the AOF file from data in memory.
420 For more information, see https://redis.io/commands/bgrewriteaof
421 """
422 return self.execute_command("BGREWRITEAOF", **kwargs)
424 def bgsave(self, schedule: bool = True, **kwargs) -> ResponseT:
425 """
426 Tell the Redis server to save its data to disk. Unlike save(),
427 this method is asynchronous and returns immediately.
429 For more information, see https://redis.io/commands/bgsave
430 """
431 pieces = []
432 if schedule:
433 pieces.append("SCHEDULE")
434 return self.execute_command("BGSAVE", *pieces, **kwargs)
436 def role(self) -> ResponseT:
437 """
438 Provide information on the role of a Redis instance in
439 the context of replication, by returning if the instance
440 is currently a master, slave, or sentinel.
442 For more information, see https://redis.io/commands/role
443 """
444 return self.execute_command("ROLE")
446 def client_kill(self, address: str, **kwargs) -> ResponseT:
447 """Disconnects the client at ``address`` (ip:port)
449 For more information, see https://redis.io/commands/client-kill
450 """
451 return self.execute_command("CLIENT KILL", address, **kwargs)
453 def client_kill_filter(
454 self,
455 _id: Optional[str] = None,
456 _type: Optional[str] = None,
457 addr: Optional[str] = None,
458 skipme: Optional[bool] = None,
459 laddr: Optional[bool] = None,
460 user: Optional[str] = None,
461 maxage: Optional[int] = None,
462 **kwargs,
463 ) -> ResponseT:
464 """
465 Disconnects client(s) using a variety of filter options
466 :param _id: Kills a client by its unique ID field
467 :param _type: Kills a client by type where type is one of 'normal',
468 'master', 'slave' or 'pubsub'
469 :param addr: Kills a client by its 'address:port'
470 :param skipme: If True, then the client calling the command
471 will not get killed even if it is identified by one of the filter
472 options. If skipme is not provided, the server defaults to skipme=True
473 :param laddr: Kills a client by its 'local (bind) address:port'
474 :param user: Kills a client for a specific user name
475 :param maxage: Kills clients that are older than the specified age in seconds
476 """
477 args = []
478 if _type is not None:
479 client_types = ("normal", "master", "slave", "pubsub")
480 if str(_type).lower() not in client_types:
481 raise DataError(f"CLIENT KILL type must be one of {client_types!r}")
482 args.extend((b"TYPE", _type))
483 if skipme is not None:
484 if not isinstance(skipme, bool):
485 raise DataError("CLIENT KILL skipme must be a bool")
486 if skipme:
487 args.extend((b"SKIPME", b"YES"))
488 else:
489 args.extend((b"SKIPME", b"NO"))
490 if _id is not None:
491 args.extend((b"ID", _id))
492 if addr is not None:
493 args.extend((b"ADDR", addr))
494 if laddr is not None:
495 args.extend((b"LADDR", laddr))
496 if user is not None:
497 args.extend((b"USER", user))
498 if maxage is not None:
499 args.extend((b"MAXAGE", maxage))
500 if not args:
501 raise DataError(
502 "CLIENT KILL <filter> <value> ... ... <filter> "
503 "<value> must specify at least one filter"
504 )
505 return self.execute_command("CLIENT KILL", *args, **kwargs)
507 def client_info(self, **kwargs) -> ResponseT:
508 """
509 Returns information and statistics about the current
510 client connection.
512 For more information, see https://redis.io/commands/client-info
513 """
514 return self.execute_command("CLIENT INFO", **kwargs)
516 def client_list(
517 self, _type: Optional[str] = None, client_id: List[EncodableT] = [], **kwargs
518 ) -> ResponseT:
519 """
520 Returns a list of currently connected clients.
521 If type of client specified, only that type will be returned.
523 :param _type: optional. one of the client types (normal, master,
524 replica, pubsub)
525 :param client_id: optional. a list of client ids
527 For more information, see https://redis.io/commands/client-list
528 """
529 args = []
530 if _type is not None:
531 client_types = ("normal", "master", "replica", "pubsub")
532 if str(_type).lower() not in client_types:
533 raise DataError(f"CLIENT LIST _type must be one of {client_types!r}")
534 args.append(b"TYPE")
535 args.append(_type)
536 if not isinstance(client_id, list):
537 raise DataError("client_id must be a list")
538 if client_id:
539 args.append(b"ID")
540 args += client_id
541 return self.execute_command("CLIENT LIST", *args, **kwargs)
543 def client_getname(self, **kwargs) -> ResponseT:
544 """
545 Returns the current connection name
547 For more information, see https://redis.io/commands/client-getname
548 """
549 return self.execute_command("CLIENT GETNAME", **kwargs)
551 def client_getredir(self, **kwargs) -> ResponseT:
552 """
553 Returns the ID (an integer) of the client to whom we are
554 redirecting tracking notifications.
556 see: https://redis.io/commands/client-getredir
557 """
558 return self.execute_command("CLIENT GETREDIR", **kwargs)
560 def client_reply(
561 self, reply: Union[Literal["ON"], Literal["OFF"], Literal["SKIP"]], **kwargs
562 ) -> ResponseT:
563 """
564 Enable and disable redis server replies.
566 ``reply`` Must be ON OFF or SKIP,
567 ON - The default most with server replies to commands
568 OFF - Disable server responses to commands
569 SKIP - Skip the response of the immediately following command.
571 Note: When setting OFF or SKIP replies, you will need a client object
572 with a timeout specified in seconds, and will need to catch the
573 TimeoutError.
574 The test_client_reply unit test illustrates this, and
575 conftest.py has a client with a timeout.
577 See https://redis.io/commands/client-reply
578 """
579 replies = ["ON", "OFF", "SKIP"]
580 if reply not in replies:
581 raise DataError(f"CLIENT REPLY must be one of {replies!r}")
582 return self.execute_command("CLIENT REPLY", reply, **kwargs)
584 def client_id(self, **kwargs) -> ResponseT:
585 """
586 Returns the current connection id
588 For more information, see https://redis.io/commands/client-id
589 """
590 return self.execute_command("CLIENT ID", **kwargs)
592 def client_tracking_on(
593 self,
594 clientid: Optional[int] = None,
595 prefix: Sequence[KeyT] = [],
596 bcast: bool = False,
597 optin: bool = False,
598 optout: bool = False,
599 noloop: bool = False,
600 ) -> ResponseT:
601 """
602 Turn on the tracking mode.
603 For more information, about the options look at client_tracking func.
605 See https://redis.io/commands/client-tracking
606 """
607 return self.client_tracking(
608 True, clientid, prefix, bcast, optin, optout, noloop
609 )
611 def client_tracking_off(
612 self,
613 clientid: Optional[int] = None,
614 prefix: Sequence[KeyT] = [],
615 bcast: bool = False,
616 optin: bool = False,
617 optout: bool = False,
618 noloop: bool = False,
619 ) -> ResponseT:
620 """
621 Turn off the tracking mode.
622 For more information, about the options look at client_tracking func.
624 See https://redis.io/commands/client-tracking
625 """
626 return self.client_tracking(
627 False, clientid, prefix, bcast, optin, optout, noloop
628 )
630 def client_tracking(
631 self,
632 on: bool = True,
633 clientid: Optional[int] = None,
634 prefix: Sequence[KeyT] = [],
635 bcast: bool = False,
636 optin: bool = False,
637 optout: bool = False,
638 noloop: bool = False,
639 **kwargs,
640 ) -> ResponseT:
641 """
642 Enables the tracking feature of the Redis server, that is used
643 for server assisted client side caching.
645 ``on`` indicate for tracking on or tracking off. The default is on.
647 ``clientid`` send invalidation messages to the connection with
648 the specified ID.
650 ``bcast`` enable tracking in broadcasting mode. In this mode
651 invalidation messages are reported for all the prefixes
652 specified, regardless of the keys requested by the connection.
654 ``optin`` when broadcasting is NOT active, normally don't track
655 keys in read only commands, unless they are called immediately
656 after a CLIENT CACHING yes command.
658 ``optout`` when broadcasting is NOT active, normally track keys in
659 read only commands, unless they are called immediately after a
660 CLIENT CACHING no command.
662 ``noloop`` don't send notifications about keys modified by this
663 connection itself.
665 ``prefix`` for broadcasting, register a given key prefix, so that
666 notifications will be provided only for keys starting with this string.
668 See https://redis.io/commands/client-tracking
669 """
671 if len(prefix) != 0 and bcast is False:
672 raise DataError("Prefix can only be used with bcast")
674 pieces = ["ON"] if on else ["OFF"]
675 if clientid is not None:
676 pieces.extend(["REDIRECT", clientid])
677 for p in prefix:
678 pieces.extend(["PREFIX", p])
679 if bcast:
680 pieces.append("BCAST")
681 if optin:
682 pieces.append("OPTIN")
683 if optout:
684 pieces.append("OPTOUT")
685 if noloop:
686 pieces.append("NOLOOP")
688 return self.execute_command("CLIENT TRACKING", *pieces)
690 def client_trackinginfo(self, **kwargs) -> ResponseT:
691 """
692 Returns the information about the current client connection's
693 use of the server assisted client side cache.
695 See https://redis.io/commands/client-trackinginfo
696 """
697 return self.execute_command("CLIENT TRACKINGINFO", **kwargs)
699 def client_setname(self, name: str, **kwargs) -> ResponseT:
700 """
701 Sets the current connection name
703 For more information, see https://redis.io/commands/client-setname
705 .. note::
706 This method sets client name only for **current** connection.
708 If you want to set a common name for all connections managed
709 by this client, use ``client_name`` constructor argument.
710 """
711 return self.execute_command("CLIENT SETNAME", name, **kwargs)
713 def client_setinfo(self, attr: str, value: str, **kwargs) -> ResponseT:
714 """
715 Sets the current connection library name or version
716 For mor information see https://redis.io/commands/client-setinfo
717 """
718 return self.execute_command("CLIENT SETINFO", attr, value, **kwargs)
720 def client_unblock(
721 self, client_id: int, error: bool = False, **kwargs
722 ) -> ResponseT:
723 """
724 Unblocks a connection by its client id.
725 If ``error`` is True, unblocks the client with a special error message.
726 If ``error`` is False (default), the client is unblocked using the
727 regular timeout mechanism.
729 For more information, see https://redis.io/commands/client-unblock
730 """
731 args = ["CLIENT UNBLOCK", int(client_id)]
732 if error:
733 args.append(b"ERROR")
734 return self.execute_command(*args, **kwargs)
736 def client_pause(self, timeout: int, all: bool = True, **kwargs) -> ResponseT:
737 """
738 Suspend all the Redis clients for the specified amount of time.
741 For more information, see https://redis.io/commands/client-pause
743 Args:
744 timeout: milliseconds to pause clients
745 all: If true (default) all client commands are blocked.
746 otherwise, clients are only blocked if they attempt to execute
747 a write command.
749 For the WRITE mode, some commands have special behavior:
751 * EVAL/EVALSHA: Will block client for all scripts.
752 * PUBLISH: Will block client.
753 * PFCOUNT: Will block client.
754 * WAIT: Acknowledgments will be delayed, so this command will
755 appear blocked.
756 """
757 args = ["CLIENT PAUSE", str(timeout)]
758 if not isinstance(timeout, int):
759 raise DataError("CLIENT PAUSE timeout must be an integer")
760 if not all:
761 args.append("WRITE")
762 return self.execute_command(*args, **kwargs)
764 def client_unpause(self, **kwargs) -> ResponseT:
765 """
766 Unpause all redis clients
768 For more information, see https://redis.io/commands/client-unpause
769 """
770 return self.execute_command("CLIENT UNPAUSE", **kwargs)
772 def client_no_evict(self, mode: str) -> Union[Awaitable[str], str]:
773 """
774 Sets the client eviction mode for the current connection.
776 For more information, see https://redis.io/commands/client-no-evict
777 """
778 return self.execute_command("CLIENT NO-EVICT", mode)
780 def client_no_touch(self, mode: str) -> Union[Awaitable[str], str]:
781 """
782 # The command controls whether commands sent by the client will alter
783 # the LRU/LFU of the keys they access.
784 # When turned on, the current client will not change LFU/LRU stats,
785 # unless it sends the TOUCH command.
787 For more information, see https://redis.io/commands/client-no-touch
788 """
789 return self.execute_command("CLIENT NO-TOUCH", mode)
791 def command(self, **kwargs):
792 """
793 Returns dict reply of details about all Redis commands.
795 For more information, see https://redis.io/commands/command
796 """
797 return self.execute_command("COMMAND", **kwargs)
799 def command_info(self, **kwargs) -> None:
800 raise NotImplementedError(
801 "COMMAND INFO is intentionally not implemented in the client."
802 )
804 def command_count(self, **kwargs) -> ResponseT:
805 return self.execute_command("COMMAND COUNT", **kwargs)
807 def command_list(
808 self,
809 module: Optional[str] = None,
810 category: Optional[str] = None,
811 pattern: Optional[str] = None,
812 ) -> ResponseT:
813 """
814 Return an array of the server's command names.
815 You can use one of the following filters:
816 ``module``: get the commands that belong to the module
817 ``category``: get the commands in the ACL category
818 ``pattern``: get the commands that match the given pattern
820 For more information, see https://redis.io/commands/command-list/
821 """
822 pieces = []
823 if module is not None:
824 pieces.extend(["MODULE", module])
825 if category is not None:
826 pieces.extend(["ACLCAT", category])
827 if pattern is not None:
828 pieces.extend(["PATTERN", pattern])
830 if pieces:
831 pieces.insert(0, "FILTERBY")
833 return self.execute_command("COMMAND LIST", *pieces)
835 def command_getkeysandflags(self, *args: str) -> List[Union[str, List[str]]]:
836 """
837 Returns array of keys from a full Redis command and their usage flags.
839 For more information, see https://redis.io/commands/command-getkeysandflags
840 """
841 return self.execute_command("COMMAND GETKEYSANDFLAGS", *args)
843 def command_docs(self, *args):
844 """
845 This function throws a NotImplementedError since it is intentionally
846 not supported.
847 """
848 raise NotImplementedError(
849 "COMMAND DOCS is intentionally not implemented in the client."
850 )
852 def config_get(
853 self, pattern: PatternT = "*", *args: PatternT, **kwargs
854 ) -> ResponseT:
855 """
856 Return a dictionary of configuration based on the ``pattern``
858 For more information, see https://redis.io/commands/config-get
859 """
860 return self.execute_command("CONFIG GET", pattern, *args, **kwargs)
862 def config_set(
863 self,
864 name: KeyT,
865 value: EncodableT,
866 *args: Union[KeyT, EncodableT],
867 **kwargs,
868 ) -> ResponseT:
869 """Set config item ``name`` with ``value``
871 For more information, see https://redis.io/commands/config-set
872 """
873 return self.execute_command("CONFIG SET", name, value, *args, **kwargs)
875 def config_resetstat(self, **kwargs) -> ResponseT:
876 """
877 Reset runtime statistics
879 For more information, see https://redis.io/commands/config-resetstat
880 """
881 return self.execute_command("CONFIG RESETSTAT", **kwargs)
883 def config_rewrite(self, **kwargs) -> ResponseT:
884 """
885 Rewrite config file with the minimal change to reflect running config.
887 For more information, see https://redis.io/commands/config-rewrite
888 """
889 return self.execute_command("CONFIG REWRITE", **kwargs)
891 def dbsize(self, **kwargs) -> ResponseT:
892 """
893 Returns the number of keys in the current database
895 For more information, see https://redis.io/commands/dbsize
896 """
897 return self.execute_command("DBSIZE", **kwargs)
899 def debug_object(self, key: KeyT, **kwargs) -> ResponseT:
900 """
901 Returns version specific meta information about a given key
903 For more information, see https://redis.io/commands/debug-object
904 """
905 return self.execute_command("DEBUG OBJECT", key, **kwargs)
907 def debug_segfault(self, **kwargs) -> None:
908 raise NotImplementedError(
909 """
910 DEBUG SEGFAULT is intentionally not implemented in the client.
912 For more information, see https://redis.io/commands/debug-segfault
913 """
914 )
916 def echo(self, value: EncodableT, **kwargs) -> ResponseT:
917 """
918 Echo the string back from the server
920 For more information, see https://redis.io/commands/echo
921 """
922 return self.execute_command("ECHO", value, **kwargs)
924 def flushall(self, asynchronous: bool = False, **kwargs) -> ResponseT:
925 """
926 Delete all keys in all databases on the current host.
928 ``asynchronous`` indicates whether the operation is
929 executed asynchronously by the server.
931 For more information, see https://redis.io/commands/flushall
932 """
933 args = []
934 if asynchronous:
935 args.append(b"ASYNC")
936 return self.execute_command("FLUSHALL", *args, **kwargs)
938 def flushdb(self, asynchronous: bool = False, **kwargs) -> ResponseT:
939 """
940 Delete all keys in the current database.
942 ``asynchronous`` indicates whether the operation is
943 executed asynchronously by the server.
945 For more information, see https://redis.io/commands/flushdb
946 """
947 args = []
948 if asynchronous:
949 args.append(b"ASYNC")
950 return self.execute_command("FLUSHDB", *args, **kwargs)
952 def sync(self) -> ResponseT:
953 """
954 Initiates a replication stream from the master.
956 For more information, see https://redis.io/commands/sync
957 """
958 from redis.client import NEVER_DECODE
960 options = {}
961 options[NEVER_DECODE] = []
962 return self.execute_command("SYNC", **options)
964 def psync(self, replicationid: str, offset: int):
965 """
966 Initiates a replication stream from the master.
967 Newer version for `sync`.
969 For more information, see https://redis.io/commands/sync
970 """
971 from redis.client import NEVER_DECODE
973 options = {}
974 options[NEVER_DECODE] = []
975 return self.execute_command("PSYNC", replicationid, offset, **options)
977 def swapdb(self, first: int, second: int, **kwargs) -> ResponseT:
978 """
979 Swap two databases
981 For more information, see https://redis.io/commands/swapdb
982 """
983 return self.execute_command("SWAPDB", first, second, **kwargs)
985 def select(self, index: int, **kwargs) -> ResponseT:
986 """Select the Redis logical database at index.
988 See: https://redis.io/commands/select
989 """
990 return self.execute_command("SELECT", index, **kwargs)
992 def info(self, section: Optional[str] = None, *args: str, **kwargs) -> ResponseT:
993 """
994 Returns a dictionary containing information about the Redis server
996 The ``section`` option can be used to select a specific section
997 of information
999 The section option is not supported by older versions of Redis Server,
1000 and will generate ResponseError
1002 For more information, see https://redis.io/commands/info
1003 """
1004 if section is None:
1005 return self.execute_command("INFO", **kwargs)
1006 else:
1007 return self.execute_command("INFO", section, *args, **kwargs)
1009 def lastsave(self, **kwargs) -> ResponseT:
1010 """
1011 Return a Python datetime object representing the last time the
1012 Redis database was saved to disk
1014 For more information, see https://redis.io/commands/lastsave
1015 """
1016 return self.execute_command("LASTSAVE", **kwargs)
1018 def latency_doctor(self):
1019 """Raise a NotImplementedError, as the client will not support LATENCY DOCTOR.
1020 This function is best used within the redis-cli.
1022 For more information, see https://redis.io/commands/latency-doctor
1023 """
1024 raise NotImplementedError(
1025 """
1026 LATENCY DOCTOR is intentionally not implemented in the client.
1028 For more information, see https://redis.io/commands/latency-doctor
1029 """
1030 )
1032 def latency_graph(self):
1033 """Raise a NotImplementedError, as the client will not support LATENCY GRAPH.
1034 This function is best used within the redis-cli.
1036 For more information, see https://redis.io/commands/latency-graph.
1037 """
1038 raise NotImplementedError(
1039 """
1040 LATENCY GRAPH is intentionally not implemented in the client.
1042 For more information, see https://redis.io/commands/latency-graph
1043 """
1044 )
1046 def lolwut(self, *version_numbers: Union[str, float], **kwargs) -> ResponseT:
1047 """
1048 Get the Redis version and a piece of generative computer art
1050 See: https://redis.io/commands/lolwut
1051 """
1052 if version_numbers:
1053 return self.execute_command("LOLWUT VERSION", *version_numbers, **kwargs)
1054 else:
1055 return self.execute_command("LOLWUT", **kwargs)
1057 def reset(self) -> ResponseT:
1058 """Perform a full reset on the connection's server-side context.
1060 See: https://redis.io/commands/reset
1061 """
1062 return self.execute_command("RESET")
1064 def migrate(
1065 self,
1066 host: str,
1067 port: int,
1068 keys: KeysT,
1069 destination_db: int,
1070 timeout: int,
1071 copy: bool = False,
1072 replace: bool = False,
1073 auth: Optional[str] = None,
1074 **kwargs,
1075 ) -> ResponseT:
1076 """
1077 Migrate 1 or more keys from the current Redis server to a different
1078 server specified by the ``host``, ``port`` and ``destination_db``.
1080 The ``timeout``, specified in milliseconds, indicates the maximum
1081 time the connection between the two servers can be idle before the
1082 command is interrupted.
1084 If ``copy`` is True, the specified ``keys`` are NOT deleted from
1085 the source server.
1087 If ``replace`` is True, this operation will overwrite the keys
1088 on the destination server if they exist.
1090 If ``auth`` is specified, authenticate to the destination server with
1091 the password provided.
1093 For more information, see https://redis.io/commands/migrate
1094 """
1095 keys = list_or_args(keys, [])
1096 if not keys:
1097 raise DataError("MIGRATE requires at least one key")
1098 pieces = []
1099 if copy:
1100 pieces.append(b"COPY")
1101 if replace:
1102 pieces.append(b"REPLACE")
1103 if auth:
1104 pieces.append(b"AUTH")
1105 pieces.append(auth)
1106 pieces.append(b"KEYS")
1107 pieces.extend(keys)
1108 return self.execute_command(
1109 "MIGRATE", host, port, "", destination_db, timeout, *pieces, **kwargs
1110 )
1112 def object(self, infotype: str, key: KeyT, **kwargs) -> ResponseT:
1113 """
1114 Return the encoding, idletime, or refcount about the key
1115 """
1116 return self.execute_command(
1117 "OBJECT", infotype, key, infotype=infotype, **kwargs
1118 )
1120 def memory_doctor(self, **kwargs) -> None:
1121 raise NotImplementedError(
1122 """
1123 MEMORY DOCTOR is intentionally not implemented in the client.
1125 For more information, see https://redis.io/commands/memory-doctor
1126 """
1127 )
1129 def memory_help(self, **kwargs) -> None:
1130 raise NotImplementedError(
1131 """
1132 MEMORY HELP is intentionally not implemented in the client.
1134 For more information, see https://redis.io/commands/memory-help
1135 """
1136 )
1138 def memory_stats(self, **kwargs) -> ResponseT:
1139 """
1140 Return a dictionary of memory stats
1142 For more information, see https://redis.io/commands/memory-stats
1143 """
1144 return self.execute_command("MEMORY STATS", **kwargs)
1146 def memory_malloc_stats(self, **kwargs) -> ResponseT:
1147 """
1148 Return an internal statistics report from the memory allocator.
1150 See: https://redis.io/commands/memory-malloc-stats
1151 """
1152 return self.execute_command("MEMORY MALLOC-STATS", **kwargs)
1154 def memory_usage(
1155 self, key: KeyT, samples: Optional[int] = None, **kwargs
1156 ) -> ResponseT:
1157 """
1158 Return the total memory usage for key, its value and associated
1159 administrative overheads.
1161 For nested data structures, ``samples`` is the number of elements to
1162 sample. If left unspecified, the server's default is 5. Use 0 to sample
1163 all elements.
1165 For more information, see https://redis.io/commands/memory-usage
1166 """
1167 args = []
1168 if isinstance(samples, int):
1169 args.extend([b"SAMPLES", samples])
1170 return self.execute_command("MEMORY USAGE", key, *args, **kwargs)
1172 def memory_purge(self, **kwargs) -> ResponseT:
1173 """
1174 Attempts to purge dirty pages for reclamation by allocator
1176 For more information, see https://redis.io/commands/memory-purge
1177 """
1178 return self.execute_command("MEMORY PURGE", **kwargs)
1180 def latency_histogram(self, *args):
1181 """
1182 This function throws a NotImplementedError since it is intentionally
1183 not supported.
1184 """
1185 raise NotImplementedError(
1186 "LATENCY HISTOGRAM is intentionally not implemented in the client."
1187 )
1189 def latency_history(self, event: str) -> ResponseT:
1190 """
1191 Returns the raw data of the ``event``'s latency spikes time series.
1193 For more information, see https://redis.io/commands/latency-history
1194 """
1195 return self.execute_command("LATENCY HISTORY", event)
1197 def latency_latest(self) -> ResponseT:
1198 """
1199 Reports the latest latency events logged.
1201 For more information, see https://redis.io/commands/latency-latest
1202 """
1203 return self.execute_command("LATENCY LATEST")
1205 def latency_reset(self, *events: str) -> ResponseT:
1206 """
1207 Resets the latency spikes time series of all, or only some, events.
1209 For more information, see https://redis.io/commands/latency-reset
1210 """
1211 return self.execute_command("LATENCY RESET", *events)
1213 def ping(self, **kwargs) -> Union[Awaitable[bool], bool]:
1214 """
1215 Ping the Redis server to test connectivity.
1217 Sends a PING command to the Redis server and returns True if the server
1218 responds with "PONG".
1220 This command is useful for:
1221 - Testing whether a connection is still alive
1222 - Verifying the server's ability to serve data
1224 For more information on the underlying ping command see https://redis.io/commands/ping
1225 """
1226 return self.execute_command("PING", **kwargs)
1228 def quit(self, **kwargs) -> ResponseT:
1229 """
1230 Ask the server to close the connection.
1232 For more information, see https://redis.io/commands/quit
1233 """
1234 return self.execute_command("QUIT", **kwargs)
1236 def replicaof(self, *args, **kwargs) -> ResponseT:
1237 """
1238 Update the replication settings of a redis replica, on the fly.
1240 Examples of valid arguments include:
1242 NO ONE (set no replication)
1243 host port (set to the host and port of a redis server)
1245 For more information, see https://redis.io/commands/replicaof
1246 """
1247 return self.execute_command("REPLICAOF", *args, **kwargs)
1249 def save(self, **kwargs) -> ResponseT:
1250 """
1251 Tell the Redis server to save its data to disk,
1252 blocking until the save is complete
1254 For more information, see https://redis.io/commands/save
1255 """
1256 return self.execute_command("SAVE", **kwargs)
1258 def shutdown(
1259 self,
1260 save: bool = False,
1261 nosave: bool = False,
1262 now: bool = False,
1263 force: bool = False,
1264 abort: bool = False,
1265 **kwargs,
1266 ) -> None:
1267 """Shutdown the Redis server. If Redis has persistence configured,
1268 data will be flushed before shutdown.
1269 It is possible to specify modifiers to alter the behavior of the command:
1270 ``save`` will force a DB saving operation even if no save points are configured.
1271 ``nosave`` will prevent a DB saving operation even if one or more save points
1272 are configured.
1273 ``now`` skips waiting for lagging replicas, i.e. it bypasses the first step in
1274 the shutdown sequence.
1275 ``force`` ignores any errors that would normally prevent the server from exiting
1276 ``abort`` cancels an ongoing shutdown and cannot be combined with other flags.
1278 For more information, see https://redis.io/commands/shutdown
1279 """
1280 if save and nosave:
1281 raise DataError("SHUTDOWN save and nosave cannot both be set")
1282 args = ["SHUTDOWN"]
1283 if save:
1284 args.append("SAVE")
1285 if nosave:
1286 args.append("NOSAVE")
1287 if now:
1288 args.append("NOW")
1289 if force:
1290 args.append("FORCE")
1291 if abort:
1292 args.append("ABORT")
1293 try:
1294 self.execute_command(*args, **kwargs)
1295 except ConnectionError:
1296 # a ConnectionError here is expected
1297 return
1298 raise RedisError("SHUTDOWN seems to have failed.")
1300 def slaveof(
1301 self, host: Optional[str] = None, port: Optional[int] = None, **kwargs
1302 ) -> ResponseT:
1303 """
1304 Set the server to be a replicated slave of the instance identified
1305 by the ``host`` and ``port``. If called without arguments, the
1306 instance is promoted to a master instead.
1308 For more information, see https://redis.io/commands/slaveof
1309 """
1310 if host is None and port is None:
1311 return self.execute_command("SLAVEOF", b"NO", b"ONE", **kwargs)
1312 return self.execute_command("SLAVEOF", host, port, **kwargs)
1314 def slowlog_get(self, num: Optional[int] = None, **kwargs) -> ResponseT:
1315 """
1316 Get the entries from the slowlog. If ``num`` is specified, get the
1317 most recent ``num`` items.
1319 For more information, see https://redis.io/commands/slowlog-get
1320 """
1321 from redis.client import NEVER_DECODE
1323 args = ["SLOWLOG GET"]
1324 if num is not None:
1325 args.append(num)
1326 decode_responses = self.get_connection_kwargs().get("decode_responses", False)
1327 if decode_responses is True:
1328 kwargs[NEVER_DECODE] = []
1329 return self.execute_command(*args, **kwargs)
1331 def slowlog_len(self, **kwargs) -> ResponseT:
1332 """
1333 Get the number of items in the slowlog
1335 For more information, see https://redis.io/commands/slowlog-len
1336 """
1337 return self.execute_command("SLOWLOG LEN", **kwargs)
1339 def slowlog_reset(self, **kwargs) -> ResponseT:
1340 """
1341 Remove all items in the slowlog
1343 For more information, see https://redis.io/commands/slowlog-reset
1344 """
1345 return self.execute_command("SLOWLOG RESET", **kwargs)
1347 def time(self, **kwargs) -> ResponseT:
1348 """
1349 Returns the server time as a 2-item tuple of ints:
1350 (seconds since epoch, microseconds into this second).
1352 For more information, see https://redis.io/commands/time
1353 """
1354 return self.execute_command("TIME", **kwargs)
1356 def wait(self, num_replicas: int, timeout: int, **kwargs) -> ResponseT:
1357 """
1358 Redis synchronous replication
1359 That returns the number of replicas that processed the query when
1360 we finally have at least ``num_replicas``, or when the ``timeout`` was
1361 reached.
1363 For more information, see https://redis.io/commands/wait
1364 """
1365 return self.execute_command("WAIT", num_replicas, timeout, **kwargs)
1367 def waitaof(
1368 self, num_local: int, num_replicas: int, timeout: int, **kwargs
1369 ) -> ResponseT:
1370 """
1371 This command blocks the current client until all previous write
1372 commands by that client are acknowledged as having been fsynced
1373 to the AOF of the local Redis and/or at least the specified number
1374 of replicas.
1376 For more information, see https://redis.io/commands/waitaof
1377 """
1378 return self.execute_command(
1379 "WAITAOF", num_local, num_replicas, timeout, **kwargs
1380 )
1382 def hello(self):
1383 """
1384 This function throws a NotImplementedError since it is intentionally
1385 not supported.
1386 """
1387 raise NotImplementedError(
1388 "HELLO is intentionally not implemented in the client."
1389 )
1391 def failover(self):
1392 """
1393 This function throws a NotImplementedError since it is intentionally
1394 not supported.
1395 """
1396 raise NotImplementedError(
1397 "FAILOVER is intentionally not implemented in the client."
1398 )
1401class AsyncManagementCommands(ManagementCommands):
1402 async def command_info(self, **kwargs) -> None:
1403 return super().command_info(**kwargs)
1405 async def debug_segfault(self, **kwargs) -> None:
1406 return super().debug_segfault(**kwargs)
1408 async def memory_doctor(self, **kwargs) -> None:
1409 return super().memory_doctor(**kwargs)
1411 async def memory_help(self, **kwargs) -> None:
1412 return super().memory_help(**kwargs)
1414 async def shutdown(
1415 self,
1416 save: bool = False,
1417 nosave: bool = False,
1418 now: bool = False,
1419 force: bool = False,
1420 abort: bool = False,
1421 **kwargs,
1422 ) -> None:
1423 """Shutdown the Redis server. If Redis has persistence configured,
1424 data will be flushed before shutdown. If the "save" option is set,
1425 a data flush will be attempted even if there is no persistence
1426 configured. If the "nosave" option is set, no data flush will be
1427 attempted. The "save" and "nosave" options cannot both be set.
1429 For more information, see https://redis.io/commands/shutdown
1430 """
1431 if save and nosave:
1432 raise DataError("SHUTDOWN save and nosave cannot both be set")
1433 args = ["SHUTDOWN"]
1434 if save:
1435 args.append("SAVE")
1436 if nosave:
1437 args.append("NOSAVE")
1438 if now:
1439 args.append("NOW")
1440 if force:
1441 args.append("FORCE")
1442 if abort:
1443 args.append("ABORT")
1444 try:
1445 await self.execute_command(*args, **kwargs)
1446 except ConnectionError:
1447 # a ConnectionError here is expected
1448 return
1449 raise RedisError("SHUTDOWN seems to have failed.")
1452class BitFieldOperation:
1453 """
1454 Command builder for BITFIELD commands.
1455 """
1457 def __init__(
1458 self,
1459 client: Union["redis.client.Redis", "redis.asyncio.client.Redis"],
1460 key: str,
1461 default_overflow: Optional[str] = None,
1462 ):
1463 self.client = client
1464 self.key = key
1465 self._default_overflow = default_overflow
1466 # for typing purposes, run the following in constructor and in reset()
1467 self.operations: list[tuple[EncodableT, ...]] = []
1468 self._last_overflow = "WRAP"
1469 self.reset()
1471 def reset(self):
1472 """
1473 Reset the state of the instance to when it was constructed
1474 """
1475 self.operations = []
1476 self._last_overflow = "WRAP"
1477 self.overflow(self._default_overflow or self._last_overflow)
1479 def overflow(self, overflow: str):
1480 """
1481 Update the overflow algorithm of successive INCRBY operations
1482 :param overflow: Overflow algorithm, one of WRAP, SAT, FAIL. See the
1483 Redis docs for descriptions of these algorithmsself.
1484 :returns: a :py:class:`BitFieldOperation` instance.
1485 """
1486 overflow = overflow.upper()
1487 if overflow != self._last_overflow:
1488 self._last_overflow = overflow
1489 self.operations.append(("OVERFLOW", overflow))
1490 return self
1492 def incrby(
1493 self,
1494 fmt: str,
1495 offset: BitfieldOffsetT,
1496 increment: int,
1497 overflow: Optional[str] = None,
1498 ):
1499 """
1500 Increment a bitfield by a given amount.
1501 :param fmt: format-string for the bitfield being updated, e.g. 'u8'
1502 for an unsigned 8-bit integer.
1503 :param offset: offset (in number of bits). If prefixed with a
1504 '#', this is an offset multiplier, e.g. given the arguments
1505 fmt='u8', offset='#2', the offset will be 16.
1506 :param int increment: value to increment the bitfield by.
1507 :param str overflow: overflow algorithm. Defaults to WRAP, but other
1508 acceptable values are SAT and FAIL. See the Redis docs for
1509 descriptions of these algorithms.
1510 :returns: a :py:class:`BitFieldOperation` instance.
1511 """
1512 if overflow is not None:
1513 self.overflow(overflow)
1515 self.operations.append(("INCRBY", fmt, offset, increment))
1516 return self
1518 def get(self, fmt: str, offset: BitfieldOffsetT):
1519 """
1520 Get the value of a given bitfield.
1521 :param fmt: format-string for the bitfield being read, e.g. 'u8' for
1522 an unsigned 8-bit integer.
1523 :param offset: offset (in number of bits). If prefixed with a
1524 '#', this is an offset multiplier, e.g. given the arguments
1525 fmt='u8', offset='#2', the offset will be 16.
1526 :returns: a :py:class:`BitFieldOperation` instance.
1527 """
1528 self.operations.append(("GET", fmt, offset))
1529 return self
1531 def set(self, fmt: str, offset: BitfieldOffsetT, value: int):
1532 """
1533 Set the value of a given bitfield.
1534 :param fmt: format-string for the bitfield being read, e.g. 'u8' for
1535 an unsigned 8-bit integer.
1536 :param offset: offset (in number of bits). If prefixed with a
1537 '#', this is an offset multiplier, e.g. given the arguments
1538 fmt='u8', offset='#2', the offset will be 16.
1539 :param int value: value to set at the given position.
1540 :returns: a :py:class:`BitFieldOperation` instance.
1541 """
1542 self.operations.append(("SET", fmt, offset, value))
1543 return self
1545 @property
1546 def command(self):
1547 cmd = ["BITFIELD", self.key]
1548 for ops in self.operations:
1549 cmd.extend(ops)
1550 return cmd
1552 def execute(self) -> ResponseT:
1553 """
1554 Execute the operation(s) in a single BITFIELD command. The return value
1555 is a list of values corresponding to each operation. If the client
1556 used to create this instance was a pipeline, the list of values
1557 will be present within the pipeline's execute.
1558 """
1559 command = self.command
1560 self.reset()
1561 return self.client.execute_command(*command)
1564class DataPersistOptions(Enum):
1565 # set the value for each provided key to each
1566 # provided value only if all do not already exist.
1567 NX = "NX"
1569 # set the value for each provided key to each
1570 # provided value only if all already exist.
1571 XX = "XX"
1574class BasicKeyCommands(CommandsProtocol):
1575 """
1576 Redis basic key-based commands
1577 """
1579 def append(self, key: KeyT, value: EncodableT) -> ResponseT:
1580 """
1581 Appends the string ``value`` to the value at ``key``. If ``key``
1582 doesn't already exist, create it with a value of ``value``.
1583 Returns the new length of the value at ``key``.
1585 For more information, see https://redis.io/commands/append
1586 """
1587 return self.execute_command("APPEND", key, value)
1589 def bitcount(
1590 self,
1591 key: KeyT,
1592 start: Optional[int] = None,
1593 end: Optional[int] = None,
1594 mode: Optional[str] = None,
1595 ) -> ResponseT:
1596 """
1597 Returns the count of set bits in the value of ``key``. Optional
1598 ``start`` and ``end`` parameters indicate which bytes to consider
1600 For more information, see https://redis.io/commands/bitcount
1601 """
1602 params = [key]
1603 if start is not None and end is not None:
1604 params.append(start)
1605 params.append(end)
1606 elif (start is not None and end is None) or (end is not None and start is None):
1607 raise DataError("Both start and end must be specified")
1608 if mode is not None:
1609 params.append(mode)
1610 return self.execute_command("BITCOUNT", *params, keys=[key])
1612 def bitfield(
1613 self: Union["redis.client.Redis", "redis.asyncio.client.Redis"],
1614 key: KeyT,
1615 default_overflow: Optional[str] = None,
1616 ) -> BitFieldOperation:
1617 """
1618 Return a BitFieldOperation instance to conveniently construct one or
1619 more bitfield operations on ``key``.
1621 For more information, see https://redis.io/commands/bitfield
1622 """
1623 return BitFieldOperation(self, key, default_overflow=default_overflow)
1625 def bitfield_ro(
1626 self: Union["redis.client.Redis", "redis.asyncio.client.Redis"],
1627 key: KeyT,
1628 encoding: str,
1629 offset: BitfieldOffsetT,
1630 items: Optional[list] = None,
1631 ) -> ResponseT:
1632 """
1633 Return an array of the specified bitfield values
1634 where the first value is found using ``encoding`` and ``offset``
1635 parameters and remaining values are result of corresponding
1636 encoding/offset pairs in optional list ``items``
1637 Read-only variant of the BITFIELD command.
1639 For more information, see https://redis.io/commands/bitfield_ro
1640 """
1641 params = [key, "GET", encoding, offset]
1643 items = items or []
1644 for encoding, offset in items:
1645 params.extend(["GET", encoding, offset])
1646 return self.execute_command("BITFIELD_RO", *params, keys=[key])
1648 def bitop(self, operation: str, dest: KeyT, *keys: KeyT) -> ResponseT:
1649 """
1650 Perform a bitwise operation using ``operation`` between ``keys`` and
1651 store the result in ``dest``.
1653 For more information, see https://redis.io/commands/bitop
1654 """
1655 return self.execute_command("BITOP", operation, dest, *keys)
1657 def bitpos(
1658 self,
1659 key: KeyT,
1660 bit: int,
1661 start: Optional[int] = None,
1662 end: Optional[int] = None,
1663 mode: Optional[str] = None,
1664 ) -> ResponseT:
1665 """
1666 Return the position of the first bit set to 1 or 0 in a string.
1667 ``start`` and ``end`` defines search range. The range is interpreted
1668 as a range of bytes and not a range of bits, so start=0 and end=2
1669 means to look at the first three bytes.
1671 For more information, see https://redis.io/commands/bitpos
1672 """
1673 if bit not in (0, 1):
1674 raise DataError("bit must be 0 or 1")
1675 params = [key, bit]
1677 start is not None and params.append(start)
1679 if start is not None and end is not None:
1680 params.append(end)
1681 elif start is None and end is not None:
1682 raise DataError("start argument is not set, when end is specified")
1684 if mode is not None:
1685 params.append(mode)
1686 return self.execute_command("BITPOS", *params, keys=[key])
1688 def copy(
1689 self,
1690 source: str,
1691 destination: str,
1692 destination_db: Optional[str] = None,
1693 replace: bool = False,
1694 ) -> ResponseT:
1695 """
1696 Copy the value stored in the ``source`` key to the ``destination`` key.
1698 ``destination_db`` an alternative destination database. By default,
1699 the ``destination`` key is created in the source Redis database.
1701 ``replace`` whether the ``destination`` key should be removed before
1702 copying the value to it. By default, the value is not copied if
1703 the ``destination`` key already exists.
1705 For more information, see https://redis.io/commands/copy
1706 """
1707 params = [source, destination]
1708 if destination_db is not None:
1709 params.extend(["DB", destination_db])
1710 if replace:
1711 params.append("REPLACE")
1712 return self.execute_command("COPY", *params)
1714 def decrby(self, name: KeyT, amount: int = 1) -> ResponseT:
1715 """
1716 Decrements the value of ``key`` by ``amount``. If no key exists,
1717 the value will be initialized as 0 - ``amount``
1719 For more information, see https://redis.io/commands/decrby
1720 """
1721 return self.execute_command("DECRBY", name, amount)
1723 decr = decrby
1725 def delete(self, *names: KeyT) -> ResponseT:
1726 """
1727 Delete one or more keys specified by ``names``
1728 """
1729 return self.execute_command("DEL", *names)
1731 def __delitem__(self, name: KeyT):
1732 self.delete(name)
1734 @experimental_method()
1735 def delex(
1736 self,
1737 name: KeyT,
1738 ifeq: Optional[Union[bytes, str]] = None,
1739 ifne: Optional[Union[bytes, str]] = None,
1740 ifdeq: Optional[str] = None, # hex digest
1741 ifdne: Optional[str] = None, # hex digest
1742 ) -> int:
1743 """
1744 Conditionally removes the specified key.
1746 Warning:
1747 **Experimental** since 7.1.
1748 This API may change or be removed without notice.
1749 The API may change based on feedback.
1751 Arguments:
1752 name: KeyT - the key to delete
1753 ifeq match-valu: Optional[Union[bytes, str]] - Delete the key only if its value is equal to match-value
1754 ifne match-value: Optional[Union[bytes, str]] - Delete the key only if its value is not equal to match-value
1755 ifdeq match-digest: Optional[str] - Delete the key only if the digest of its value is equal to match-digest
1756 ifdne match-digest: Optional[str] - Delete the key only if the digest of its value is not equal to match-digest
1758 Returns:
1759 int: 1 if the key was deleted, 0 otherwise.
1760 Raises:
1761 redis.exceptions.ResponseError: if key exists but is not a string
1762 and a condition is specified.
1763 ValueError: if more than one condition is provided.
1766 Requires Redis 8.4 or greater.
1767 For more information, see https://redis.io/commands/delex
1768 """
1769 conds = [x is not None for x in (ifeq, ifne, ifdeq, ifdne)]
1770 if sum(conds) > 1:
1771 raise ValueError("Only one of IFEQ/IFNE/IFDEQ/IFDNE may be specified")
1773 pieces = ["DELEX", name]
1774 if ifeq is not None:
1775 pieces += ["IFEQ", ifeq]
1776 elif ifne is not None:
1777 pieces += ["IFNE", ifne]
1778 elif ifdeq is not None:
1779 pieces += ["IFDEQ", ifdeq]
1780 elif ifdne is not None:
1781 pieces += ["IFDNE", ifdne]
1783 return self.execute_command(*pieces)
1785 def dump(self, name: KeyT) -> ResponseT:
1786 """
1787 Return a serialized version of the value stored at the specified key.
1788 If key does not exist a nil bulk reply is returned.
1790 For more information, see https://redis.io/commands/dump
1791 """
1792 from redis.client import NEVER_DECODE
1794 options = {}
1795 options[NEVER_DECODE] = []
1796 return self.execute_command("DUMP", name, **options)
1798 def exists(self, *names: KeyT) -> ResponseT:
1799 """
1800 Returns the number of ``names`` that exist
1802 For more information, see https://redis.io/commands/exists
1803 """
1804 return self.execute_command("EXISTS", *names, keys=names)
1806 __contains__ = exists
1808 def expire(
1809 self,
1810 name: KeyT,
1811 time: ExpiryT,
1812 nx: bool = False,
1813 xx: bool = False,
1814 gt: bool = False,
1815 lt: bool = False,
1816 ) -> ResponseT:
1817 """
1818 Set an expire flag on key ``name`` for ``time`` seconds with given
1819 ``option``. ``time`` can be represented by an integer or a Python timedelta
1820 object.
1822 Valid options are:
1823 NX -> Set expiry only when the key has no expiry
1824 XX -> Set expiry only when the key has an existing expiry
1825 GT -> Set expiry only when the new expiry is greater than current one
1826 LT -> Set expiry only when the new expiry is less than current one
1828 For more information, see https://redis.io/commands/expire
1829 """
1830 if isinstance(time, datetime.timedelta):
1831 time = int(time.total_seconds())
1833 exp_option = list()
1834 if nx:
1835 exp_option.append("NX")
1836 if xx:
1837 exp_option.append("XX")
1838 if gt:
1839 exp_option.append("GT")
1840 if lt:
1841 exp_option.append("LT")
1843 return self.execute_command("EXPIRE", name, time, *exp_option)
1845 def expireat(
1846 self,
1847 name: KeyT,
1848 when: AbsExpiryT,
1849 nx: bool = False,
1850 xx: bool = False,
1851 gt: bool = False,
1852 lt: bool = False,
1853 ) -> ResponseT:
1854 """
1855 Set an expire flag on key ``name`` with given ``option``. ``when``
1856 can be represented as an integer indicating unix time or a Python
1857 datetime object.
1859 Valid options are:
1860 -> NX -- Set expiry only when the key has no expiry
1861 -> XX -- Set expiry only when the key has an existing expiry
1862 -> GT -- Set expiry only when the new expiry is greater than current one
1863 -> LT -- Set expiry only when the new expiry is less than current one
1865 For more information, see https://redis.io/commands/expireat
1866 """
1867 if isinstance(when, datetime.datetime):
1868 when = int(when.timestamp())
1870 exp_option = list()
1871 if nx:
1872 exp_option.append("NX")
1873 if xx:
1874 exp_option.append("XX")
1875 if gt:
1876 exp_option.append("GT")
1877 if lt:
1878 exp_option.append("LT")
1880 return self.execute_command("EXPIREAT", name, when, *exp_option)
1882 def expiretime(self, key: str) -> int:
1883 """
1884 Returns the absolute Unix timestamp (since January 1, 1970) in seconds
1885 at which the given key will expire.
1887 For more information, see https://redis.io/commands/expiretime
1888 """
1889 return self.execute_command("EXPIRETIME", key)
1891 @experimental_method()
1892 def digest(self, name: KeyT) -> Optional[str]:
1893 """
1894 Return the digest of the value stored at the specified key.
1896 Warning:
1897 **Experimental** since 7.1.
1898 This API may change or be removed without notice.
1899 The API may change based on feedback.
1901 Arguments:
1902 - name: KeyT - the key to get the digest of
1904 Returns:
1905 - None if the key does not exist
1906 - (bulk string) the XXH3 digest of the value as a hex string
1907 Raises:
1908 - ResponseError if key exists but is not a string
1911 Requires Redis 8.4 or greater.
1912 For more information, see https://redis.io/commands/digest
1913 """
1914 # Bulk string response is already handled (bytes/str based on decode_responses)
1915 return self.execute_command("DIGEST", name)
1917 def get(self, name: KeyT) -> ResponseT:
1918 """
1919 Return the value at key ``name``, or None if the key doesn't exist
1921 For more information, see https://redis.io/commands/get
1922 """
1923 return self.execute_command("GET", name, keys=[name])
1925 def getdel(self, name: KeyT) -> ResponseT:
1926 """
1927 Get the value at key ``name`` and delete the key. This command
1928 is similar to GET, except for the fact that it also deletes
1929 the key on success (if and only if the key's value type
1930 is a string).
1932 For more information, see https://redis.io/commands/getdel
1933 """
1934 return self.execute_command("GETDEL", name)
1936 def getex(
1937 self,
1938 name: KeyT,
1939 ex: Optional[ExpiryT] = None,
1940 px: Optional[ExpiryT] = None,
1941 exat: Optional[AbsExpiryT] = None,
1942 pxat: Optional[AbsExpiryT] = None,
1943 persist: bool = False,
1944 ) -> ResponseT:
1945 """
1946 Get the value of key and optionally set its expiration.
1947 GETEX is similar to GET, but is a write command with
1948 additional options. All time parameters can be given as
1949 datetime.timedelta or integers.
1951 ``ex`` sets an expire flag on key ``name`` for ``ex`` seconds.
1953 ``px`` sets an expire flag on key ``name`` for ``px`` milliseconds.
1955 ``exat`` sets an expire flag on key ``name`` for ``ex`` seconds,
1956 specified in unix time.
1958 ``pxat`` sets an expire flag on key ``name`` for ``ex`` milliseconds,
1959 specified in unix time.
1961 ``persist`` remove the time to live associated with ``name``.
1963 For more information, see https://redis.io/commands/getex
1964 """
1965 if not at_most_one_value_set((ex, px, exat, pxat, persist)):
1966 raise DataError(
1967 "``ex``, ``px``, ``exat``, ``pxat``, "
1968 "and ``persist`` are mutually exclusive."
1969 )
1971 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat)
1973 if persist:
1974 exp_options.append("PERSIST")
1976 return self.execute_command("GETEX", name, *exp_options)
1978 def __getitem__(self, name: KeyT):
1979 """
1980 Return the value at key ``name``, raises a KeyError if the key
1981 doesn't exist.
1982 """
1983 value = self.get(name)
1984 if value is not None:
1985 return value
1986 raise KeyError(name)
1988 def getbit(self, name: KeyT, offset: int) -> ResponseT:
1989 """
1990 Returns an integer indicating the value of ``offset`` in ``name``
1992 For more information, see https://redis.io/commands/getbit
1993 """
1994 return self.execute_command("GETBIT", name, offset, keys=[name])
1996 def getrange(self, key: KeyT, start: int, end: int) -> ResponseT:
1997 """
1998 Returns the substring of the string value stored at ``key``,
1999 determined by the offsets ``start`` and ``end`` (both are inclusive)
2001 For more information, see https://redis.io/commands/getrange
2002 """
2003 return self.execute_command("GETRANGE", key, start, end, keys=[key])
2005 def getset(self, name: KeyT, value: EncodableT) -> ResponseT:
2006 """
2007 Sets the value at key ``name`` to ``value``
2008 and returns the old value at key ``name`` atomically.
2010 As per Redis 6.2, GETSET is considered deprecated.
2011 Please use SET with GET parameter in new code.
2013 For more information, see https://redis.io/commands/getset
2014 """
2015 return self.execute_command("GETSET", name, value)
2017 def incrby(self, name: KeyT, amount: int = 1) -> ResponseT:
2018 """
2019 Increments the value of ``key`` by ``amount``. If no key exists,
2020 the value will be initialized as ``amount``
2022 For more information, see https://redis.io/commands/incrby
2023 """
2024 return self.execute_command("INCRBY", name, amount)
2026 incr = incrby
2028 def incrbyfloat(self, name: KeyT, amount: float = 1.0) -> ResponseT:
2029 """
2030 Increments the value at key ``name`` by floating ``amount``.
2031 If no key exists, the value will be initialized as ``amount``
2033 For more information, see https://redis.io/commands/incrbyfloat
2034 """
2035 return self.execute_command("INCRBYFLOAT", name, amount)
2037 def keys(self, pattern: PatternT = "*", **kwargs) -> ResponseT:
2038 """
2039 Returns a list of keys matching ``pattern``
2041 For more information, see https://redis.io/commands/keys
2042 """
2043 return self.execute_command("KEYS", pattern, **kwargs)
2045 def lmove(
2046 self, first_list: str, second_list: str, src: str = "LEFT", dest: str = "RIGHT"
2047 ) -> ResponseT:
2048 """
2049 Atomically returns and removes the first/last element of a list,
2050 pushing it as the first/last element on the destination list.
2051 Returns the element being popped and pushed.
2053 For more information, see https://redis.io/commands/lmove
2054 """
2055 params = [first_list, second_list, src, dest]
2056 return self.execute_command("LMOVE", *params)
2058 def blmove(
2059 self,
2060 first_list: str,
2061 second_list: str,
2062 timeout: int,
2063 src: str = "LEFT",
2064 dest: str = "RIGHT",
2065 ) -> ResponseT:
2066 """
2067 Blocking version of lmove.
2069 For more information, see https://redis.io/commands/blmove
2070 """
2071 params = [first_list, second_list, src, dest, timeout]
2072 return self.execute_command("BLMOVE", *params)
2074 def mget(self, keys: KeysT, *args: EncodableT) -> ResponseT:
2075 """
2076 Returns a list of values ordered identically to ``keys``
2078 ** Important ** When this method is used with Cluster clients, all keys
2079 must be in the same hash slot, otherwise a RedisClusterException
2080 will be raised.
2082 For more information, see https://redis.io/commands/mget
2083 """
2084 from redis.client import EMPTY_RESPONSE
2086 args = list_or_args(keys, args)
2087 options = {}
2088 if not args:
2089 options[EMPTY_RESPONSE] = []
2090 options["keys"] = args
2091 return self.execute_command("MGET", *args, **options)
2093 def mset(self, mapping: Mapping[AnyKeyT, EncodableT]) -> ResponseT:
2094 """
2095 Sets key/values based on a mapping. Mapping is a dictionary of
2096 key/value pairs. Both keys and values should be strings or types that
2097 can be cast to a string via str().
2099 ** Important ** When this method is used with Cluster clients, all keys
2100 must be in the same hash slot, otherwise a RedisClusterException
2101 will be raised.
2103 For more information, see https://redis.io/commands/mset
2104 """
2105 items = []
2106 for pair in mapping.items():
2107 items.extend(pair)
2108 return self.execute_command("MSET", *items)
2110 def msetex(
2111 self,
2112 mapping: Mapping[AnyKeyT, EncodableT],
2113 data_persist_option: Optional[DataPersistOptions] = None,
2114 ex: Optional[ExpiryT] = None,
2115 px: Optional[ExpiryT] = None,
2116 exat: Optional[AbsExpiryT] = None,
2117 pxat: Optional[AbsExpiryT] = None,
2118 keepttl: bool = False,
2119 ) -> Union[Awaitable[int], int]:
2120 """
2121 Sets key/values based on the provided ``mapping`` items.
2123 ** Important ** When this method is used with Cluster clients, all keys
2124 must be in the same hash slot, otherwise a RedisClusterException
2125 will be raised.
2127 ``mapping`` accepts a dict of key/value pairs that will be added to the database.
2129 ``data_persist_option`` can be set to ``NX`` or ``XX`` to control the
2130 behavior of the command.
2131 ``NX`` will set the value for each provided key to each
2132 provided value only if all do not already exist.
2133 ``XX`` will set the value for each provided key to each
2134 provided value only if all already exist.
2136 ``ex`` sets an expire flag on the keys in ``mapping`` for ``ex`` seconds.
2138 ``px`` sets an expire flag on the keys in ``mapping`` for ``px`` milliseconds.
2140 ``exat`` sets an expire flag on the keys in ``mapping`` for ``exat`` seconds,
2141 specified in unix time.
2143 ``pxat`` sets an expire flag on the keys in ``mapping`` for ``pxat`` milliseconds,
2144 specified in unix time.
2146 ``keepttl`` if True, retain the time to live associated with the keys.
2148 Returns the number of fields that were added.
2150 Available since Redis 8.4
2151 For more information, see https://redis.io/commands/msetex
2152 """
2153 if not at_most_one_value_set((ex, px, exat, pxat, keepttl)):
2154 raise DataError(
2155 "``ex``, ``px``, ``exat``, ``pxat``, "
2156 "and ``keepttl`` are mutually exclusive."
2157 )
2159 exp_options: list[EncodableT] = []
2160 if data_persist_option:
2161 exp_options.append(data_persist_option.value)
2163 exp_options.extend(extract_expire_flags(ex, px, exat, pxat))
2165 if keepttl:
2166 exp_options.append("KEEPTTL")
2168 pieces = ["MSETEX", len(mapping)]
2170 for pair in mapping.items():
2171 pieces.extend(pair)
2173 return self.execute_command(*pieces, *exp_options)
2175 def msetnx(self, mapping: Mapping[AnyKeyT, EncodableT]) -> ResponseT:
2176 """
2177 Sets key/values based on a mapping if none of the keys are already set.
2178 Mapping is a dictionary of key/value pairs. Both keys and values
2179 should be strings or types that can be cast to a string via str().
2180 Returns a boolean indicating if the operation was successful.
2182 ** Important ** When this method is used with Cluster clients, all keys
2183 must be in the same hash slot, otherwise a RedisClusterException
2184 will be raised.
2186 For more information, see https://redis.io/commands/msetnx
2187 """
2188 items = []
2189 for pair in mapping.items():
2190 items.extend(pair)
2191 return self.execute_command("MSETNX", *items)
2193 def move(self, name: KeyT, db: int) -> ResponseT:
2194 """
2195 Moves the key ``name`` to a different Redis database ``db``
2197 For more information, see https://redis.io/commands/move
2198 """
2199 return self.execute_command("MOVE", name, db)
2201 def persist(self, name: KeyT) -> ResponseT:
2202 """
2203 Removes an expiration on ``name``
2205 For more information, see https://redis.io/commands/persist
2206 """
2207 return self.execute_command("PERSIST", name)
2209 def pexpire(
2210 self,
2211 name: KeyT,
2212 time: ExpiryT,
2213 nx: bool = False,
2214 xx: bool = False,
2215 gt: bool = False,
2216 lt: bool = False,
2217 ) -> ResponseT:
2218 """
2219 Set an expire flag on key ``name`` for ``time`` milliseconds
2220 with given ``option``. ``time`` can be represented by an
2221 integer or a Python timedelta object.
2223 Valid options are:
2224 NX -> Set expiry only when the key has no expiry
2225 XX -> Set expiry only when the key has an existing expiry
2226 GT -> Set expiry only when the new expiry is greater than current one
2227 LT -> Set expiry only when the new expiry is less than current one
2229 For more information, see https://redis.io/commands/pexpire
2230 """
2231 if isinstance(time, datetime.timedelta):
2232 time = int(time.total_seconds() * 1000)
2234 exp_option = list()
2235 if nx:
2236 exp_option.append("NX")
2237 if xx:
2238 exp_option.append("XX")
2239 if gt:
2240 exp_option.append("GT")
2241 if lt:
2242 exp_option.append("LT")
2243 return self.execute_command("PEXPIRE", name, time, *exp_option)
2245 def pexpireat(
2246 self,
2247 name: KeyT,
2248 when: AbsExpiryT,
2249 nx: bool = False,
2250 xx: bool = False,
2251 gt: bool = False,
2252 lt: bool = False,
2253 ) -> ResponseT:
2254 """
2255 Set an expire flag on key ``name`` with given ``option``. ``when``
2256 can be represented as an integer representing unix time in
2257 milliseconds (unix time * 1000) or a Python datetime object.
2259 Valid options are:
2260 NX -> Set expiry only when the key has no expiry
2261 XX -> Set expiry only when the key has an existing expiry
2262 GT -> Set expiry only when the new expiry is greater than current one
2263 LT -> Set expiry only when the new expiry is less than current one
2265 For more information, see https://redis.io/commands/pexpireat
2266 """
2267 if isinstance(when, datetime.datetime):
2268 when = int(when.timestamp() * 1000)
2269 exp_option = list()
2270 if nx:
2271 exp_option.append("NX")
2272 if xx:
2273 exp_option.append("XX")
2274 if gt:
2275 exp_option.append("GT")
2276 if lt:
2277 exp_option.append("LT")
2278 return self.execute_command("PEXPIREAT", name, when, *exp_option)
2280 def pexpiretime(self, key: str) -> int:
2281 """
2282 Returns the absolute Unix timestamp (since January 1, 1970) in milliseconds
2283 at which the given key will expire.
2285 For more information, see https://redis.io/commands/pexpiretime
2286 """
2287 return self.execute_command("PEXPIRETIME", key)
2289 def psetex(self, name: KeyT, time_ms: ExpiryT, value: EncodableT):
2290 """
2291 Set the value of key ``name`` to ``value`` that expires in ``time_ms``
2292 milliseconds. ``time_ms`` can be represented by an integer or a Python
2293 timedelta object
2295 For more information, see https://redis.io/commands/psetex
2296 """
2297 if isinstance(time_ms, datetime.timedelta):
2298 time_ms = int(time_ms.total_seconds() * 1000)
2299 return self.execute_command("PSETEX", name, time_ms, value)
2301 def pttl(self, name: KeyT) -> ResponseT:
2302 """
2303 Returns the number of milliseconds until the key ``name`` will expire
2305 For more information, see https://redis.io/commands/pttl
2306 """
2307 return self.execute_command("PTTL", name)
2309 def hrandfield(
2310 self, key: str, count: Optional[int] = None, withvalues: bool = False
2311 ) -> ResponseT:
2312 """
2313 Return a random field from the hash value stored at key.
2315 count: if the argument is positive, return an array of distinct fields.
2316 If called with a negative count, the behavior changes and the command
2317 is allowed to return the same field multiple times. In this case,
2318 the number of returned fields is the absolute value of the
2319 specified count.
2320 withvalues: The optional WITHVALUES modifier changes the reply so it
2321 includes the respective values of the randomly selected hash fields.
2323 For more information, see https://redis.io/commands/hrandfield
2324 """
2325 params = []
2326 if count is not None:
2327 params.append(count)
2328 if withvalues:
2329 params.append("WITHVALUES")
2331 return self.execute_command("HRANDFIELD", key, *params)
2333 def randomkey(self, **kwargs) -> ResponseT:
2334 """
2335 Returns the name of a random key
2337 For more information, see https://redis.io/commands/randomkey
2338 """
2339 return self.execute_command("RANDOMKEY", **kwargs)
2341 def rename(self, src: KeyT, dst: KeyT) -> ResponseT:
2342 """
2343 Rename key ``src`` to ``dst``
2345 For more information, see https://redis.io/commands/rename
2346 """
2347 return self.execute_command("RENAME", src, dst)
2349 def renamenx(self, src: KeyT, dst: KeyT):
2350 """
2351 Rename key ``src`` to ``dst`` if ``dst`` doesn't already exist
2353 For more information, see https://redis.io/commands/renamenx
2354 """
2355 return self.execute_command("RENAMENX", src, dst)
2357 def restore(
2358 self,
2359 name: KeyT,
2360 ttl: float,
2361 value: EncodableT,
2362 replace: bool = False,
2363 absttl: bool = False,
2364 idletime: Optional[int] = None,
2365 frequency: Optional[int] = None,
2366 ) -> ResponseT:
2367 """
2368 Create a key using the provided serialized value, previously obtained
2369 using DUMP.
2371 ``replace`` allows an existing key on ``name`` to be overridden. If
2372 it's not specified an error is raised on collision.
2374 ``absttl`` if True, specified ``ttl`` should represent an absolute Unix
2375 timestamp in milliseconds in which the key will expire. (Redis 5.0 or
2376 greater).
2378 ``idletime`` Used for eviction, this is the number of seconds the
2379 key must be idle, prior to execution.
2381 ``frequency`` Used for eviction, this is the frequency counter of
2382 the object stored at the key, prior to execution.
2384 For more information, see https://redis.io/commands/restore
2385 """
2386 params = [name, ttl, value]
2387 if replace:
2388 params.append("REPLACE")
2389 if absttl:
2390 params.append("ABSTTL")
2391 if idletime is not None:
2392 params.append("IDLETIME")
2393 try:
2394 params.append(int(idletime))
2395 except ValueError:
2396 raise DataError("idletimemust be an integer")
2398 if frequency is not None:
2399 params.append("FREQ")
2400 try:
2401 params.append(int(frequency))
2402 except ValueError:
2403 raise DataError("frequency must be an integer")
2405 return self.execute_command("RESTORE", *params)
2407 @experimental_args(["ifeq", "ifne", "ifdeq", "ifdne"])
2408 def set(
2409 self,
2410 name: KeyT,
2411 value: EncodableT,
2412 ex: Optional[ExpiryT] = None,
2413 px: Optional[ExpiryT] = None,
2414 nx: bool = False,
2415 xx: bool = False,
2416 keepttl: bool = False,
2417 get: bool = False,
2418 exat: Optional[AbsExpiryT] = None,
2419 pxat: Optional[AbsExpiryT] = None,
2420 ifeq: Optional[Union[bytes, str]] = None,
2421 ifne: Optional[Union[bytes, str]] = None,
2422 ifdeq: Optional[str] = None, # hex digest of current value
2423 ifdne: Optional[str] = None, # hex digest of current value
2424 ) -> ResponseT:
2425 """
2426 Set the value at key ``name`` to ``value``
2428 Warning:
2429 **Experimental** since 7.1.
2430 The usage of the arguments ``ifeq``, ``ifne``, ``ifdeq``, and ``ifdne``
2431 is experimental. The API or returned results when those parameters are used
2432 may change based on feedback.
2434 ``ex`` sets an expire flag on key ``name`` for ``ex`` seconds.
2436 ``px`` sets an expire flag on key ``name`` for ``px`` milliseconds.
2438 ``nx`` if set to True, set the value at key ``name`` to ``value`` only
2439 if it does not exist.
2441 ``xx`` if set to True, set the value at key ``name`` to ``value`` only
2442 if it already exists.
2444 ``keepttl`` if True, retain the time to live associated with the key.
2445 (Available since Redis 6.0)
2447 ``get`` if True, set the value at key ``name`` to ``value`` and return
2448 the old value stored at key, or None if the key did not exist.
2449 (Available since Redis 6.2)
2451 ``exat`` sets an expire flag on key ``name`` for ``ex`` seconds,
2452 specified in unix time.
2454 ``pxat`` sets an expire flag on key ``name`` for ``ex`` milliseconds,
2455 specified in unix time.
2457 ``ifeq`` set the value at key ``name`` to ``value`` only if the current
2458 value exactly matches the argument.
2459 If key doesn’t exist - it won’t be created.
2460 (Requires Redis 8.4 or greater)
2462 ``ifne`` set the value at key ``name`` to ``value`` only if the current
2463 value does not exactly match the argument.
2464 If key doesn’t exist - it will be created.
2465 (Requires Redis 8.4 or greater)
2467 ``ifdeq`` set the value at key ``name`` to ``value`` only if the current
2468 value XXH3 hex digest exactly matches the argument.
2469 If key doesn’t exist - it won’t be created.
2470 (Requires Redis 8.4 or greater)
2472 ``ifdne`` set the value at key ``name`` to ``value`` only if the current
2473 value XXH3 hex digest does not exactly match the argument.
2474 If key doesn’t exist - it will be created.
2475 (Requires Redis 8.4 or greater)
2477 For more information, see https://redis.io/commands/set
2478 """
2480 if not at_most_one_value_set((ex, px, exat, pxat, keepttl)):
2481 raise DataError(
2482 "``ex``, ``px``, ``exat``, ``pxat``, "
2483 "and ``keepttl`` are mutually exclusive."
2484 )
2486 # Enforce mutual exclusivity among all conditional switches.
2487 if not at_most_one_value_set((nx, xx, ifeq, ifne, ifdeq, ifdne)):
2488 raise DataError(
2489 "``nx``, ``xx``, ``ifeq``, ``ifne``, ``ifdeq``, ``ifdne`` are mutually exclusive."
2490 )
2492 pieces: list[EncodableT] = [name, value]
2493 options = {}
2495 # Conditional modifier (exactly one at most)
2496 if nx:
2497 pieces.append("NX")
2498 elif xx:
2499 pieces.append("XX")
2500 elif ifeq is not None:
2501 pieces.extend(("IFEQ", ifeq))
2502 elif ifne is not None:
2503 pieces.extend(("IFNE", ifne))
2504 elif ifdeq is not None:
2505 pieces.extend(("IFDEQ", ifdeq))
2506 elif ifdne is not None:
2507 pieces.extend(("IFDNE", ifdne))
2509 if get:
2510 pieces.append("GET")
2511 options["get"] = True
2513 pieces.extend(extract_expire_flags(ex, px, exat, pxat))
2515 if keepttl:
2516 pieces.append("KEEPTTL")
2518 return self.execute_command("SET", *pieces, **options)
2520 def __setitem__(self, name: KeyT, value: EncodableT):
2521 self.set(name, value)
2523 def setbit(self, name: KeyT, offset: int, value: int) -> ResponseT:
2524 """
2525 Flag the ``offset`` in ``name`` as ``value``. Returns an integer
2526 indicating the previous value of ``offset``.
2528 For more information, see https://redis.io/commands/setbit
2529 """
2530 value = value and 1 or 0
2531 return self.execute_command("SETBIT", name, offset, value)
2533 def setex(self, name: KeyT, time: ExpiryT, value: EncodableT) -> ResponseT:
2534 """
2535 Set the value of key ``name`` to ``value`` that expires in ``time``
2536 seconds. ``time`` can be represented by an integer or a Python
2537 timedelta object.
2539 For more information, see https://redis.io/commands/setex
2540 """
2541 if isinstance(time, datetime.timedelta):
2542 time = int(time.total_seconds())
2543 return self.execute_command("SETEX", name, time, value)
2545 def setnx(self, name: KeyT, value: EncodableT) -> ResponseT:
2546 """
2547 Set the value of key ``name`` to ``value`` if key doesn't exist
2549 For more information, see https://redis.io/commands/setnx
2550 """
2551 return self.execute_command("SETNX", name, value)
2553 def setrange(self, name: KeyT, offset: int, value: EncodableT) -> ResponseT:
2554 """
2555 Overwrite bytes in the value of ``name`` starting at ``offset`` with
2556 ``value``. If ``offset`` plus the length of ``value`` exceeds the
2557 length of the original value, the new value will be larger than before.
2558 If ``offset`` exceeds the length of the original value, null bytes
2559 will be used to pad between the end of the previous value and the start
2560 of what's being injected.
2562 Returns the length of the new string.
2564 For more information, see https://redis.io/commands/setrange
2565 """
2566 return self.execute_command("SETRANGE", name, offset, value)
2568 def stralgo(
2569 self,
2570 algo: Literal["LCS"],
2571 value1: KeyT,
2572 value2: KeyT,
2573 specific_argument: Union[Literal["strings"], Literal["keys"]] = "strings",
2574 len: bool = False,
2575 idx: bool = False,
2576 minmatchlen: Optional[int] = None,
2577 withmatchlen: bool = False,
2578 **kwargs,
2579 ) -> ResponseT:
2580 """
2581 Implements complex algorithms that operate on strings.
2582 Right now the only algorithm implemented is the LCS algorithm
2583 (longest common substring). However new algorithms could be
2584 implemented in the future.
2586 ``algo`` Right now must be LCS
2587 ``value1`` and ``value2`` Can be two strings or two keys
2588 ``specific_argument`` Specifying if the arguments to the algorithm
2589 will be keys or strings. strings is the default.
2590 ``len`` Returns just the len of the match.
2591 ``idx`` Returns the match positions in each string.
2592 ``minmatchlen`` Restrict the list of matches to the ones of a given
2593 minimal length. Can be provided only when ``idx`` set to True.
2594 ``withmatchlen`` Returns the matches with the len of the match.
2595 Can be provided only when ``idx`` set to True.
2597 For more information, see https://redis.io/commands/stralgo
2598 """
2599 # check validity
2600 supported_algo = ["LCS"]
2601 if algo not in supported_algo:
2602 supported_algos_str = ", ".join(supported_algo)
2603 raise DataError(f"The supported algorithms are: {supported_algos_str}")
2604 if specific_argument not in ["keys", "strings"]:
2605 raise DataError("specific_argument can be only keys or strings")
2606 if len and idx:
2607 raise DataError("len and idx cannot be provided together.")
2609 pieces: list[EncodableT] = [algo, specific_argument.upper(), value1, value2]
2610 if len:
2611 pieces.append(b"LEN")
2612 if idx:
2613 pieces.append(b"IDX")
2614 try:
2615 int(minmatchlen)
2616 pieces.extend([b"MINMATCHLEN", minmatchlen])
2617 except TypeError:
2618 pass
2619 if withmatchlen:
2620 pieces.append(b"WITHMATCHLEN")
2622 return self.execute_command(
2623 "STRALGO",
2624 *pieces,
2625 len=len,
2626 idx=idx,
2627 minmatchlen=minmatchlen,
2628 withmatchlen=withmatchlen,
2629 **kwargs,
2630 )
2632 def strlen(self, name: KeyT) -> ResponseT:
2633 """
2634 Return the number of bytes stored in the value of ``name``
2636 For more information, see https://redis.io/commands/strlen
2637 """
2638 return self.execute_command("STRLEN", name, keys=[name])
2640 def substr(self, name: KeyT, start: int, end: int = -1) -> ResponseT:
2641 """
2642 Return a substring of the string at key ``name``. ``start`` and ``end``
2643 are 0-based integers specifying the portion of the string to return.
2644 """
2645 return self.execute_command("SUBSTR", name, start, end, keys=[name])
2647 def touch(self, *args: KeyT) -> ResponseT:
2648 """
2649 Alters the last access time of a key(s) ``*args``. A key is ignored
2650 if it does not exist.
2652 For more information, see https://redis.io/commands/touch
2653 """
2654 return self.execute_command("TOUCH", *args)
2656 def ttl(self, name: KeyT) -> ResponseT:
2657 """
2658 Returns the number of seconds until the key ``name`` will expire
2660 For more information, see https://redis.io/commands/ttl
2661 """
2662 return self.execute_command("TTL", name)
2664 def type(self, name: KeyT) -> ResponseT:
2665 """
2666 Returns the type of key ``name``
2668 For more information, see https://redis.io/commands/type
2669 """
2670 return self.execute_command("TYPE", name, keys=[name])
2672 def watch(self, *names: KeyT) -> None:
2673 """
2674 Watches the values at keys ``names``, or None if the key doesn't exist
2676 For more information, see https://redis.io/commands/watch
2677 """
2678 warnings.warn(DeprecationWarning("Call WATCH from a Pipeline object"))
2680 def unwatch(self) -> None:
2681 """
2682 Unwatches all previously watched keys for a transaction
2684 For more information, see https://redis.io/commands/unwatch
2685 """
2686 warnings.warn(DeprecationWarning("Call UNWATCH from a Pipeline object"))
2688 def unlink(self, *names: KeyT) -> ResponseT:
2689 """
2690 Unlink one or more keys specified by ``names``
2692 For more information, see https://redis.io/commands/unlink
2693 """
2694 return self.execute_command("UNLINK", *names)
2696 def lcs(
2697 self,
2698 key1: str,
2699 key2: str,
2700 len: Optional[bool] = False,
2701 idx: Optional[bool] = False,
2702 minmatchlen: Optional[int] = 0,
2703 withmatchlen: Optional[bool] = False,
2704 ) -> Union[str, int, list]:
2705 """
2706 Find the longest common subsequence between ``key1`` and ``key2``.
2707 If ``len`` is true the length of the match will will be returned.
2708 If ``idx`` is true the match position in each strings will be returned.
2709 ``minmatchlen`` restrict the list of matches to the ones of
2710 the given ``minmatchlen``.
2711 If ``withmatchlen`` the length of the match also will be returned.
2712 For more information, see https://redis.io/commands/lcs
2713 """
2714 pieces = [key1, key2]
2715 if len:
2716 pieces.append("LEN")
2717 if idx:
2718 pieces.append("IDX")
2719 if minmatchlen != 0:
2720 pieces.extend(["MINMATCHLEN", minmatchlen])
2721 if withmatchlen:
2722 pieces.append("WITHMATCHLEN")
2723 return self.execute_command("LCS", *pieces, keys=[key1, key2])
2726class AsyncBasicKeyCommands(BasicKeyCommands):
2727 def __delitem__(self, name: KeyT):
2728 raise TypeError("Async Redis client does not support class deletion")
2730 def __contains__(self, name: KeyT):
2731 raise TypeError("Async Redis client does not support class inclusion")
2733 def __getitem__(self, name: KeyT):
2734 raise TypeError("Async Redis client does not support class retrieval")
2736 def __setitem__(self, name: KeyT, value: EncodableT):
2737 raise TypeError("Async Redis client does not support class assignment")
2739 async def watch(self, *names: KeyT) -> None:
2740 return super().watch(*names)
2742 async def unwatch(self) -> None:
2743 return super().unwatch()
2746class ListCommands(CommandsProtocol):
2747 """
2748 Redis commands for List data type.
2749 see: https://redis.io/topics/data-types#lists
2750 """
2752 def blpop(
2753 self, keys: List, timeout: Optional[Number] = 0
2754 ) -> Union[Awaitable[list], list]:
2755 """
2756 LPOP a value off of the first non-empty list
2757 named in the ``keys`` list.
2759 If none of the lists in ``keys`` has a value to LPOP, then block
2760 for ``timeout`` seconds, or until a value gets pushed on to one
2761 of the lists.
2763 If timeout is 0, then block indefinitely.
2765 For more information, see https://redis.io/commands/blpop
2766 """
2767 if timeout is None:
2768 timeout = 0
2769 keys = list_or_args(keys, None)
2770 keys.append(timeout)
2771 return self.execute_command("BLPOP", *keys)
2773 def brpop(
2774 self, keys: List, timeout: Optional[Number] = 0
2775 ) -> Union[Awaitable[list], list]:
2776 """
2777 RPOP a value off of the first non-empty list
2778 named in the ``keys`` list.
2780 If none of the lists in ``keys`` has a value to RPOP, then block
2781 for ``timeout`` seconds, or until a value gets pushed on to one
2782 of the lists.
2784 If timeout is 0, then block indefinitely.
2786 For more information, see https://redis.io/commands/brpop
2787 """
2788 if timeout is None:
2789 timeout = 0
2790 keys = list_or_args(keys, None)
2791 keys.append(timeout)
2792 return self.execute_command("BRPOP", *keys)
2794 def brpoplpush(
2795 self, src: KeyT, dst: KeyT, timeout: Optional[Number] = 0
2796 ) -> Union[Awaitable[Optional[str]], Optional[str]]:
2797 """
2798 Pop a value off the tail of ``src``, push it on the head of ``dst``
2799 and then return it.
2801 This command blocks until a value is in ``src`` or until ``timeout``
2802 seconds elapse, whichever is first. A ``timeout`` value of 0 blocks
2803 forever.
2805 For more information, see https://redis.io/commands/brpoplpush
2806 """
2807 if timeout is None:
2808 timeout = 0
2809 return self.execute_command("BRPOPLPUSH", src, dst, timeout)
2811 def blmpop(
2812 self,
2813 timeout: float,
2814 numkeys: int,
2815 *args: str,
2816 direction: str,
2817 count: Optional[int] = 1,
2818 ) -> Optional[list]:
2819 """
2820 Pop ``count`` values (default 1) from first non-empty in the list
2821 of provided key names.
2823 When all lists are empty this command blocks the connection until another
2824 client pushes to it or until the timeout, timeout of 0 blocks indefinitely
2826 For more information, see https://redis.io/commands/blmpop
2827 """
2828 cmd_args = [timeout, numkeys, *args, direction, "COUNT", count]
2830 return self.execute_command("BLMPOP", *cmd_args)
2832 def lmpop(
2833 self,
2834 num_keys: int,
2835 *args: str,
2836 direction: str,
2837 count: Optional[int] = 1,
2838 ) -> Union[Awaitable[list], list]:
2839 """
2840 Pop ``count`` values (default 1) first non-empty list key from the list
2841 of args provided key names.
2843 For more information, see https://redis.io/commands/lmpop
2844 """
2845 cmd_args = [num_keys] + list(args) + [direction]
2846 if count != 1:
2847 cmd_args.extend(["COUNT", count])
2849 return self.execute_command("LMPOP", *cmd_args)
2851 def lindex(
2852 self, name: KeyT, index: int
2853 ) -> Union[Awaitable[Optional[str]], Optional[str]]:
2854 """
2855 Return the item from list ``name`` at position ``index``
2857 Negative indexes are supported and will return an item at the
2858 end of the list
2860 For more information, see https://redis.io/commands/lindex
2861 """
2862 return self.execute_command("LINDEX", name, index, keys=[name])
2864 def linsert(
2865 self, name: KeyT, where: str, refvalue: str, value: str
2866 ) -> Union[Awaitable[int], int]:
2867 """
2868 Insert ``value`` in list ``name`` either immediately before or after
2869 [``where``] ``refvalue``
2871 Returns the new length of the list on success or -1 if ``refvalue``
2872 is not in the list.
2874 For more information, see https://redis.io/commands/linsert
2875 """
2876 return self.execute_command("LINSERT", name, where, refvalue, value)
2878 def llen(self, name: KeyT) -> Union[Awaitable[int], int]:
2879 """
2880 Return the length of the list ``name``
2882 For more information, see https://redis.io/commands/llen
2883 """
2884 return self.execute_command("LLEN", name, keys=[name])
2886 def lpop(
2887 self,
2888 name: KeyT,
2889 count: Optional[int] = None,
2890 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]:
2891 """
2892 Removes and returns the first elements of the list ``name``.
2894 By default, the command pops a single element from the beginning of
2895 the list. When provided with the optional ``count`` argument, the reply
2896 will consist of up to count elements, depending on the list's length.
2898 For more information, see https://redis.io/commands/lpop
2899 """
2900 if count is not None:
2901 return self.execute_command("LPOP", name, count)
2902 else:
2903 return self.execute_command("LPOP", name)
2905 def lpush(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
2906 """
2907 Push ``values`` onto the head of the list ``name``
2909 For more information, see https://redis.io/commands/lpush
2910 """
2911 return self.execute_command("LPUSH", name, *values)
2913 def lpushx(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
2914 """
2915 Push ``value`` onto the head of the list ``name`` if ``name`` exists
2917 For more information, see https://redis.io/commands/lpushx
2918 """
2919 return self.execute_command("LPUSHX", name, *values)
2921 def lrange(self, name: KeyT, start: int, end: int) -> Union[Awaitable[list], list]:
2922 """
2923 Return a slice of the list ``name`` between
2924 position ``start`` and ``end``
2926 ``start`` and ``end`` can be negative numbers just like
2927 Python slicing notation
2929 For more information, see https://redis.io/commands/lrange
2930 """
2931 return self.execute_command("LRANGE", name, start, end, keys=[name])
2933 def lrem(self, name: KeyT, count: int, value: str) -> Union[Awaitable[int], int]:
2934 """
2935 Remove the first ``count`` occurrences of elements equal to ``value``
2936 from the list stored at ``name``.
2938 The count argument influences the operation in the following ways:
2939 count > 0: Remove elements equal to value moving from head to tail.
2940 count < 0: Remove elements equal to value moving from tail to head.
2941 count = 0: Remove all elements equal to value.
2943 For more information, see https://redis.io/commands/lrem
2944 """
2945 return self.execute_command("LREM", name, count, value)
2947 def lset(self, name: KeyT, index: int, value: str) -> Union[Awaitable[str], str]:
2948 """
2949 Set element at ``index`` of list ``name`` to ``value``
2951 For more information, see https://redis.io/commands/lset
2952 """
2953 return self.execute_command("LSET", name, index, value)
2955 def ltrim(self, name: KeyT, start: int, end: int) -> Union[Awaitable[str], str]:
2956 """
2957 Trim the list ``name``, removing all values not within the slice
2958 between ``start`` and ``end``
2960 ``start`` and ``end`` can be negative numbers just like
2961 Python slicing notation
2963 For more information, see https://redis.io/commands/ltrim
2964 """
2965 return self.execute_command("LTRIM", name, start, end)
2967 def rpop(
2968 self,
2969 name: KeyT,
2970 count: Optional[int] = None,
2971 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]:
2972 """
2973 Removes and returns the last elements of the list ``name``.
2975 By default, the command pops a single element from the end of the list.
2976 When provided with the optional ``count`` argument, the reply will
2977 consist of up to count elements, depending on the list's length.
2979 For more information, see https://redis.io/commands/rpop
2980 """
2981 if count is not None:
2982 return self.execute_command("RPOP", name, count)
2983 else:
2984 return self.execute_command("RPOP", name)
2986 def rpoplpush(self, src: KeyT, dst: KeyT) -> Union[Awaitable[str], str]:
2987 """
2988 RPOP a value off of the ``src`` list and atomically LPUSH it
2989 on to the ``dst`` list. Returns the value.
2991 For more information, see https://redis.io/commands/rpoplpush
2992 """
2993 return self.execute_command("RPOPLPUSH", src, dst)
2995 def rpush(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
2996 """
2997 Push ``values`` onto the tail of the list ``name``
2999 For more information, see https://redis.io/commands/rpush
3000 """
3001 return self.execute_command("RPUSH", name, *values)
3003 def rpushx(self, name: KeyT, *values: str) -> Union[Awaitable[int], int]:
3004 """
3005 Push ``value`` onto the tail of the list ``name`` if ``name`` exists
3007 For more information, see https://redis.io/commands/rpushx
3008 """
3009 return self.execute_command("RPUSHX", name, *values)
3011 def lpos(
3012 self,
3013 name: KeyT,
3014 value: str,
3015 rank: Optional[int] = None,
3016 count: Optional[int] = None,
3017 maxlen: Optional[int] = None,
3018 ) -> Union[str, List, None]:
3019 """
3020 Get position of ``value`` within the list ``name``
3022 If specified, ``rank`` indicates the "rank" of the first element to
3023 return in case there are multiple copies of ``value`` in the list.
3024 By default, LPOS returns the position of the first occurrence of
3025 ``value`` in the list. When ``rank`` 2, LPOS returns the position of
3026 the second ``value`` in the list. If ``rank`` is negative, LPOS
3027 searches the list in reverse. For example, -1 would return the
3028 position of the last occurrence of ``value`` and -2 would return the
3029 position of the next to last occurrence of ``value``.
3031 If specified, ``count`` indicates that LPOS should return a list of
3032 up to ``count`` positions. A ``count`` of 2 would return a list of
3033 up to 2 positions. A ``count`` of 0 returns a list of all positions
3034 matching ``value``. When ``count`` is specified and but ``value``
3035 does not exist in the list, an empty list is returned.
3037 If specified, ``maxlen`` indicates the maximum number of list
3038 elements to scan. A ``maxlen`` of 1000 will only return the
3039 position(s) of items within the first 1000 entries in the list.
3040 A ``maxlen`` of 0 (the default) will scan the entire list.
3042 For more information, see https://redis.io/commands/lpos
3043 """
3044 pieces: list[EncodableT] = [name, value]
3045 if rank is not None:
3046 pieces.extend(["RANK", rank])
3048 if count is not None:
3049 pieces.extend(["COUNT", count])
3051 if maxlen is not None:
3052 pieces.extend(["MAXLEN", maxlen])
3054 return self.execute_command("LPOS", *pieces, keys=[name])
3056 def sort(
3057 self,
3058 name: KeyT,
3059 start: Optional[int] = None,
3060 num: Optional[int] = None,
3061 by: Optional[str] = None,
3062 get: Optional[List[str]] = None,
3063 desc: bool = False,
3064 alpha: bool = False,
3065 store: Optional[str] = None,
3066 groups: Optional[bool] = False,
3067 ) -> Union[List, int]:
3068 """
3069 Sort and return the list, set or sorted set at ``name``.
3071 ``start`` and ``num`` allow for paging through the sorted data
3073 ``by`` allows using an external key to weight and sort the items.
3074 Use an "*" to indicate where in the key the item value is located
3076 ``get`` allows for returning items from external keys rather than the
3077 sorted data itself. Use an "*" to indicate where in the key
3078 the item value is located
3080 ``desc`` allows for reversing the sort
3082 ``alpha`` allows for sorting lexicographically rather than numerically
3084 ``store`` allows for storing the result of the sort into
3085 the key ``store``
3087 ``groups`` if set to True and if ``get`` contains at least two
3088 elements, sort will return a list of tuples, each containing the
3089 values fetched from the arguments to ``get``.
3091 For more information, see https://redis.io/commands/sort
3092 """
3093 if (start is not None and num is None) or (num is not None and start is None):
3094 raise DataError("``start`` and ``num`` must both be specified")
3096 pieces: list[EncodableT] = [name]
3097 if by is not None:
3098 pieces.extend([b"BY", by])
3099 if start is not None and num is not None:
3100 pieces.extend([b"LIMIT", start, num])
3101 if get is not None:
3102 # If get is a string assume we want to get a single value.
3103 # Otherwise assume it's an interable and we want to get multiple
3104 # values. We can't just iterate blindly because strings are
3105 # iterable.
3106 if isinstance(get, (bytes, str)):
3107 pieces.extend([b"GET", get])
3108 else:
3109 for g in get:
3110 pieces.extend([b"GET", g])
3111 if desc:
3112 pieces.append(b"DESC")
3113 if alpha:
3114 pieces.append(b"ALPHA")
3115 if store is not None:
3116 pieces.extend([b"STORE", store])
3117 if groups:
3118 if not get or isinstance(get, (bytes, str)) or len(get) < 2:
3119 raise DataError(
3120 'when using "groups" the "get" argument '
3121 "must be specified and contain at least "
3122 "two keys"
3123 )
3125 options = {"groups": len(get) if groups else None}
3126 options["keys"] = [name]
3127 return self.execute_command("SORT", *pieces, **options)
3129 def sort_ro(
3130 self,
3131 key: str,
3132 start: Optional[int] = None,
3133 num: Optional[int] = None,
3134 by: Optional[str] = None,
3135 get: Optional[List[str]] = None,
3136 desc: bool = False,
3137 alpha: bool = False,
3138 ) -> list:
3139 """
3140 Returns the elements contained in the list, set or sorted set at key.
3141 (read-only variant of the SORT command)
3143 ``start`` and ``num`` allow for paging through the sorted data
3145 ``by`` allows using an external key to weight and sort the items.
3146 Use an "*" to indicate where in the key the item value is located
3148 ``get`` allows for returning items from external keys rather than the
3149 sorted data itself. Use an "*" to indicate where in the key
3150 the item value is located
3152 ``desc`` allows for reversing the sort
3154 ``alpha`` allows for sorting lexicographically rather than numerically
3156 For more information, see https://redis.io/commands/sort_ro
3157 """
3158 return self.sort(
3159 key, start=start, num=num, by=by, get=get, desc=desc, alpha=alpha
3160 )
3163AsyncListCommands = ListCommands
3166class ScanCommands(CommandsProtocol):
3167 """
3168 Redis SCAN commands.
3169 see: https://redis.io/commands/scan
3170 """
3172 def scan(
3173 self,
3174 cursor: int = 0,
3175 match: Union[PatternT, None] = None,
3176 count: Optional[int] = None,
3177 _type: Optional[str] = None,
3178 **kwargs,
3179 ) -> ResponseT:
3180 """
3181 Incrementally return lists of key names. Also return a cursor
3182 indicating the scan position.
3184 ``match`` allows for filtering the keys by pattern
3186 ``count`` provides a hint to Redis about the number of keys to
3187 return per batch.
3189 ``_type`` filters the returned values by a particular Redis type.
3190 Stock Redis instances allow for the following types:
3191 HASH, LIST, SET, STREAM, STRING, ZSET
3192 Additionally, Redis modules can expose other types as well.
3194 For more information, see https://redis.io/commands/scan
3195 """
3196 pieces: list[EncodableT] = [cursor]
3197 if match is not None:
3198 pieces.extend([b"MATCH", match])
3199 if count is not None:
3200 pieces.extend([b"COUNT", count])
3201 if _type is not None:
3202 pieces.extend([b"TYPE", _type])
3203 return self.execute_command("SCAN", *pieces, **kwargs)
3205 def scan_iter(
3206 self,
3207 match: Union[PatternT, None] = None,
3208 count: Optional[int] = None,
3209 _type: Optional[str] = None,
3210 **kwargs,
3211 ) -> Iterator:
3212 """
3213 Make an iterator using the SCAN command so that the client doesn't
3214 need to remember the cursor position.
3216 ``match`` allows for filtering the keys by pattern
3218 ``count`` provides a hint to Redis about the number of keys to
3219 return per batch.
3221 ``_type`` filters the returned values by a particular Redis type.
3222 Stock Redis instances allow for the following types:
3223 HASH, LIST, SET, STREAM, STRING, ZSET
3224 Additionally, Redis modules can expose other types as well.
3225 """
3226 cursor = "0"
3227 while cursor != 0:
3228 cursor, data = self.scan(
3229 cursor=cursor, match=match, count=count, _type=_type, **kwargs
3230 )
3231 yield from data
3233 def sscan(
3234 self,
3235 name: KeyT,
3236 cursor: int = 0,
3237 match: Union[PatternT, None] = None,
3238 count: Optional[int] = None,
3239 ) -> ResponseT:
3240 """
3241 Incrementally return lists of elements in a set. Also return a cursor
3242 indicating the scan position.
3244 ``match`` allows for filtering the keys by pattern
3246 ``count`` allows for hint the minimum number of returns
3248 For more information, see https://redis.io/commands/sscan
3249 """
3250 pieces: list[EncodableT] = [name, cursor]
3251 if match is not None:
3252 pieces.extend([b"MATCH", match])
3253 if count is not None:
3254 pieces.extend([b"COUNT", count])
3255 return self.execute_command("SSCAN", *pieces)
3257 def sscan_iter(
3258 self,
3259 name: KeyT,
3260 match: Union[PatternT, None] = None,
3261 count: Optional[int] = None,
3262 ) -> Iterator:
3263 """
3264 Make an iterator using the SSCAN command so that the client doesn't
3265 need to remember the cursor position.
3267 ``match`` allows for filtering the keys by pattern
3269 ``count`` allows for hint the minimum number of returns
3270 """
3271 cursor = "0"
3272 while cursor != 0:
3273 cursor, data = self.sscan(name, cursor=cursor, match=match, count=count)
3274 yield from data
3276 def hscan(
3277 self,
3278 name: KeyT,
3279 cursor: int = 0,
3280 match: Union[PatternT, None] = None,
3281 count: Optional[int] = None,
3282 no_values: Union[bool, None] = None,
3283 ) -> ResponseT:
3284 """
3285 Incrementally return key/value slices in a hash. Also return a cursor
3286 indicating the scan position.
3288 ``match`` allows for filtering the keys by pattern
3290 ``count`` allows for hint the minimum number of returns
3292 ``no_values`` indicates to return only the keys, without values.
3294 For more information, see https://redis.io/commands/hscan
3295 """
3296 pieces: list[EncodableT] = [name, cursor]
3297 if match is not None:
3298 pieces.extend([b"MATCH", match])
3299 if count is not None:
3300 pieces.extend([b"COUNT", count])
3301 if no_values is not None:
3302 pieces.extend([b"NOVALUES"])
3303 return self.execute_command("HSCAN", *pieces, no_values=no_values)
3305 def hscan_iter(
3306 self,
3307 name: str,
3308 match: Union[PatternT, None] = None,
3309 count: Optional[int] = None,
3310 no_values: Union[bool, None] = None,
3311 ) -> Iterator:
3312 """
3313 Make an iterator using the HSCAN command so that the client doesn't
3314 need to remember the cursor position.
3316 ``match`` allows for filtering the keys by pattern
3318 ``count`` allows for hint the minimum number of returns
3320 ``no_values`` indicates to return only the keys, without values
3321 """
3322 cursor = "0"
3323 while cursor != 0:
3324 cursor, data = self.hscan(
3325 name, cursor=cursor, match=match, count=count, no_values=no_values
3326 )
3327 if no_values:
3328 yield from data
3329 else:
3330 yield from data.items()
3332 def zscan(
3333 self,
3334 name: KeyT,
3335 cursor: int = 0,
3336 match: Union[PatternT, None] = None,
3337 count: Optional[int] = None,
3338 score_cast_func: Union[type, Callable] = float,
3339 ) -> ResponseT:
3340 """
3341 Incrementally return lists of elements in a sorted set. Also return a
3342 cursor indicating the scan position.
3344 ``match`` allows for filtering the keys by pattern
3346 ``count`` allows for hint the minimum number of returns
3348 ``score_cast_func`` a callable used to cast the score return value
3350 For more information, see https://redis.io/commands/zscan
3351 """
3352 pieces = [name, cursor]
3353 if match is not None:
3354 pieces.extend([b"MATCH", match])
3355 if count is not None:
3356 pieces.extend([b"COUNT", count])
3357 options = {"score_cast_func": score_cast_func}
3358 return self.execute_command("ZSCAN", *pieces, **options)
3360 def zscan_iter(
3361 self,
3362 name: KeyT,
3363 match: Union[PatternT, None] = None,
3364 count: Optional[int] = None,
3365 score_cast_func: Union[type, Callable] = float,
3366 ) -> Iterator:
3367 """
3368 Make an iterator using the ZSCAN command so that the client doesn't
3369 need to remember the cursor position.
3371 ``match`` allows for filtering the keys by pattern
3373 ``count`` allows for hint the minimum number of returns
3375 ``score_cast_func`` a callable used to cast the score return value
3376 """
3377 cursor = "0"
3378 while cursor != 0:
3379 cursor, data = self.zscan(
3380 name,
3381 cursor=cursor,
3382 match=match,
3383 count=count,
3384 score_cast_func=score_cast_func,
3385 )
3386 yield from data
3389class AsyncScanCommands(ScanCommands):
3390 async def scan_iter(
3391 self,
3392 match: Union[PatternT, None] = None,
3393 count: Optional[int] = None,
3394 _type: Optional[str] = None,
3395 **kwargs,
3396 ) -> AsyncIterator:
3397 """
3398 Make an iterator using the SCAN command so that the client doesn't
3399 need to remember the cursor position.
3401 ``match`` allows for filtering the keys by pattern
3403 ``count`` provides a hint to Redis about the number of keys to
3404 return per batch.
3406 ``_type`` filters the returned values by a particular Redis type.
3407 Stock Redis instances allow for the following types:
3408 HASH, LIST, SET, STREAM, STRING, ZSET
3409 Additionally, Redis modules can expose other types as well.
3410 """
3411 cursor = "0"
3412 while cursor != 0:
3413 cursor, data = await self.scan(
3414 cursor=cursor, match=match, count=count, _type=_type, **kwargs
3415 )
3416 for d in data:
3417 yield d
3419 async def sscan_iter(
3420 self,
3421 name: KeyT,
3422 match: Union[PatternT, None] = None,
3423 count: Optional[int] = None,
3424 ) -> AsyncIterator:
3425 """
3426 Make an iterator using the SSCAN command so that the client doesn't
3427 need to remember the cursor position.
3429 ``match`` allows for filtering the keys by pattern
3431 ``count`` allows for hint the minimum number of returns
3432 """
3433 cursor = "0"
3434 while cursor != 0:
3435 cursor, data = await self.sscan(
3436 name, cursor=cursor, match=match, count=count
3437 )
3438 for d in data:
3439 yield d
3441 async def hscan_iter(
3442 self,
3443 name: str,
3444 match: Union[PatternT, None] = None,
3445 count: Optional[int] = None,
3446 no_values: Union[bool, None] = None,
3447 ) -> AsyncIterator:
3448 """
3449 Make an iterator using the HSCAN command so that the client doesn't
3450 need to remember the cursor position.
3452 ``match`` allows for filtering the keys by pattern
3454 ``count`` allows for hint the minimum number of returns
3456 ``no_values`` indicates to return only the keys, without values
3457 """
3458 cursor = "0"
3459 while cursor != 0:
3460 cursor, data = await self.hscan(
3461 name, cursor=cursor, match=match, count=count, no_values=no_values
3462 )
3463 if no_values:
3464 for it in data:
3465 yield it
3466 else:
3467 for it in data.items():
3468 yield it
3470 async def zscan_iter(
3471 self,
3472 name: KeyT,
3473 match: Union[PatternT, None] = None,
3474 count: Optional[int] = None,
3475 score_cast_func: Union[type, Callable] = float,
3476 ) -> AsyncIterator:
3477 """
3478 Make an iterator using the ZSCAN command so that the client doesn't
3479 need to remember the cursor position.
3481 ``match`` allows for filtering the keys by pattern
3483 ``count`` allows for hint the minimum number of returns
3485 ``score_cast_func`` a callable used to cast the score return value
3486 """
3487 cursor = "0"
3488 while cursor != 0:
3489 cursor, data = await self.zscan(
3490 name,
3491 cursor=cursor,
3492 match=match,
3493 count=count,
3494 score_cast_func=score_cast_func,
3495 )
3496 for d in data:
3497 yield d
3500class SetCommands(CommandsProtocol):
3501 """
3502 Redis commands for Set data type.
3503 see: https://redis.io/topics/data-types#sets
3504 """
3506 def sadd(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
3507 """
3508 Add ``value(s)`` to set ``name``
3510 For more information, see https://redis.io/commands/sadd
3511 """
3512 return self.execute_command("SADD", name, *values)
3514 def scard(self, name: KeyT) -> Union[Awaitable[int], int]:
3515 """
3516 Return the number of elements in set ``name``
3518 For more information, see https://redis.io/commands/scard
3519 """
3520 return self.execute_command("SCARD", name, keys=[name])
3522 def sdiff(self, keys: List, *args: List) -> Union[Awaitable[list], list]:
3523 """
3524 Return the difference of sets specified by ``keys``
3526 For more information, see https://redis.io/commands/sdiff
3527 """
3528 args = list_or_args(keys, args)
3529 return self.execute_command("SDIFF", *args, keys=args)
3531 def sdiffstore(
3532 self, dest: str, keys: List, *args: List
3533 ) -> Union[Awaitable[int], int]:
3534 """
3535 Store the difference of sets specified by ``keys`` into a new
3536 set named ``dest``. Returns the number of keys in the new set.
3538 For more information, see https://redis.io/commands/sdiffstore
3539 """
3540 args = list_or_args(keys, args)
3541 return self.execute_command("SDIFFSTORE", dest, *args)
3543 def sinter(self, keys: List, *args: List) -> Union[Awaitable[list], list]:
3544 """
3545 Return the intersection of sets specified by ``keys``
3547 For more information, see https://redis.io/commands/sinter
3548 """
3549 args = list_or_args(keys, args)
3550 return self.execute_command("SINTER", *args, keys=args)
3552 def sintercard(
3553 self, numkeys: int, keys: List[KeyT], limit: int = 0
3554 ) -> Union[Awaitable[int], int]:
3555 """
3556 Return the cardinality of the intersect of multiple sets specified by ``keys``.
3558 When LIMIT provided (defaults to 0 and means unlimited), if the intersection
3559 cardinality reaches limit partway through the computation, the algorithm will
3560 exit and yield limit as the cardinality
3562 For more information, see https://redis.io/commands/sintercard
3563 """
3564 args = [numkeys, *keys, "LIMIT", limit]
3565 return self.execute_command("SINTERCARD", *args, keys=keys)
3567 def sinterstore(
3568 self, dest: KeyT, keys: List, *args: List
3569 ) -> Union[Awaitable[int], int]:
3570 """
3571 Store the intersection of sets specified by ``keys`` into a new
3572 set named ``dest``. Returns the number of keys in the new set.
3574 For more information, see https://redis.io/commands/sinterstore
3575 """
3576 args = list_or_args(keys, args)
3577 return self.execute_command("SINTERSTORE", dest, *args)
3579 def sismember(
3580 self, name: KeyT, value: str
3581 ) -> Union[Awaitable[Union[Literal[0], Literal[1]]], Union[Literal[0], Literal[1]]]:
3582 """
3583 Return whether ``value`` is a member of set ``name``:
3584 - 1 if the value is a member of the set.
3585 - 0 if the value is not a member of the set or if key does not exist.
3587 For more information, see https://redis.io/commands/sismember
3588 """
3589 return self.execute_command("SISMEMBER", name, value, keys=[name])
3591 def smembers(self, name: KeyT) -> Union[Awaitable[Set], Set]:
3592 """
3593 Return all members of the set ``name``
3595 For more information, see https://redis.io/commands/smembers
3596 """
3597 return self.execute_command("SMEMBERS", name, keys=[name])
3599 def smismember(
3600 self, name: KeyT, values: List, *args: List
3601 ) -> Union[
3602 Awaitable[List[Union[Literal[0], Literal[1]]]],
3603 List[Union[Literal[0], Literal[1]]],
3604 ]:
3605 """
3606 Return whether each value in ``values`` is a member of the set ``name``
3607 as a list of ``int`` in the order of ``values``:
3608 - 1 if the value is a member of the set.
3609 - 0 if the value is not a member of the set or if key does not exist.
3611 For more information, see https://redis.io/commands/smismember
3612 """
3613 args = list_or_args(values, args)
3614 return self.execute_command("SMISMEMBER", name, *args, keys=[name])
3616 def smove(self, src: KeyT, dst: KeyT, value: str) -> Union[Awaitable[bool], bool]:
3617 """
3618 Move ``value`` from set ``src`` to set ``dst`` atomically
3620 For more information, see https://redis.io/commands/smove
3621 """
3622 return self.execute_command("SMOVE", src, dst, value)
3624 def spop(self, name: KeyT, count: Optional[int] = None) -> Union[str, List, None]:
3625 """
3626 Remove and return a random member of set ``name``
3628 For more information, see https://redis.io/commands/spop
3629 """
3630 args = (count is not None) and [count] or []
3631 return self.execute_command("SPOP", name, *args)
3633 def srandmember(
3634 self, name: KeyT, number: Optional[int] = None
3635 ) -> Union[str, List, None]:
3636 """
3637 If ``number`` is None, returns a random member of set ``name``.
3639 If ``number`` is supplied, returns a list of ``number`` random
3640 members of set ``name``. Note this is only available when running
3641 Redis 2.6+.
3643 For more information, see https://redis.io/commands/srandmember
3644 """
3645 args = (number is not None) and [number] or []
3646 return self.execute_command("SRANDMEMBER", name, *args)
3648 def srem(self, name: KeyT, *values: FieldT) -> Union[Awaitable[int], int]:
3649 """
3650 Remove ``values`` from set ``name``
3652 For more information, see https://redis.io/commands/srem
3653 """
3654 return self.execute_command("SREM", name, *values)
3656 def sunion(self, keys: List, *args: List) -> Union[Awaitable[List], List]:
3657 """
3658 Return the union of sets specified by ``keys``
3660 For more information, see https://redis.io/commands/sunion
3661 """
3662 args = list_or_args(keys, args)
3663 return self.execute_command("SUNION", *args, keys=args)
3665 def sunionstore(
3666 self, dest: KeyT, keys: List, *args: List
3667 ) -> Union[Awaitable[int], int]:
3668 """
3669 Store the union of sets specified by ``keys`` into a new
3670 set named ``dest``. Returns the number of keys in the new set.
3672 For more information, see https://redis.io/commands/sunionstore
3673 """
3674 args = list_or_args(keys, args)
3675 return self.execute_command("SUNIONSTORE", dest, *args)
3678AsyncSetCommands = SetCommands
3681class StreamCommands(CommandsProtocol):
3682 """
3683 Redis commands for Stream data type.
3684 see: https://redis.io/topics/streams-intro
3685 """
3687 def xack(self, name: KeyT, groupname: GroupT, *ids: StreamIdT) -> ResponseT:
3688 """
3689 Acknowledges the successful processing of one or more messages.
3691 Args:
3692 name: name of the stream.
3693 groupname: name of the consumer group.
3694 *ids: message ids to acknowledge.
3696 For more information, see https://redis.io/commands/xack
3697 """
3698 return self.execute_command("XACK", name, groupname, *ids)
3700 def xackdel(
3701 self,
3702 name: KeyT,
3703 groupname: GroupT,
3704 *ids: StreamIdT,
3705 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF",
3706 ) -> ResponseT:
3707 """
3708 Combines the functionality of XACK and XDEL. Acknowledges the specified
3709 message IDs in the given consumer group and simultaneously attempts to
3710 delete the corresponding entries from the stream.
3711 """
3712 if not ids:
3713 raise DataError("XACKDEL requires at least one message ID")
3715 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
3716 raise DataError("XACKDEL ref_policy must be one of: KEEPREF, DELREF, ACKED")
3718 pieces = [name, groupname, ref_policy, "IDS", len(ids)]
3719 pieces.extend(ids)
3720 return self.execute_command("XACKDEL", *pieces)
3722 def xadd(
3723 self,
3724 name: KeyT,
3725 fields: Dict[FieldT, EncodableT],
3726 id: StreamIdT = "*",
3727 maxlen: Optional[int] = None,
3728 approximate: bool = True,
3729 nomkstream: bool = False,
3730 minid: Union[StreamIdT, None] = None,
3731 limit: Optional[int] = None,
3732 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None,
3733 ) -> ResponseT:
3734 """
3735 Add to a stream.
3736 name: name of the stream
3737 fields: dict of field/value pairs to insert into the stream
3738 id: Location to insert this record. By default it is appended.
3739 maxlen: truncate old stream members beyond this size.
3740 Can't be specified with minid.
3741 approximate: actual stream length may be slightly more than maxlen
3742 nomkstream: When set to true, do not make a stream
3743 minid: the minimum id in the stream to query.
3744 Can't be specified with maxlen.
3745 limit: specifies the maximum number of entries to retrieve
3746 ref_policy: optional reference policy for consumer groups when trimming:
3747 - KEEPREF (default): When trimming, preserves references in consumer groups' PEL
3748 - DELREF: When trimming, removes all references from consumer groups' PEL
3749 - ACKED: When trimming, only removes entries acknowledged by all consumer groups
3751 For more information, see https://redis.io/commands/xadd
3752 """
3753 pieces: list[EncodableT] = []
3754 if maxlen is not None and minid is not None:
3755 raise DataError("Only one of ```maxlen``` or ```minid``` may be specified")
3757 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
3758 raise DataError("XADD ref_policy must be one of: KEEPREF, DELREF, ACKED")
3760 if maxlen is not None:
3761 if not isinstance(maxlen, int) or maxlen < 0:
3762 raise DataError("XADD maxlen must be non-negative integer")
3763 pieces.append(b"MAXLEN")
3764 if approximate:
3765 pieces.append(b"~")
3766 pieces.append(str(maxlen))
3767 if minid is not None:
3768 pieces.append(b"MINID")
3769 if approximate:
3770 pieces.append(b"~")
3771 pieces.append(minid)
3772 if limit is not None:
3773 pieces.extend([b"LIMIT", limit])
3774 if nomkstream:
3775 pieces.append(b"NOMKSTREAM")
3776 if ref_policy is not None:
3777 pieces.append(ref_policy)
3778 pieces.append(id)
3779 if not isinstance(fields, dict) or len(fields) == 0:
3780 raise DataError("XADD fields must be a non-empty dict")
3781 for pair in fields.items():
3782 pieces.extend(pair)
3783 return self.execute_command("XADD", name, *pieces)
3785 def xautoclaim(
3786 self,
3787 name: KeyT,
3788 groupname: GroupT,
3789 consumername: ConsumerT,
3790 min_idle_time: int,
3791 start_id: StreamIdT = "0-0",
3792 count: Optional[int] = None,
3793 justid: bool = False,
3794 ) -> ResponseT:
3795 """
3796 Transfers ownership of pending stream entries that match the specified
3797 criteria. Conceptually, equivalent to calling XPENDING and then XCLAIM,
3798 but provides a more straightforward way to deal with message delivery
3799 failures via SCAN-like semantics.
3800 name: name of the stream.
3801 groupname: name of the consumer group.
3802 consumername: name of a consumer that claims the message.
3803 min_idle_time: filter messages that were idle less than this amount of
3804 milliseconds.
3805 start_id: filter messages with equal or greater ID.
3806 count: optional integer, upper limit of the number of entries that the
3807 command attempts to claim. Set to 100 by default.
3808 justid: optional boolean, false by default. Return just an array of IDs
3809 of messages successfully claimed, without returning the actual message
3811 For more information, see https://redis.io/commands/xautoclaim
3812 """
3813 try:
3814 if int(min_idle_time) < 0:
3815 raise DataError(
3816 "XAUTOCLAIM min_idle_time must be a nonnegative integer"
3817 )
3818 except TypeError:
3819 pass
3821 kwargs = {}
3822 pieces = [name, groupname, consumername, min_idle_time, start_id]
3824 try:
3825 if int(count) < 0:
3826 raise DataError("XPENDING count must be a integer >= 0")
3827 pieces.extend([b"COUNT", count])
3828 except TypeError:
3829 pass
3830 if justid:
3831 pieces.append(b"JUSTID")
3832 kwargs["parse_justid"] = True
3834 return self.execute_command("XAUTOCLAIM", *pieces, **kwargs)
3836 def xclaim(
3837 self,
3838 name: KeyT,
3839 groupname: GroupT,
3840 consumername: ConsumerT,
3841 min_idle_time: int,
3842 message_ids: Union[List[StreamIdT], Tuple[StreamIdT]],
3843 idle: Optional[int] = None,
3844 time: Optional[int] = None,
3845 retrycount: Optional[int] = None,
3846 force: bool = False,
3847 justid: bool = False,
3848 ) -> ResponseT:
3849 """
3850 Changes the ownership of a pending message.
3852 name: name of the stream.
3854 groupname: name of the consumer group.
3856 consumername: name of a consumer that claims the message.
3858 min_idle_time: filter messages that were idle less than this amount of
3859 milliseconds
3861 message_ids: non-empty list or tuple of message IDs to claim
3863 idle: optional. Set the idle time (last time it was delivered) of the
3864 message in ms
3866 time: optional integer. This is the same as idle but instead of a
3867 relative amount of milliseconds, it sets the idle time to a specific
3868 Unix time (in milliseconds).
3870 retrycount: optional integer. set the retry counter to the specified
3871 value. This counter is incremented every time a message is delivered
3872 again.
3874 force: optional boolean, false by default. Creates the pending message
3875 entry in the PEL even if certain specified IDs are not already in the
3876 PEL assigned to a different client.
3878 justid: optional boolean, false by default. Return just an array of IDs
3879 of messages successfully claimed, without returning the actual message
3881 For more information, see https://redis.io/commands/xclaim
3882 """
3883 if not isinstance(min_idle_time, int) or min_idle_time < 0:
3884 raise DataError("XCLAIM min_idle_time must be a non negative integer")
3885 if not isinstance(message_ids, (list, tuple)) or not message_ids:
3886 raise DataError(
3887 "XCLAIM message_ids must be a non empty list or "
3888 "tuple of message IDs to claim"
3889 )
3891 kwargs = {}
3892 pieces: list[EncodableT] = [name, groupname, consumername, str(min_idle_time)]
3893 pieces.extend(list(message_ids))
3895 if idle is not None:
3896 if not isinstance(idle, int):
3897 raise DataError("XCLAIM idle must be an integer")
3898 pieces.extend((b"IDLE", str(idle)))
3899 if time is not None:
3900 if not isinstance(time, int):
3901 raise DataError("XCLAIM time must be an integer")
3902 pieces.extend((b"TIME", str(time)))
3903 if retrycount is not None:
3904 if not isinstance(retrycount, int):
3905 raise DataError("XCLAIM retrycount must be an integer")
3906 pieces.extend((b"RETRYCOUNT", str(retrycount)))
3908 if force:
3909 if not isinstance(force, bool):
3910 raise DataError("XCLAIM force must be a boolean")
3911 pieces.append(b"FORCE")
3912 if justid:
3913 if not isinstance(justid, bool):
3914 raise DataError("XCLAIM justid must be a boolean")
3915 pieces.append(b"JUSTID")
3916 kwargs["parse_justid"] = True
3917 return self.execute_command("XCLAIM", *pieces, **kwargs)
3919 def xdel(self, name: KeyT, *ids: StreamIdT) -> ResponseT:
3920 """
3921 Deletes one or more messages from a stream.
3923 Args:
3924 name: name of the stream.
3925 *ids: message ids to delete.
3927 For more information, see https://redis.io/commands/xdel
3928 """
3929 return self.execute_command("XDEL", name, *ids)
3931 def xdelex(
3932 self,
3933 name: KeyT,
3934 *ids: StreamIdT,
3935 ref_policy: Literal["KEEPREF", "DELREF", "ACKED"] = "KEEPREF",
3936 ) -> ResponseT:
3937 """
3938 Extended version of XDEL that provides more control over how message entries
3939 are deleted concerning consumer groups.
3940 """
3941 if not ids:
3942 raise DataError("XDELEX requires at least one message ID")
3944 if ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
3945 raise DataError("XDELEX ref_policy must be one of: KEEPREF, DELREF, ACKED")
3947 pieces = [name, ref_policy, "IDS", len(ids)]
3948 pieces.extend(ids)
3949 return self.execute_command("XDELEX", *pieces)
3951 def xgroup_create(
3952 self,
3953 name: KeyT,
3954 groupname: GroupT,
3955 id: StreamIdT = "$",
3956 mkstream: bool = False,
3957 entries_read: Optional[int] = None,
3958 ) -> ResponseT:
3959 """
3960 Create a new consumer group associated with a stream.
3961 name: name of the stream.
3962 groupname: name of the consumer group.
3963 id: ID of the last item in the stream to consider already delivered.
3965 For more information, see https://redis.io/commands/xgroup-create
3966 """
3967 pieces: list[EncodableT] = ["XGROUP CREATE", name, groupname, id]
3968 if mkstream:
3969 pieces.append(b"MKSTREAM")
3970 if entries_read is not None:
3971 pieces.extend(["ENTRIESREAD", entries_read])
3973 return self.execute_command(*pieces)
3975 def xgroup_delconsumer(
3976 self, name: KeyT, groupname: GroupT, consumername: ConsumerT
3977 ) -> ResponseT:
3978 """
3979 Remove a specific consumer from a consumer group.
3980 Returns the number of pending messages that the consumer had before it
3981 was deleted.
3982 name: name of the stream.
3983 groupname: name of the consumer group.
3984 consumername: name of consumer to delete
3986 For more information, see https://redis.io/commands/xgroup-delconsumer
3987 """
3988 return self.execute_command("XGROUP DELCONSUMER", name, groupname, consumername)
3990 def xgroup_destroy(self, name: KeyT, groupname: GroupT) -> ResponseT:
3991 """
3992 Destroy a consumer group.
3993 name: name of the stream.
3994 groupname: name of the consumer group.
3996 For more information, see https://redis.io/commands/xgroup-destroy
3997 """
3998 return self.execute_command("XGROUP DESTROY", name, groupname)
4000 def xgroup_createconsumer(
4001 self, name: KeyT, groupname: GroupT, consumername: ConsumerT
4002 ) -> ResponseT:
4003 """
4004 Consumers in a consumer group are auto-created every time a new
4005 consumer name is mentioned by some command.
4006 They can be explicitly created by using this command.
4007 name: name of the stream.
4008 groupname: name of the consumer group.
4009 consumername: name of consumer to create.
4011 See: https://redis.io/commands/xgroup-createconsumer
4012 """
4013 return self.execute_command(
4014 "XGROUP CREATECONSUMER", name, groupname, consumername
4015 )
4017 def xgroup_setid(
4018 self,
4019 name: KeyT,
4020 groupname: GroupT,
4021 id: StreamIdT,
4022 entries_read: Optional[int] = None,
4023 ) -> ResponseT:
4024 """
4025 Set the consumer group last delivered ID to something else.
4026 name: name of the stream.
4027 groupname: name of the consumer group.
4028 id: ID of the last item in the stream to consider already delivered.
4030 For more information, see https://redis.io/commands/xgroup-setid
4031 """
4032 pieces = [name, groupname, id]
4033 if entries_read is not None:
4034 pieces.extend(["ENTRIESREAD", entries_read])
4035 return self.execute_command("XGROUP SETID", *pieces)
4037 def xinfo_consumers(self, name: KeyT, groupname: GroupT) -> ResponseT:
4038 """
4039 Returns general information about the consumers in the group.
4040 name: name of the stream.
4041 groupname: name of the consumer group.
4043 For more information, see https://redis.io/commands/xinfo-consumers
4044 """
4045 return self.execute_command("XINFO CONSUMERS", name, groupname)
4047 def xinfo_groups(self, name: KeyT) -> ResponseT:
4048 """
4049 Returns general information about the consumer groups of the stream.
4050 name: name of the stream.
4052 For more information, see https://redis.io/commands/xinfo-groups
4053 """
4054 return self.execute_command("XINFO GROUPS", name)
4056 def xinfo_stream(self, name: KeyT, full: bool = False) -> ResponseT:
4057 """
4058 Returns general information about the stream.
4059 name: name of the stream.
4060 full: optional boolean, false by default. Return full summary
4062 For more information, see https://redis.io/commands/xinfo-stream
4063 """
4064 pieces = [name]
4065 options = {}
4066 if full:
4067 pieces.append(b"FULL")
4068 options = {"full": full}
4069 return self.execute_command("XINFO STREAM", *pieces, **options)
4071 def xlen(self, name: KeyT) -> ResponseT:
4072 """
4073 Returns the number of elements in a given stream.
4075 For more information, see https://redis.io/commands/xlen
4076 """
4077 return self.execute_command("XLEN", name, keys=[name])
4079 def xpending(self, name: KeyT, groupname: GroupT) -> ResponseT:
4080 """
4081 Returns information about pending messages of a group.
4082 name: name of the stream.
4083 groupname: name of the consumer group.
4085 For more information, see https://redis.io/commands/xpending
4086 """
4087 return self.execute_command("XPENDING", name, groupname, keys=[name])
4089 def xpending_range(
4090 self,
4091 name: KeyT,
4092 groupname: GroupT,
4093 min: StreamIdT,
4094 max: StreamIdT,
4095 count: int,
4096 consumername: Union[ConsumerT, None] = None,
4097 idle: Optional[int] = None,
4098 ) -> ResponseT:
4099 """
4100 Returns information about pending messages, in a range.
4102 name: name of the stream.
4103 groupname: name of the consumer group.
4104 idle: available from version 6.2. filter entries by their
4105 idle-time, given in milliseconds (optional).
4106 min: minimum stream ID.
4107 max: maximum stream ID.
4108 count: number of messages to return
4109 consumername: name of a consumer to filter by (optional).
4110 """
4111 if {min, max, count} == {None}:
4112 if idle is not None or consumername is not None:
4113 raise DataError(
4114 "if XPENDING is provided with idle time"
4115 " or consumername, it must be provided"
4116 " with min, max and count parameters"
4117 )
4118 return self.xpending(name, groupname)
4120 pieces = [name, groupname]
4121 if min is None or max is None or count is None:
4122 raise DataError(
4123 "XPENDING must be provided with min, max "
4124 "and count parameters, or none of them."
4125 )
4126 # idle
4127 try:
4128 if int(idle) < 0:
4129 raise DataError("XPENDING idle must be a integer >= 0")
4130 pieces.extend(["IDLE", idle])
4131 except TypeError:
4132 pass
4133 # count
4134 try:
4135 if int(count) < 0:
4136 raise DataError("XPENDING count must be a integer >= 0")
4137 pieces.extend([min, max, count])
4138 except TypeError:
4139 pass
4140 # consumername
4141 if consumername:
4142 pieces.append(consumername)
4144 return self.execute_command("XPENDING", *pieces, parse_detail=True)
4146 def xrange(
4147 self,
4148 name: KeyT,
4149 min: StreamIdT = "-",
4150 max: StreamIdT = "+",
4151 count: Optional[int] = None,
4152 ) -> ResponseT:
4153 """
4154 Read stream values within an interval.
4156 name: name of the stream.
4158 start: first stream ID. defaults to '-',
4159 meaning the earliest available.
4161 finish: last stream ID. defaults to '+',
4162 meaning the latest available.
4164 count: if set, only return this many items, beginning with the
4165 earliest available.
4167 For more information, see https://redis.io/commands/xrange
4168 """
4169 pieces = [min, max]
4170 if count is not None:
4171 if not isinstance(count, int) or count < 1:
4172 raise DataError("XRANGE count must be a positive integer")
4173 pieces.append(b"COUNT")
4174 pieces.append(str(count))
4176 return self.execute_command("XRANGE", name, *pieces, keys=[name])
4178 def xread(
4179 self,
4180 streams: Dict[KeyT, StreamIdT],
4181 count: Optional[int] = None,
4182 block: Optional[int] = None,
4183 ) -> ResponseT:
4184 """
4185 Block and monitor multiple streams for new data.
4187 streams: a dict of stream names to stream IDs, where
4188 IDs indicate the last ID already seen.
4190 count: if set, only return this many items, beginning with the
4191 earliest available.
4193 block: number of milliseconds to wait, if nothing already present.
4195 For more information, see https://redis.io/commands/xread
4196 """
4197 pieces = []
4198 if block is not None:
4199 if not isinstance(block, int) or block < 0:
4200 raise DataError("XREAD block must be a non-negative integer")
4201 pieces.append(b"BLOCK")
4202 pieces.append(str(block))
4203 if count is not None:
4204 if not isinstance(count, int) or count < 1:
4205 raise DataError("XREAD count must be a positive integer")
4206 pieces.append(b"COUNT")
4207 pieces.append(str(count))
4208 if not isinstance(streams, dict) or len(streams) == 0:
4209 raise DataError("XREAD streams must be a non empty dict")
4210 pieces.append(b"STREAMS")
4211 keys, values = zip(*streams.items())
4212 pieces.extend(keys)
4213 pieces.extend(values)
4214 return self.execute_command("XREAD", *pieces, keys=keys)
4216 def xreadgroup(
4217 self,
4218 groupname: str,
4219 consumername: str,
4220 streams: Dict[KeyT, StreamIdT],
4221 count: Optional[int] = None,
4222 block: Optional[int] = None,
4223 noack: bool = False,
4224 claim_min_idle_time: Optional[int] = None,
4225 ) -> ResponseT:
4226 """
4227 Read from a stream via a consumer group.
4229 groupname: name of the consumer group.
4231 consumername: name of the requesting consumer.
4233 streams: a dict of stream names to stream IDs, where
4234 IDs indicate the last ID already seen.
4236 count: if set, only return this many items, beginning with the
4237 earliest available.
4239 block: number of milliseconds to wait, if nothing already present.
4240 noack: do not add messages to the PEL
4242 claim_min_idle_time: accepts an integer type and represents a
4243 time interval in milliseconds
4245 For more information, see https://redis.io/commands/xreadgroup
4246 """
4247 options = {}
4248 pieces: list[EncodableT] = [b"GROUP", groupname, consumername]
4249 if count is not None:
4250 if not isinstance(count, int) or count < 1:
4251 raise DataError("XREADGROUP count must be a positive integer")
4252 pieces.append(b"COUNT")
4253 pieces.append(str(count))
4254 if block is not None:
4255 if not isinstance(block, int) or block < 0:
4256 raise DataError("XREADGROUP block must be a non-negative integer")
4257 pieces.append(b"BLOCK")
4258 pieces.append(str(block))
4259 if noack:
4260 pieces.append(b"NOACK")
4261 if claim_min_idle_time is not None:
4262 if not isinstance(claim_min_idle_time, int) or claim_min_idle_time < 0:
4263 raise DataError(
4264 "XREADGROUP claim_min_idle_time must be a non-negative integer"
4265 )
4266 pieces.append(b"CLAIM")
4267 pieces.append(claim_min_idle_time)
4268 options["claim_min_idle_time"] = claim_min_idle_time
4269 if not isinstance(streams, dict) or len(streams) == 0:
4270 raise DataError("XREADGROUP streams must be a non empty dict")
4271 pieces.append(b"STREAMS")
4272 pieces.extend(streams.keys())
4273 pieces.extend(streams.values())
4274 return self.execute_command("XREADGROUP", *pieces, **options)
4276 def xrevrange(
4277 self,
4278 name: KeyT,
4279 max: StreamIdT = "+",
4280 min: StreamIdT = "-",
4281 count: Optional[int] = None,
4282 ) -> ResponseT:
4283 """
4284 Read stream values within an interval, in reverse order.
4286 name: name of the stream
4288 start: first stream ID. defaults to '+',
4289 meaning the latest available.
4291 finish: last stream ID. defaults to '-',
4292 meaning the earliest available.
4294 count: if set, only return this many items, beginning with the
4295 latest available.
4297 For more information, see https://redis.io/commands/xrevrange
4298 """
4299 pieces: list[EncodableT] = [max, min]
4300 if count is not None:
4301 if not isinstance(count, int) or count < 1:
4302 raise DataError("XREVRANGE count must be a positive integer")
4303 pieces.append(b"COUNT")
4304 pieces.append(str(count))
4306 return self.execute_command("XREVRANGE", name, *pieces, keys=[name])
4308 def xtrim(
4309 self,
4310 name: KeyT,
4311 maxlen: Optional[int] = None,
4312 approximate: bool = True,
4313 minid: Union[StreamIdT, None] = None,
4314 limit: Optional[int] = None,
4315 ref_policy: Optional[Literal["KEEPREF", "DELREF", "ACKED"]] = None,
4316 ) -> ResponseT:
4317 """
4318 Trims old messages from a stream.
4319 name: name of the stream.
4320 maxlen: truncate old stream messages beyond this size
4321 Can't be specified with minid.
4322 approximate: actual stream length may be slightly more than maxlen
4323 minid: the minimum id in the stream to query
4324 Can't be specified with maxlen.
4325 limit: specifies the maximum number of entries to retrieve
4326 ref_policy: optional reference policy for consumer groups:
4327 - KEEPREF (default): Trims entries but preserves references in consumer groups' PEL
4328 - DELREF: Trims entries and removes all references from consumer groups' PEL
4329 - ACKED: Only trims entries that were read and acknowledged by all consumer groups
4331 For more information, see https://redis.io/commands/xtrim
4332 """
4333 pieces: list[EncodableT] = []
4334 if maxlen is not None and minid is not None:
4335 raise DataError("Only one of ``maxlen`` or ``minid`` may be specified")
4337 if maxlen is None and minid is None:
4338 raise DataError("One of ``maxlen`` or ``minid`` must be specified")
4340 if ref_policy is not None and ref_policy not in {"KEEPREF", "DELREF", "ACKED"}:
4341 raise DataError("XTRIM ref_policy must be one of: KEEPREF, DELREF, ACKED")
4343 if maxlen is not None:
4344 pieces.append(b"MAXLEN")
4345 if minid is not None:
4346 pieces.append(b"MINID")
4347 if approximate:
4348 pieces.append(b"~")
4349 if maxlen is not None:
4350 pieces.append(maxlen)
4351 if minid is not None:
4352 pieces.append(minid)
4353 if limit is not None:
4354 pieces.append(b"LIMIT")
4355 pieces.append(limit)
4356 if ref_policy is not None:
4357 pieces.append(ref_policy)
4359 return self.execute_command("XTRIM", name, *pieces)
4362AsyncStreamCommands = StreamCommands
4365class SortedSetCommands(CommandsProtocol):
4366 """
4367 Redis commands for Sorted Sets data type.
4368 see: https://redis.io/topics/data-types-intro#redis-sorted-sets
4369 """
4371 def zadd(
4372 self,
4373 name: KeyT,
4374 mapping: Mapping[AnyKeyT, EncodableT],
4375 nx: bool = False,
4376 xx: bool = False,
4377 ch: bool = False,
4378 incr: bool = False,
4379 gt: bool = False,
4380 lt: bool = False,
4381 ) -> ResponseT:
4382 """
4383 Set any number of element-name, score pairs to the key ``name``. Pairs
4384 are specified as a dict of element-names keys to score values.
4386 ``nx`` forces ZADD to only create new elements and not to update
4387 scores for elements that already exist.
4389 ``xx`` forces ZADD to only update scores of elements that already
4390 exist. New elements will not be added.
4392 ``ch`` modifies the return value to be the numbers of elements changed.
4393 Changed elements include new elements that were added and elements
4394 whose scores changed.
4396 ``incr`` modifies ZADD to behave like ZINCRBY. In this mode only a
4397 single element/score pair can be specified and the score is the amount
4398 the existing score will be incremented by. When using this mode the
4399 return value of ZADD will be the new score of the element.
4401 ``lt`` only updates existing elements if the new score is less than
4402 the current score. This flag doesn't prevent adding new elements.
4404 ``gt`` only updates existing elements if the new score is greater than
4405 the current score. This flag doesn't prevent adding new elements.
4407 The return value of ZADD varies based on the mode specified. With no
4408 options, ZADD returns the number of new elements added to the sorted
4409 set.
4411 ``nx``, ``lt``, and ``gt`` are mutually exclusive options.
4413 See: https://redis.io/commands/ZADD
4414 """
4415 if not mapping:
4416 raise DataError("ZADD requires at least one element/score pair")
4417 if nx and xx:
4418 raise DataError("ZADD allows either 'nx' or 'xx', not both")
4419 if gt and lt:
4420 raise DataError("ZADD allows either 'gt' or 'lt', not both")
4421 if incr and len(mapping) != 1:
4422 raise DataError(
4423 "ZADD option 'incr' only works when passing a single element/score pair"
4424 )
4425 if nx and (gt or lt):
4426 raise DataError("Only one of 'nx', 'lt', or 'gr' may be defined.")
4428 pieces: list[EncodableT] = []
4429 options = {}
4430 if nx:
4431 pieces.append(b"NX")
4432 if xx:
4433 pieces.append(b"XX")
4434 if ch:
4435 pieces.append(b"CH")
4436 if incr:
4437 pieces.append(b"INCR")
4438 options["as_score"] = True
4439 if gt:
4440 pieces.append(b"GT")
4441 if lt:
4442 pieces.append(b"LT")
4443 for pair in mapping.items():
4444 pieces.append(pair[1])
4445 pieces.append(pair[0])
4446 return self.execute_command("ZADD", name, *pieces, **options)
4448 def zcard(self, name: KeyT) -> ResponseT:
4449 """
4450 Return the number of elements in the sorted set ``name``
4452 For more information, see https://redis.io/commands/zcard
4453 """
4454 return self.execute_command("ZCARD", name, keys=[name])
4456 def zcount(self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT) -> ResponseT:
4457 """
4458 Returns the number of elements in the sorted set at key ``name`` with
4459 a score between ``min`` and ``max``.
4461 For more information, see https://redis.io/commands/zcount
4462 """
4463 return self.execute_command("ZCOUNT", name, min, max, keys=[name])
4465 def zdiff(self, keys: KeysT, withscores: bool = False) -> ResponseT:
4466 """
4467 Returns the difference between the first and all successive input
4468 sorted sets provided in ``keys``.
4470 For more information, see https://redis.io/commands/zdiff
4471 """
4472 pieces = [len(keys), *keys]
4473 if withscores:
4474 pieces.append("WITHSCORES")
4475 return self.execute_command("ZDIFF", *pieces, keys=keys)
4477 def zdiffstore(self, dest: KeyT, keys: KeysT) -> ResponseT:
4478 """
4479 Computes the difference between the first and all successive input
4480 sorted sets provided in ``keys`` and stores the result in ``dest``.
4482 For more information, see https://redis.io/commands/zdiffstore
4483 """
4484 pieces = [len(keys), *keys]
4485 return self.execute_command("ZDIFFSTORE", dest, *pieces)
4487 def zincrby(self, name: KeyT, amount: float, value: EncodableT) -> ResponseT:
4488 """
4489 Increment the score of ``value`` in sorted set ``name`` by ``amount``
4491 For more information, see https://redis.io/commands/zincrby
4492 """
4493 return self.execute_command("ZINCRBY", name, amount, value)
4495 def zinter(
4496 self, keys: KeysT, aggregate: Optional[str] = None, withscores: bool = False
4497 ) -> ResponseT:
4498 """
4499 Return the intersect of multiple sorted sets specified by ``keys``.
4500 With the ``aggregate`` option, it is possible to specify how the
4501 results of the union are aggregated. This option defaults to SUM,
4502 where the score of an element is summed across the inputs where it
4503 exists. When this option is set to either MIN or MAX, the resulting
4504 set will contain the minimum or maximum score of an element across
4505 the inputs where it exists.
4507 For more information, see https://redis.io/commands/zinter
4508 """
4509 return self._zaggregate("ZINTER", None, keys, aggregate, withscores=withscores)
4511 def zinterstore(
4512 self,
4513 dest: KeyT,
4514 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
4515 aggregate: Optional[str] = None,
4516 ) -> ResponseT:
4517 """
4518 Intersect multiple sorted sets specified by ``keys`` into a new
4519 sorted set, ``dest``. Scores in the destination will be aggregated
4520 based on the ``aggregate``. This option defaults to SUM, where the
4521 score of an element is summed across the inputs where it exists.
4522 When this option is set to either MIN or MAX, the resulting set will
4523 contain the minimum or maximum score of an element across the inputs
4524 where it exists.
4526 For more information, see https://redis.io/commands/zinterstore
4527 """
4528 return self._zaggregate("ZINTERSTORE", dest, keys, aggregate)
4530 def zintercard(
4531 self, numkeys: int, keys: List[str], limit: int = 0
4532 ) -> Union[Awaitable[int], int]:
4533 """
4534 Return the cardinality of the intersect of multiple sorted sets
4535 specified by ``keys``.
4536 When LIMIT provided (defaults to 0 and means unlimited), if the intersection
4537 cardinality reaches limit partway through the computation, the algorithm will
4538 exit and yield limit as the cardinality
4540 For more information, see https://redis.io/commands/zintercard
4541 """
4542 args = [numkeys, *keys, "LIMIT", limit]
4543 return self.execute_command("ZINTERCARD", *args, keys=keys)
4545 def zlexcount(self, name, min, max):
4546 """
4547 Return the number of items in the sorted set ``name`` between the
4548 lexicographical range ``min`` and ``max``.
4550 For more information, see https://redis.io/commands/zlexcount
4551 """
4552 return self.execute_command("ZLEXCOUNT", name, min, max, keys=[name])
4554 def zpopmax(self, name: KeyT, count: Optional[int] = None) -> ResponseT:
4555 """
4556 Remove and return up to ``count`` members with the highest scores
4557 from the sorted set ``name``.
4559 For more information, see https://redis.io/commands/zpopmax
4560 """
4561 args = (count is not None) and [count] or []
4562 options = {"withscores": True}
4563 return self.execute_command("ZPOPMAX", name, *args, **options)
4565 def zpopmin(self, name: KeyT, count: Optional[int] = None) -> ResponseT:
4566 """
4567 Remove and return up to ``count`` members with the lowest scores
4568 from the sorted set ``name``.
4570 For more information, see https://redis.io/commands/zpopmin
4571 """
4572 args = (count is not None) and [count] or []
4573 options = {"withscores": True}
4574 return self.execute_command("ZPOPMIN", name, *args, **options)
4576 def zrandmember(
4577 self, key: KeyT, count: Optional[int] = None, withscores: bool = False
4578 ) -> ResponseT:
4579 """
4580 Return a random element from the sorted set value stored at key.
4582 ``count`` if the argument is positive, return an array of distinct
4583 fields. If called with a negative count, the behavior changes and
4584 the command is allowed to return the same field multiple times.
4585 In this case, the number of returned fields is the absolute value
4586 of the specified count.
4588 ``withscores`` The optional WITHSCORES modifier changes the reply so it
4589 includes the respective scores of the randomly selected elements from
4590 the sorted set.
4592 For more information, see https://redis.io/commands/zrandmember
4593 """
4594 params = []
4595 if count is not None:
4596 params.append(count)
4597 if withscores:
4598 params.append("WITHSCORES")
4600 return self.execute_command("ZRANDMEMBER", key, *params)
4602 def bzpopmax(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT:
4603 """
4604 ZPOPMAX a value off of the first non-empty sorted set
4605 named in the ``keys`` list.
4607 If none of the sorted sets in ``keys`` has a value to ZPOPMAX,
4608 then block for ``timeout`` seconds, or until a member gets added
4609 to one of the sorted sets.
4611 If timeout is 0, then block indefinitely.
4613 For more information, see https://redis.io/commands/bzpopmax
4614 """
4615 if timeout is None:
4616 timeout = 0
4617 keys = list_or_args(keys, None)
4618 keys.append(timeout)
4619 return self.execute_command("BZPOPMAX", *keys)
4621 def bzpopmin(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT:
4622 """
4623 ZPOPMIN a value off of the first non-empty sorted set
4624 named in the ``keys`` list.
4626 If none of the sorted sets in ``keys`` has a value to ZPOPMIN,
4627 then block for ``timeout`` seconds, or until a member gets added
4628 to one of the sorted sets.
4630 If timeout is 0, then block indefinitely.
4632 For more information, see https://redis.io/commands/bzpopmin
4633 """
4634 if timeout is None:
4635 timeout = 0
4636 keys: list[EncodableT] = list_or_args(keys, None)
4637 keys.append(timeout)
4638 return self.execute_command("BZPOPMIN", *keys)
4640 def zmpop(
4641 self,
4642 num_keys: int,
4643 keys: List[str],
4644 min: Optional[bool] = False,
4645 max: Optional[bool] = False,
4646 count: Optional[int] = 1,
4647 ) -> Union[Awaitable[list], list]:
4648 """
4649 Pop ``count`` values (default 1) off of the first non-empty sorted set
4650 named in the ``keys`` list.
4651 For more information, see https://redis.io/commands/zmpop
4652 """
4653 args = [num_keys] + keys
4654 if (min and max) or (not min and not max):
4655 raise DataError
4656 elif min:
4657 args.append("MIN")
4658 else:
4659 args.append("MAX")
4660 if count != 1:
4661 args.extend(["COUNT", count])
4663 return self.execute_command("ZMPOP", *args)
4665 def bzmpop(
4666 self,
4667 timeout: float,
4668 numkeys: int,
4669 keys: List[str],
4670 min: Optional[bool] = False,
4671 max: Optional[bool] = False,
4672 count: Optional[int] = 1,
4673 ) -> Optional[list]:
4674 """
4675 Pop ``count`` values (default 1) off of the first non-empty sorted set
4676 named in the ``keys`` list.
4678 If none of the sorted sets in ``keys`` has a value to pop,
4679 then block for ``timeout`` seconds, or until a member gets added
4680 to one of the sorted sets.
4682 If timeout is 0, then block indefinitely.
4684 For more information, see https://redis.io/commands/bzmpop
4685 """
4686 args = [timeout, numkeys, *keys]
4687 if (min and max) or (not min and not max):
4688 raise DataError("Either min or max, but not both must be set")
4689 elif min:
4690 args.append("MIN")
4691 else:
4692 args.append("MAX")
4693 args.extend(["COUNT", count])
4695 return self.execute_command("BZMPOP", *args)
4697 def _zrange(
4698 self,
4699 command,
4700 dest: Union[KeyT, None],
4701 name: KeyT,
4702 start: int,
4703 end: int,
4704 desc: bool = False,
4705 byscore: bool = False,
4706 bylex: bool = False,
4707 withscores: bool = False,
4708 score_cast_func: Union[type, Callable, None] = float,
4709 offset: Optional[int] = None,
4710 num: Optional[int] = None,
4711 ) -> ResponseT:
4712 if byscore and bylex:
4713 raise DataError("``byscore`` and ``bylex`` can not be specified together.")
4714 if (offset is not None and num is None) or (num is not None and offset is None):
4715 raise DataError("``offset`` and ``num`` must both be specified.")
4716 if bylex and withscores:
4717 raise DataError(
4718 "``withscores`` not supported in combination with ``bylex``."
4719 )
4720 pieces = [command]
4721 if dest:
4722 pieces.append(dest)
4723 pieces.extend([name, start, end])
4724 if byscore:
4725 pieces.append("BYSCORE")
4726 if bylex:
4727 pieces.append("BYLEX")
4728 if desc:
4729 pieces.append("REV")
4730 if offset is not None and num is not None:
4731 pieces.extend(["LIMIT", offset, num])
4732 if withscores:
4733 pieces.append("WITHSCORES")
4734 options = {"withscores": withscores, "score_cast_func": score_cast_func}
4735 options["keys"] = [name]
4736 return self.execute_command(*pieces, **options)
4738 def zrange(
4739 self,
4740 name: KeyT,
4741 start: int,
4742 end: int,
4743 desc: bool = False,
4744 withscores: bool = False,
4745 score_cast_func: Union[type, Callable] = float,
4746 byscore: bool = False,
4747 bylex: bool = False,
4748 offset: Optional[int] = None,
4749 num: Optional[int] = None,
4750 ) -> ResponseT:
4751 """
4752 Return a range of values from sorted set ``name`` between
4753 ``start`` and ``end`` sorted in ascending order.
4755 ``start`` and ``end`` can be negative, indicating the end of the range.
4757 ``desc`` a boolean indicating whether to sort the results in reversed
4758 order.
4760 ``withscores`` indicates to return the scores along with the values.
4761 The return type is a list of (value, score) pairs.
4763 ``score_cast_func`` a callable used to cast the score return value.
4765 ``byscore`` when set to True, returns the range of elements from the
4766 sorted set having scores equal or between ``start`` and ``end``.
4768 ``bylex`` when set to True, returns the range of elements from the
4769 sorted set between the ``start`` and ``end`` lexicographical closed
4770 range intervals.
4771 Valid ``start`` and ``end`` must start with ( or [, in order to specify
4772 whether the range interval is exclusive or inclusive, respectively.
4774 ``offset`` and ``num`` are specified, then return a slice of the range.
4775 Can't be provided when using ``bylex``.
4777 For more information, see https://redis.io/commands/zrange
4778 """
4779 # Need to support ``desc`` also when using old redis version
4780 # because it was supported in 3.5.3 (of redis-py)
4781 if not byscore and not bylex and (offset is None and num is None) and desc:
4782 return self.zrevrange(name, start, end, withscores, score_cast_func)
4784 return self._zrange(
4785 "ZRANGE",
4786 None,
4787 name,
4788 start,
4789 end,
4790 desc,
4791 byscore,
4792 bylex,
4793 withscores,
4794 score_cast_func,
4795 offset,
4796 num,
4797 )
4799 def zrevrange(
4800 self,
4801 name: KeyT,
4802 start: int,
4803 end: int,
4804 withscores: bool = False,
4805 score_cast_func: Union[type, Callable] = float,
4806 ) -> ResponseT:
4807 """
4808 Return a range of values from sorted set ``name`` between
4809 ``start`` and ``end`` sorted in descending order.
4811 ``start`` and ``end`` can be negative, indicating the end of the range.
4813 ``withscores`` indicates to return the scores along with the values
4814 The return type is a list of (value, score) pairs
4816 ``score_cast_func`` a callable used to cast the score return value
4818 For more information, see https://redis.io/commands/zrevrange
4819 """
4820 pieces = ["ZREVRANGE", name, start, end]
4821 if withscores:
4822 pieces.append(b"WITHSCORES")
4823 options = {"withscores": withscores, "score_cast_func": score_cast_func}
4824 options["keys"] = name
4825 return self.execute_command(*pieces, **options)
4827 def zrangestore(
4828 self,
4829 dest: KeyT,
4830 name: KeyT,
4831 start: int,
4832 end: int,
4833 byscore: bool = False,
4834 bylex: bool = False,
4835 desc: bool = False,
4836 offset: Optional[int] = None,
4837 num: Optional[int] = None,
4838 ) -> ResponseT:
4839 """
4840 Stores in ``dest`` the result of a range of values from sorted set
4841 ``name`` between ``start`` and ``end`` sorted in ascending order.
4843 ``start`` and ``end`` can be negative, indicating the end of the range.
4845 ``byscore`` when set to True, returns the range of elements from the
4846 sorted set having scores equal or between ``start`` and ``end``.
4848 ``bylex`` when set to True, returns the range of elements from the
4849 sorted set between the ``start`` and ``end`` lexicographical closed
4850 range intervals.
4851 Valid ``start`` and ``end`` must start with ( or [, in order to specify
4852 whether the range interval is exclusive or inclusive, respectively.
4854 ``desc`` a boolean indicating whether to sort the results in reversed
4855 order.
4857 ``offset`` and ``num`` are specified, then return a slice of the range.
4858 Can't be provided when using ``bylex``.
4860 For more information, see https://redis.io/commands/zrangestore
4861 """
4862 return self._zrange(
4863 "ZRANGESTORE",
4864 dest,
4865 name,
4866 start,
4867 end,
4868 desc,
4869 byscore,
4870 bylex,
4871 False,
4872 None,
4873 offset,
4874 num,
4875 )
4877 def zrangebylex(
4878 self,
4879 name: KeyT,
4880 min: EncodableT,
4881 max: EncodableT,
4882 start: Optional[int] = None,
4883 num: Optional[int] = None,
4884 ) -> ResponseT:
4885 """
4886 Return the lexicographical range of values from sorted set ``name``
4887 between ``min`` and ``max``.
4889 If ``start`` and ``num`` are specified, then return a slice of the
4890 range.
4892 For more information, see https://redis.io/commands/zrangebylex
4893 """
4894 if (start is not None and num is None) or (num is not None and start is None):
4895 raise DataError("``start`` and ``num`` must both be specified")
4896 pieces = ["ZRANGEBYLEX", name, min, max]
4897 if start is not None and num is not None:
4898 pieces.extend([b"LIMIT", start, num])
4899 return self.execute_command(*pieces, keys=[name])
4901 def zrevrangebylex(
4902 self,
4903 name: KeyT,
4904 max: EncodableT,
4905 min: EncodableT,
4906 start: Optional[int] = None,
4907 num: Optional[int] = None,
4908 ) -> ResponseT:
4909 """
4910 Return the reversed lexicographical range of values from sorted set
4911 ``name`` between ``max`` and ``min``.
4913 If ``start`` and ``num`` are specified, then return a slice of the
4914 range.
4916 For more information, see https://redis.io/commands/zrevrangebylex
4917 """
4918 if (start is not None and num is None) or (num is not None and start is None):
4919 raise DataError("``start`` and ``num`` must both be specified")
4920 pieces = ["ZREVRANGEBYLEX", name, max, min]
4921 if start is not None and num is not None:
4922 pieces.extend(["LIMIT", start, num])
4923 return self.execute_command(*pieces, keys=[name])
4925 def zrangebyscore(
4926 self,
4927 name: KeyT,
4928 min: ZScoreBoundT,
4929 max: ZScoreBoundT,
4930 start: Optional[int] = None,
4931 num: Optional[int] = None,
4932 withscores: bool = False,
4933 score_cast_func: Union[type, Callable] = float,
4934 ) -> ResponseT:
4935 """
4936 Return a range of values from the sorted set ``name`` with scores
4937 between ``min`` and ``max``.
4939 If ``start`` and ``num`` are specified, then return a slice
4940 of the range.
4942 ``withscores`` indicates to return the scores along with the values.
4943 The return type is a list of (value, score) pairs
4945 `score_cast_func`` a callable used to cast the score return value
4947 For more information, see https://redis.io/commands/zrangebyscore
4948 """
4949 if (start is not None and num is None) or (num is not None and start is None):
4950 raise DataError("``start`` and ``num`` must both be specified")
4951 pieces = ["ZRANGEBYSCORE", name, min, max]
4952 if start is not None and num is not None:
4953 pieces.extend(["LIMIT", start, num])
4954 if withscores:
4955 pieces.append("WITHSCORES")
4956 options = {"withscores": withscores, "score_cast_func": score_cast_func}
4957 options["keys"] = [name]
4958 return self.execute_command(*pieces, **options)
4960 def zrevrangebyscore(
4961 self,
4962 name: KeyT,
4963 max: ZScoreBoundT,
4964 min: ZScoreBoundT,
4965 start: Optional[int] = None,
4966 num: Optional[int] = None,
4967 withscores: bool = False,
4968 score_cast_func: Union[type, Callable] = float,
4969 ):
4970 """
4971 Return a range of values from the sorted set ``name`` with scores
4972 between ``min`` and ``max`` in descending order.
4974 If ``start`` and ``num`` are specified, then return a slice
4975 of the range.
4977 ``withscores`` indicates to return the scores along with the values.
4978 The return type is a list of (value, score) pairs
4980 ``score_cast_func`` a callable used to cast the score return value
4982 For more information, see https://redis.io/commands/zrevrangebyscore
4983 """
4984 if (start is not None and num is None) or (num is not None and start is None):
4985 raise DataError("``start`` and ``num`` must both be specified")
4986 pieces = ["ZREVRANGEBYSCORE", name, max, min]
4987 if start is not None and num is not None:
4988 pieces.extend(["LIMIT", start, num])
4989 if withscores:
4990 pieces.append("WITHSCORES")
4991 options = {"withscores": withscores, "score_cast_func": score_cast_func}
4992 options["keys"] = [name]
4993 return self.execute_command(*pieces, **options)
4995 def zrank(
4996 self,
4997 name: KeyT,
4998 value: EncodableT,
4999 withscore: bool = False,
5000 score_cast_func: Union[type, Callable] = float,
5001 ) -> ResponseT:
5002 """
5003 Returns a 0-based value indicating the rank of ``value`` in sorted set
5004 ``name``.
5005 The optional WITHSCORE argument supplements the command's
5006 reply with the score of the element returned.
5008 ``score_cast_func`` a callable used to cast the score return value
5010 For more information, see https://redis.io/commands/zrank
5011 """
5012 pieces = ["ZRANK", name, value]
5013 if withscore:
5014 pieces.append("WITHSCORE")
5016 options = {"withscore": withscore, "score_cast_func": score_cast_func}
5018 return self.execute_command(*pieces, **options)
5020 def zrem(self, name: KeyT, *values: FieldT) -> ResponseT:
5021 """
5022 Remove member ``values`` from sorted set ``name``
5024 For more information, see https://redis.io/commands/zrem
5025 """
5026 return self.execute_command("ZREM", name, *values)
5028 def zremrangebylex(self, name: KeyT, min: EncodableT, max: EncodableT) -> ResponseT:
5029 """
5030 Remove all elements in the sorted set ``name`` between the
5031 lexicographical range specified by ``min`` and ``max``.
5033 Returns the number of elements removed.
5035 For more information, see https://redis.io/commands/zremrangebylex
5036 """
5037 return self.execute_command("ZREMRANGEBYLEX", name, min, max)
5039 def zremrangebyrank(self, name: KeyT, min: int, max: int) -> ResponseT:
5040 """
5041 Remove all elements in the sorted set ``name`` with ranks between
5042 ``min`` and ``max``. Values are 0-based, ordered from smallest score
5043 to largest. Values can be negative indicating the highest scores.
5044 Returns the number of elements removed
5046 For more information, see https://redis.io/commands/zremrangebyrank
5047 """
5048 return self.execute_command("ZREMRANGEBYRANK", name, min, max)
5050 def zremrangebyscore(
5051 self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT
5052 ) -> ResponseT:
5053 """
5054 Remove all elements in the sorted set ``name`` with scores
5055 between ``min`` and ``max``. Returns the number of elements removed.
5057 For more information, see https://redis.io/commands/zremrangebyscore
5058 """
5059 return self.execute_command("ZREMRANGEBYSCORE", name, min, max)
5061 def zrevrank(
5062 self,
5063 name: KeyT,
5064 value: EncodableT,
5065 withscore: bool = False,
5066 score_cast_func: Union[type, Callable] = float,
5067 ) -> ResponseT:
5068 """
5069 Returns a 0-based value indicating the descending rank of
5070 ``value`` in sorted set ``name``.
5071 The optional ``withscore`` argument supplements the command's
5072 reply with the score of the element returned.
5074 ``score_cast_func`` a callable used to cast the score return value
5076 For more information, see https://redis.io/commands/zrevrank
5077 """
5078 pieces = ["ZREVRANK", name, value]
5079 if withscore:
5080 pieces.append("WITHSCORE")
5082 options = {"withscore": withscore, "score_cast_func": score_cast_func}
5084 return self.execute_command(*pieces, **options)
5086 def zscore(self, name: KeyT, value: EncodableT) -> ResponseT:
5087 """
5088 Return the score of element ``value`` in sorted set ``name``
5090 For more information, see https://redis.io/commands/zscore
5091 """
5092 return self.execute_command("ZSCORE", name, value, keys=[name])
5094 def zunion(
5095 self,
5096 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
5097 aggregate: Optional[str] = None,
5098 withscores: bool = False,
5099 score_cast_func: Union[type, Callable] = float,
5100 ) -> ResponseT:
5101 """
5102 Return the union of multiple sorted sets specified by ``keys``.
5103 ``keys`` can be provided as dictionary of keys and their weights.
5104 Scores will be aggregated based on the ``aggregate``, or SUM if
5105 none is provided.
5107 ``score_cast_func`` a callable used to cast the score return value
5109 For more information, see https://redis.io/commands/zunion
5110 """
5111 return self._zaggregate(
5112 "ZUNION",
5113 None,
5114 keys,
5115 aggregate,
5116 withscores=withscores,
5117 score_cast_func=score_cast_func,
5118 )
5120 def zunionstore(
5121 self,
5122 dest: KeyT,
5123 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
5124 aggregate: Optional[str] = None,
5125 ) -> ResponseT:
5126 """
5127 Union multiple sorted sets specified by ``keys`` into
5128 a new sorted set, ``dest``. Scores in the destination will be
5129 aggregated based on the ``aggregate``, or SUM if none is provided.
5131 For more information, see https://redis.io/commands/zunionstore
5132 """
5133 return self._zaggregate("ZUNIONSTORE", dest, keys, aggregate)
5135 def zmscore(self, key: KeyT, members: List[str]) -> ResponseT:
5136 """
5137 Returns the scores associated with the specified members
5138 in the sorted set stored at key.
5139 ``members`` should be a list of the member name.
5140 Return type is a list of score.
5141 If the member does not exist, a None will be returned
5142 in corresponding position.
5144 For more information, see https://redis.io/commands/zmscore
5145 """
5146 if not members:
5147 raise DataError("ZMSCORE members must be a non-empty list")
5148 pieces = [key] + members
5149 return self.execute_command("ZMSCORE", *pieces, keys=[key])
5151 def _zaggregate(
5152 self,
5153 command: str,
5154 dest: Union[KeyT, None],
5155 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
5156 aggregate: Optional[str] = None,
5157 **options,
5158 ) -> ResponseT:
5159 pieces: list[EncodableT] = [command]
5160 if dest is not None:
5161 pieces.append(dest)
5162 pieces.append(len(keys))
5163 if isinstance(keys, dict):
5164 keys, weights = keys.keys(), keys.values()
5165 else:
5166 weights = None
5167 pieces.extend(keys)
5168 if weights:
5169 pieces.append(b"WEIGHTS")
5170 pieces.extend(weights)
5171 if aggregate:
5172 if aggregate.upper() in ["SUM", "MIN", "MAX"]:
5173 pieces.append(b"AGGREGATE")
5174 pieces.append(aggregate)
5175 else:
5176 raise DataError("aggregate can be sum, min or max.")
5177 if options.get("withscores", False):
5178 pieces.append(b"WITHSCORES")
5179 options["keys"] = keys
5180 return self.execute_command(*pieces, **options)
5183AsyncSortedSetCommands = SortedSetCommands
5186class HyperlogCommands(CommandsProtocol):
5187 """
5188 Redis commands of HyperLogLogs data type.
5189 see: https://redis.io/topics/data-types-intro#hyperloglogs
5190 """
5192 def pfadd(self, name: KeyT, *values: FieldT) -> ResponseT:
5193 """
5194 Adds the specified elements to the specified HyperLogLog.
5196 For more information, see https://redis.io/commands/pfadd
5197 """
5198 return self.execute_command("PFADD", name, *values)
5200 def pfcount(self, *sources: KeyT) -> ResponseT:
5201 """
5202 Return the approximated cardinality of
5203 the set observed by the HyperLogLog at key(s).
5205 For more information, see https://redis.io/commands/pfcount
5206 """
5207 return self.execute_command("PFCOUNT", *sources)
5209 def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT:
5210 """
5211 Merge N different HyperLogLogs into a single one.
5213 For more information, see https://redis.io/commands/pfmerge
5214 """
5215 return self.execute_command("PFMERGE", dest, *sources)
5218AsyncHyperlogCommands = HyperlogCommands
5221class HashDataPersistOptions(Enum):
5222 # set the value for each provided key to each
5223 # provided value only if all do not already exist.
5224 FNX = "FNX"
5226 # set the value for each provided key to each
5227 # provided value only if all already exist.
5228 FXX = "FXX"
5231class HashCommands(CommandsProtocol):
5232 """
5233 Redis commands for Hash data type.
5234 see: https://redis.io/topics/data-types-intro#redis-hashes
5235 """
5237 def hdel(self, name: str, *keys: str) -> Union[Awaitable[int], int]:
5238 """
5239 Delete ``keys`` from hash ``name``
5241 For more information, see https://redis.io/commands/hdel
5242 """
5243 return self.execute_command("HDEL", name, *keys)
5245 def hexists(self, name: str, key: str) -> Union[Awaitable[bool], bool]:
5246 """
5247 Returns a boolean indicating if ``key`` exists within hash ``name``
5249 For more information, see https://redis.io/commands/hexists
5250 """
5251 return self.execute_command("HEXISTS", name, key, keys=[name])
5253 def hget(
5254 self, name: str, key: str
5255 ) -> Union[Awaitable[Optional[str]], Optional[str]]:
5256 """
5257 Return the value of ``key`` within the hash ``name``
5259 For more information, see https://redis.io/commands/hget
5260 """
5261 return self.execute_command("HGET", name, key, keys=[name])
5263 def hgetall(self, name: str) -> Union[Awaitable[dict], dict]:
5264 """
5265 Return a Python dict of the hash's name/value pairs
5267 For more information, see https://redis.io/commands/hgetall
5268 """
5269 return self.execute_command("HGETALL", name, keys=[name])
5271 def hgetdel(
5272 self, name: str, *keys: str
5273 ) -> Union[
5274 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]]
5275 ]:
5276 """
5277 Return the value of ``key`` within the hash ``name`` and
5278 delete the field in the hash.
5279 This command is similar to HGET, except for the fact that it also deletes
5280 the key on success from the hash with the provided ```name```.
5282 Available since Redis 8.0
5283 For more information, see https://redis.io/commands/hgetdel
5284 """
5285 if len(keys) == 0:
5286 raise DataError("'hgetdel' should have at least one key provided")
5288 return self.execute_command("HGETDEL", name, "FIELDS", len(keys), *keys)
5290 def hgetex(
5291 self,
5292 name: KeyT,
5293 *keys: str,
5294 ex: Optional[ExpiryT] = None,
5295 px: Optional[ExpiryT] = None,
5296 exat: Optional[AbsExpiryT] = None,
5297 pxat: Optional[AbsExpiryT] = None,
5298 persist: bool = False,
5299 ) -> Union[
5300 Awaitable[Optional[List[Union[str, bytes]]]], Optional[List[Union[str, bytes]]]
5301 ]:
5302 """
5303 Return the values of ``key`` and ``keys`` within the hash ``name``
5304 and optionally set their expiration.
5306 ``ex`` sets an expire flag on ``kyes`` for ``ex`` seconds.
5308 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds.
5310 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds,
5311 specified in unix time.
5313 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds,
5314 specified in unix time.
5316 ``persist`` remove the time to live associated with the ``keys``.
5318 Available since Redis 8.0
5319 For more information, see https://redis.io/commands/hgetex
5320 """
5321 if not keys:
5322 raise DataError("'hgetex' should have at least one key provided")
5324 if not at_most_one_value_set((ex, px, exat, pxat, persist)):
5325 raise DataError(
5326 "``ex``, ``px``, ``exat``, ``pxat``, "
5327 "and ``persist`` are mutually exclusive."
5328 )
5330 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat)
5332 if persist:
5333 exp_options.append("PERSIST")
5335 return self.execute_command(
5336 "HGETEX",
5337 name,
5338 *exp_options,
5339 "FIELDS",
5340 len(keys),
5341 *keys,
5342 )
5344 def hincrby(
5345 self, name: str, key: str, amount: int = 1
5346 ) -> Union[Awaitable[int], int]:
5347 """
5348 Increment the value of ``key`` in hash ``name`` by ``amount``
5350 For more information, see https://redis.io/commands/hincrby
5351 """
5352 return self.execute_command("HINCRBY", name, key, amount)
5354 def hincrbyfloat(
5355 self, name: str, key: str, amount: float = 1.0
5356 ) -> Union[Awaitable[float], float]:
5357 """
5358 Increment the value of ``key`` in hash ``name`` by floating ``amount``
5360 For more information, see https://redis.io/commands/hincrbyfloat
5361 """
5362 return self.execute_command("HINCRBYFLOAT", name, key, amount)
5364 def hkeys(self, name: str) -> Union[Awaitable[List], List]:
5365 """
5366 Return the list of keys within hash ``name``
5368 For more information, see https://redis.io/commands/hkeys
5369 """
5370 return self.execute_command("HKEYS", name, keys=[name])
5372 def hlen(self, name: str) -> Union[Awaitable[int], int]:
5373 """
5374 Return the number of elements in hash ``name``
5376 For more information, see https://redis.io/commands/hlen
5377 """
5378 return self.execute_command("HLEN", name, keys=[name])
5380 def hset(
5381 self,
5382 name: str,
5383 key: Optional[str] = None,
5384 value: Optional[str] = None,
5385 mapping: Optional[dict] = None,
5386 items: Optional[list] = None,
5387 ) -> Union[Awaitable[int], int]:
5388 """
5389 Set ``key`` to ``value`` within hash ``name``,
5390 ``mapping`` accepts a dict of key/value pairs that will be
5391 added to hash ``name``.
5392 ``items`` accepts a list of key/value pairs that will be
5393 added to hash ``name``.
5394 Returns the number of fields that were added.
5396 For more information, see https://redis.io/commands/hset
5397 """
5399 if key is None and not mapping and not items:
5400 raise DataError("'hset' with no key value pairs")
5402 pieces = []
5403 if items:
5404 pieces.extend(items)
5405 if key is not None:
5406 pieces.extend((key, value))
5407 if mapping:
5408 for pair in mapping.items():
5409 pieces.extend(pair)
5411 return self.execute_command("HSET", name, *pieces)
5413 def hsetex(
5414 self,
5415 name: str,
5416 key: Optional[str] = None,
5417 value: Optional[str] = None,
5418 mapping: Optional[dict] = None,
5419 items: Optional[list] = None,
5420 ex: Optional[ExpiryT] = None,
5421 px: Optional[ExpiryT] = None,
5422 exat: Optional[AbsExpiryT] = None,
5423 pxat: Optional[AbsExpiryT] = None,
5424 data_persist_option: Optional[HashDataPersistOptions] = None,
5425 keepttl: bool = False,
5426 ) -> Union[Awaitable[int], int]:
5427 """
5428 Set ``key`` to ``value`` within hash ``name``
5430 ``mapping`` accepts a dict of key/value pairs that will be
5431 added to hash ``name``.
5433 ``items`` accepts a list of key/value pairs that will be
5434 added to hash ``name``.
5436 ``ex`` sets an expire flag on ``keys`` for ``ex`` seconds.
5438 ``px`` sets an expire flag on ``keys`` for ``px`` milliseconds.
5440 ``exat`` sets an expire flag on ``keys`` for ``ex`` seconds,
5441 specified in unix time.
5443 ``pxat`` sets an expire flag on ``keys`` for ``ex`` milliseconds,
5444 specified in unix time.
5446 ``data_persist_option`` can be set to ``FNX`` or ``FXX`` to control the
5447 behavior of the command.
5448 ``FNX`` will set the value for each provided key to each
5449 provided value only if all do not already exist.
5450 ``FXX`` will set the value for each provided key to each
5451 provided value only if all already exist.
5453 ``keepttl`` if True, retain the time to live associated with the keys.
5455 Returns the number of fields that were added.
5457 Available since Redis 8.0
5458 For more information, see https://redis.io/commands/hsetex
5459 """
5460 if key is None and not mapping and not items:
5461 raise DataError("'hsetex' with no key value pairs")
5463 if items and len(items) % 2 != 0:
5464 raise DataError(
5465 "'hsetex' with odd number of items. "
5466 "'items' must contain a list of key/value pairs."
5467 )
5469 if not at_most_one_value_set((ex, px, exat, pxat, keepttl)):
5470 raise DataError(
5471 "``ex``, ``px``, ``exat``, ``pxat``, "
5472 "and ``keepttl`` are mutually exclusive."
5473 )
5475 exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat)
5476 if data_persist_option:
5477 exp_options.append(data_persist_option.value)
5479 if keepttl:
5480 exp_options.append("KEEPTTL")
5482 pieces = []
5483 if items:
5484 pieces.extend(items)
5485 if key is not None:
5486 pieces.extend((key, value))
5487 if mapping:
5488 for pair in mapping.items():
5489 pieces.extend(pair)
5491 return self.execute_command(
5492 "HSETEX", name, *exp_options, "FIELDS", int(len(pieces) / 2), *pieces
5493 )
5495 def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool]:
5496 """
5497 Set ``key`` to ``value`` within hash ``name`` if ``key`` does not
5498 exist. Returns 1 if HSETNX created a field, otherwise 0.
5500 For more information, see https://redis.io/commands/hsetnx
5501 """
5502 return self.execute_command("HSETNX", name, key, value)
5504 @deprecated_function(
5505 version="4.0.0",
5506 reason="Use 'hset' instead.",
5507 name="hmset",
5508 )
5509 def hmset(self, name: str, mapping: dict) -> Union[Awaitable[str], str]:
5510 """
5511 Set key to value within hash ``name`` for each corresponding
5512 key and value from the ``mapping`` dict.
5514 For more information, see https://redis.io/commands/hmset
5515 """
5516 if not mapping:
5517 raise DataError("'hmset' with 'mapping' of length 0")
5518 items = []
5519 for pair in mapping.items():
5520 items.extend(pair)
5521 return self.execute_command("HMSET", name, *items)
5523 def hmget(self, name: str, keys: List, *args: List) -> Union[Awaitable[List], List]:
5524 """
5525 Returns a list of values ordered identically to ``keys``
5527 For more information, see https://redis.io/commands/hmget
5528 """
5529 args = list_or_args(keys, args)
5530 return self.execute_command("HMGET", name, *args, keys=[name])
5532 def hvals(self, name: str) -> Union[Awaitable[List], List]:
5533 """
5534 Return the list of values within hash ``name``
5536 For more information, see https://redis.io/commands/hvals
5537 """
5538 return self.execute_command("HVALS", name, keys=[name])
5540 def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]:
5541 """
5542 Return the number of bytes stored in the value of ``key``
5543 within hash ``name``
5545 For more information, see https://redis.io/commands/hstrlen
5546 """
5547 return self.execute_command("HSTRLEN", name, key, keys=[name])
5549 def hexpire(
5550 self,
5551 name: KeyT,
5552 seconds: ExpiryT,
5553 *fields: str,
5554 nx: bool = False,
5555 xx: bool = False,
5556 gt: bool = False,
5557 lt: bool = False,
5558 ) -> ResponseT:
5559 """
5560 Sets or updates the expiration time for fields within a hash key, using relative
5561 time in seconds.
5563 If a field already has an expiration time, the behavior of the update can be
5564 controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5566 The return value provides detailed information about the outcome for each field.
5568 For more information, see https://redis.io/commands/hexpire
5570 Args:
5571 name: The name of the hash key.
5572 seconds: Expiration time in seconds, relative. Can be an integer, or a
5573 Python `timedelta` object.
5574 fields: List of fields within the hash to apply the expiration time to.
5575 nx: Set expiry only when the field has no expiry.
5576 xx: Set expiry only when the field has an existing expiry.
5577 gt: Set expiry only when the new expiry is greater than the current one.
5578 lt: Set expiry only when the new expiry is less than the current one.
5580 Returns:
5581 Returns a list which contains for each field in the request:
5582 - `-2` if the field does not exist, or if the key does not exist.
5583 - `0` if the specified NX | XX | GT | LT condition was not met.
5584 - `1` if the expiration time was set or updated.
5585 - `2` if the field was deleted because the specified expiration time is
5586 in the past.
5587 """
5588 conditions = [nx, xx, gt, lt]
5589 if sum(conditions) > 1:
5590 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5592 if isinstance(seconds, datetime.timedelta):
5593 seconds = int(seconds.total_seconds())
5595 options = []
5596 if nx:
5597 options.append("NX")
5598 if xx:
5599 options.append("XX")
5600 if gt:
5601 options.append("GT")
5602 if lt:
5603 options.append("LT")
5605 return self.execute_command(
5606 "HEXPIRE", name, seconds, *options, "FIELDS", len(fields), *fields
5607 )
5609 def hpexpire(
5610 self,
5611 name: KeyT,
5612 milliseconds: ExpiryT,
5613 *fields: str,
5614 nx: bool = False,
5615 xx: bool = False,
5616 gt: bool = False,
5617 lt: bool = False,
5618 ) -> ResponseT:
5619 """
5620 Sets or updates the expiration time for fields within a hash key, using relative
5621 time in milliseconds.
5623 If a field already has an expiration time, the behavior of the update can be
5624 controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5626 The return value provides detailed information about the outcome for each field.
5628 For more information, see https://redis.io/commands/hpexpire
5630 Args:
5631 name: The name of the hash key.
5632 milliseconds: Expiration time in milliseconds, relative. Can be an integer,
5633 or a Python `timedelta` object.
5634 fields: List of fields within the hash to apply the expiration time to.
5635 nx: Set expiry only when the field has no expiry.
5636 xx: Set expiry only when the field has an existing expiry.
5637 gt: Set expiry only when the new expiry is greater than the current one.
5638 lt: Set expiry only when the new expiry is less than the current one.
5640 Returns:
5641 Returns a list which contains for each field in the request:
5642 - `-2` if the field does not exist, or if the key does not exist.
5643 - `0` if the specified NX | XX | GT | LT condition was not met.
5644 - `1` if the expiration time was set or updated.
5645 - `2` if the field was deleted because the specified expiration time is
5646 in the past.
5647 """
5648 conditions = [nx, xx, gt, lt]
5649 if sum(conditions) > 1:
5650 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5652 if isinstance(milliseconds, datetime.timedelta):
5653 milliseconds = int(milliseconds.total_seconds() * 1000)
5655 options = []
5656 if nx:
5657 options.append("NX")
5658 if xx:
5659 options.append("XX")
5660 if gt:
5661 options.append("GT")
5662 if lt:
5663 options.append("LT")
5665 return self.execute_command(
5666 "HPEXPIRE", name, milliseconds, *options, "FIELDS", len(fields), *fields
5667 )
5669 def hexpireat(
5670 self,
5671 name: KeyT,
5672 unix_time_seconds: AbsExpiryT,
5673 *fields: str,
5674 nx: bool = False,
5675 xx: bool = False,
5676 gt: bool = False,
5677 lt: bool = False,
5678 ) -> ResponseT:
5679 """
5680 Sets or updates the expiration time for fields within a hash key, using an
5681 absolute Unix timestamp in seconds.
5683 If a field already has an expiration time, the behavior of the update can be
5684 controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5686 The return value provides detailed information about the outcome for each field.
5688 For more information, see https://redis.io/commands/hexpireat
5690 Args:
5691 name: The name of the hash key.
5692 unix_time_seconds: Expiration time as Unix timestamp in seconds. Can be an
5693 integer or a Python `datetime` object.
5694 fields: List of fields within the hash to apply the expiration time to.
5695 nx: Set expiry only when the field has no expiry.
5696 xx: Set expiry only when the field has an existing expiration time.
5697 gt: Set expiry only when the new expiry is greater than the current one.
5698 lt: Set expiry only when the new expiry is less than the current one.
5700 Returns:
5701 Returns a list which contains for each field in the request:
5702 - `-2` if the field does not exist, or if the key does not exist.
5703 - `0` if the specified NX | XX | GT | LT condition was not met.
5704 - `1` if the expiration time was set or updated.
5705 - `2` if the field was deleted because the specified expiration time is
5706 in the past.
5707 """
5708 conditions = [nx, xx, gt, lt]
5709 if sum(conditions) > 1:
5710 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5712 if isinstance(unix_time_seconds, datetime.datetime):
5713 unix_time_seconds = int(unix_time_seconds.timestamp())
5715 options = []
5716 if nx:
5717 options.append("NX")
5718 if xx:
5719 options.append("XX")
5720 if gt:
5721 options.append("GT")
5722 if lt:
5723 options.append("LT")
5725 return self.execute_command(
5726 "HEXPIREAT",
5727 name,
5728 unix_time_seconds,
5729 *options,
5730 "FIELDS",
5731 len(fields),
5732 *fields,
5733 )
5735 def hpexpireat(
5736 self,
5737 name: KeyT,
5738 unix_time_milliseconds: AbsExpiryT,
5739 *fields: str,
5740 nx: bool = False,
5741 xx: bool = False,
5742 gt: bool = False,
5743 lt: bool = False,
5744 ) -> ResponseT:
5745 """
5746 Sets or updates the expiration time for fields within a hash key, using an
5747 absolute Unix timestamp in milliseconds.
5749 If a field already has an expiration time, the behavior of the update can be
5750 controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5752 The return value provides detailed information about the outcome for each field.
5754 For more information, see https://redis.io/commands/hpexpireat
5756 Args:
5757 name: The name of the hash key.
5758 unix_time_milliseconds: Expiration time as Unix timestamp in milliseconds.
5759 Can be an integer or a Python `datetime` object.
5760 fields: List of fields within the hash to apply the expiry.
5761 nx: Set expiry only when the field has no expiry.
5762 xx: Set expiry only when the field has an existing expiry.
5763 gt: Set expiry only when the new expiry is greater than the current one.
5764 lt: Set expiry only when the new expiry is less than the current one.
5766 Returns:
5767 Returns a list which contains for each field in the request:
5768 - `-2` if the field does not exist, or if the key does not exist.
5769 - `0` if the specified NX | XX | GT | LT condition was not met.
5770 - `1` if the expiration time was set or updated.
5771 - `2` if the field was deleted because the specified expiration time is
5772 in the past.
5773 """
5774 conditions = [nx, xx, gt, lt]
5775 if sum(conditions) > 1:
5776 raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5778 if isinstance(unix_time_milliseconds, datetime.datetime):
5779 unix_time_milliseconds = int(unix_time_milliseconds.timestamp() * 1000)
5781 options = []
5782 if nx:
5783 options.append("NX")
5784 if xx:
5785 options.append("XX")
5786 if gt:
5787 options.append("GT")
5788 if lt:
5789 options.append("LT")
5791 return self.execute_command(
5792 "HPEXPIREAT",
5793 name,
5794 unix_time_milliseconds,
5795 *options,
5796 "FIELDS",
5797 len(fields),
5798 *fields,
5799 )
5801 def hpersist(self, name: KeyT, *fields: str) -> ResponseT:
5802 """
5803 Removes the expiration time for each specified field in a hash.
5805 For more information, see https://redis.io/commands/hpersist
5807 Args:
5808 name: The name of the hash key.
5809 fields: A list of fields within the hash from which to remove the
5810 expiration time.
5812 Returns:
5813 Returns a list which contains for each field in the request:
5814 - `-2` if the field does not exist, or if the key does not exist.
5815 - `-1` if the field exists but has no associated expiration time.
5816 - `1` if the expiration time was successfully removed from the field.
5817 """
5818 return self.execute_command("HPERSIST", name, "FIELDS", len(fields), *fields)
5820 def hexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
5821 """
5822 Returns the expiration times of hash fields as Unix timestamps in seconds.
5824 For more information, see https://redis.io/commands/hexpiretime
5826 Args:
5827 key: The hash key.
5828 fields: A list of fields within the hash for which to get the expiration
5829 time.
5831 Returns:
5832 Returns a list which contains for each field in the request:
5833 - `-2` if the field does not exist, or if the key does not exist.
5834 - `-1` if the field exists but has no associated expire time.
5835 - A positive integer representing the expiration Unix timestamp in
5836 seconds, if the field has an associated expiration time.
5837 """
5838 return self.execute_command(
5839 "HEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key]
5840 )
5842 def hpexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
5843 """
5844 Returns the expiration times of hash fields as Unix timestamps in milliseconds.
5846 For more information, see https://redis.io/commands/hpexpiretime
5848 Args:
5849 key: The hash key.
5850 fields: A list of fields within the hash for which to get the expiration
5851 time.
5853 Returns:
5854 Returns a list which contains for each field in the request:
5855 - `-2` if the field does not exist, or if the key does not exist.
5856 - `-1` if the field exists but has no associated expire time.
5857 - A positive integer representing the expiration Unix timestamp in
5858 milliseconds, if the field has an associated expiration time.
5859 """
5860 return self.execute_command(
5861 "HPEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key]
5862 )
5864 def httl(self, key: KeyT, *fields: str) -> ResponseT:
5865 """
5866 Returns the TTL (Time To Live) in seconds for each specified field within a hash
5867 key.
5869 For more information, see https://redis.io/commands/httl
5871 Args:
5872 key: The hash key.
5873 fields: A list of fields within the hash for which to get the TTL.
5875 Returns:
5876 Returns a list which contains for each field in the request:
5877 - `-2` if the field does not exist, or if the key does not exist.
5878 - `-1` if the field exists but has no associated expire time.
5879 - A positive integer representing the TTL in seconds if the field has
5880 an associated expiration time.
5881 """
5882 return self.execute_command(
5883 "HTTL", key, "FIELDS", len(fields), *fields, keys=[key]
5884 )
5886 def hpttl(self, key: KeyT, *fields: str) -> ResponseT:
5887 """
5888 Returns the TTL (Time To Live) in milliseconds for each specified field within a
5889 hash key.
5891 For more information, see https://redis.io/commands/hpttl
5893 Args:
5894 key: The hash key.
5895 fields: A list of fields within the hash for which to get the TTL.
5897 Returns:
5898 Returns a list which contains for each field in the request:
5899 - `-2` if the field does not exist, or if the key does not exist.
5900 - `-1` if the field exists but has no associated expire time.
5901 - A positive integer representing the TTL in milliseconds if the field
5902 has an associated expiration time.
5903 """
5904 return self.execute_command(
5905 "HPTTL", key, "FIELDS", len(fields), *fields, keys=[key]
5906 )
5909AsyncHashCommands = HashCommands
5912class Script:
5913 """
5914 An executable Lua script object returned by ``register_script``
5915 """
5917 def __init__(self, registered_client: "redis.client.Redis", script: ScriptTextT):
5918 self.registered_client = registered_client
5919 self.script = script
5920 # Precalculate and store the SHA1 hex digest of the script.
5922 if isinstance(script, str):
5923 # We need the encoding from the client in order to generate an
5924 # accurate byte representation of the script
5925 encoder = self.get_encoder()
5926 script = encoder.encode(script)
5927 self.sha = hashlib.sha1(script).hexdigest()
5929 def __call__(
5930 self,
5931 keys: Union[Sequence[KeyT], None] = None,
5932 args: Union[Iterable[EncodableT], None] = None,
5933 client: Union["redis.client.Redis", None] = None,
5934 ):
5935 """Execute the script, passing any required ``args``"""
5936 keys = keys or []
5937 args = args or []
5938 if client is None:
5939 client = self.registered_client
5940 args = tuple(keys) + tuple(args)
5941 # make sure the Redis server knows about the script
5942 from redis.client import Pipeline
5944 if isinstance(client, Pipeline):
5945 # Make sure the pipeline can register the script before executing.
5946 client.scripts.add(self)
5947 try:
5948 return client.evalsha(self.sha, len(keys), *args)
5949 except NoScriptError:
5950 # Maybe the client is pointed to a different server than the client
5951 # that created this instance?
5952 # Overwrite the sha just in case there was a discrepancy.
5953 self.sha = client.script_load(self.script)
5954 return client.evalsha(self.sha, len(keys), *args)
5956 def get_encoder(self):
5957 """Get the encoder to encode string scripts into bytes."""
5958 try:
5959 return self.registered_client.get_encoder()
5960 except AttributeError:
5961 # DEPRECATED
5962 # In version <=4.1.2, this was the code we used to get the encoder.
5963 # However, after 4.1.2 we added support for scripting in clustered
5964 # redis. ClusteredRedis doesn't have a `.connection_pool` attribute
5965 # so we changed the Script class to use
5966 # `self.registered_client.get_encoder` (see above).
5967 # However, that is technically a breaking change, as consumers who
5968 # use Scripts directly might inject a `registered_client` that
5969 # doesn't have a `.get_encoder` field. This try/except prevents us
5970 # from breaking backward-compatibility. Ideally, it would be
5971 # removed in the next major release.
5972 return self.registered_client.connection_pool.get_encoder()
5975class AsyncScript:
5976 """
5977 An executable Lua script object returned by ``register_script``
5978 """
5980 def __init__(
5981 self,
5982 registered_client: "redis.asyncio.client.Redis",
5983 script: ScriptTextT,
5984 ):
5985 self.registered_client = registered_client
5986 self.script = script
5987 # Precalculate and store the SHA1 hex digest of the script.
5989 if isinstance(script, str):
5990 # We need the encoding from the client in order to generate an
5991 # accurate byte representation of the script
5992 try:
5993 encoder = registered_client.connection_pool.get_encoder()
5994 except AttributeError:
5995 # Cluster
5996 encoder = registered_client.get_encoder()
5997 script = encoder.encode(script)
5998 self.sha = hashlib.sha1(script).hexdigest()
6000 async def __call__(
6001 self,
6002 keys: Union[Sequence[KeyT], None] = None,
6003 args: Union[Iterable[EncodableT], None] = None,
6004 client: Union["redis.asyncio.client.Redis", None] = None,
6005 ):
6006 """Execute the script, passing any required ``args``"""
6007 keys = keys or []
6008 args = args or []
6009 if client is None:
6010 client = self.registered_client
6011 args = tuple(keys) + tuple(args)
6012 # make sure the Redis server knows about the script
6013 from redis.asyncio.client import Pipeline
6015 if isinstance(client, Pipeline):
6016 # Make sure the pipeline can register the script before executing.
6017 client.scripts.add(self)
6018 try:
6019 return await client.evalsha(self.sha, len(keys), *args)
6020 except NoScriptError:
6021 # Maybe the client is pointed to a different server than the client
6022 # that created this instance?
6023 # Overwrite the sha just in case there was a discrepancy.
6024 self.sha = await client.script_load(self.script)
6025 return await client.evalsha(self.sha, len(keys), *args)
6028class PubSubCommands(CommandsProtocol):
6029 """
6030 Redis PubSub commands.
6031 see https://redis.io/topics/pubsub
6032 """
6034 def publish(self, channel: ChannelT, message: EncodableT, **kwargs) -> ResponseT:
6035 """
6036 Publish ``message`` on ``channel``.
6037 Returns the number of subscribers the message was delivered to.
6039 For more information, see https://redis.io/commands/publish
6040 """
6041 return self.execute_command("PUBLISH", channel, message, **kwargs)
6043 def spublish(self, shard_channel: ChannelT, message: EncodableT) -> ResponseT:
6044 """
6045 Posts a message to the given shard channel.
6046 Returns the number of clients that received the message
6048 For more information, see https://redis.io/commands/spublish
6049 """
6050 return self.execute_command("SPUBLISH", shard_channel, message)
6052 def pubsub_channels(self, pattern: PatternT = "*", **kwargs) -> ResponseT:
6053 """
6054 Return a list of channels that have at least one subscriber
6056 For more information, see https://redis.io/commands/pubsub-channels
6057 """
6058 return self.execute_command("PUBSUB CHANNELS", pattern, **kwargs)
6060 def pubsub_shardchannels(self, pattern: PatternT = "*", **kwargs) -> ResponseT:
6061 """
6062 Return a list of shard_channels that have at least one subscriber
6064 For more information, see https://redis.io/commands/pubsub-shardchannels
6065 """
6066 return self.execute_command("PUBSUB SHARDCHANNELS", pattern, **kwargs)
6068 def pubsub_numpat(self, **kwargs) -> ResponseT:
6069 """
6070 Returns the number of subscriptions to patterns
6072 For more information, see https://redis.io/commands/pubsub-numpat
6073 """
6074 return self.execute_command("PUBSUB NUMPAT", **kwargs)
6076 def pubsub_numsub(self, *args: ChannelT, **kwargs) -> ResponseT:
6077 """
6078 Return a list of (channel, number of subscribers) tuples
6079 for each channel given in ``*args``
6081 For more information, see https://redis.io/commands/pubsub-numsub
6082 """
6083 return self.execute_command("PUBSUB NUMSUB", *args, **kwargs)
6085 def pubsub_shardnumsub(self, *args: ChannelT, **kwargs) -> ResponseT:
6086 """
6087 Return a list of (shard_channel, number of subscribers) tuples
6088 for each channel given in ``*args``
6090 For more information, see https://redis.io/commands/pubsub-shardnumsub
6091 """
6092 return self.execute_command("PUBSUB SHARDNUMSUB", *args, **kwargs)
6095AsyncPubSubCommands = PubSubCommands
6098class ScriptCommands(CommandsProtocol):
6099 """
6100 Redis Lua script commands. see:
6101 https://redis.io/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/
6102 """
6104 def _eval(
6105 self,
6106 command: str,
6107 script: str,
6108 numkeys: int,
6109 *keys_and_args: Union[KeyT, EncodableT],
6110 ) -> Union[Awaitable[str], str]:
6111 return self.execute_command(command, script, numkeys, *keys_and_args)
6113 def eval(
6114 self, script: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT]
6115 ) -> Union[Awaitable[str], str]:
6116 """
6117 Execute the Lua ``script``, specifying the ``numkeys`` the script
6118 will touch and the key names and argument values in ``keys_and_args``.
6119 Returns the result of the script.
6121 In practice, use the object returned by ``register_script``. This
6122 function exists purely for Redis API completion.
6124 For more information, see https://redis.io/commands/eval
6125 """
6126 return self._eval("EVAL", script, numkeys, *keys_and_args)
6128 def eval_ro(
6129 self, script: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT]
6130 ) -> Union[Awaitable[str], str]:
6131 """
6132 The read-only variant of the EVAL command
6134 Execute the read-only Lua ``script`` specifying the ``numkeys`` the script
6135 will touch and the key names and argument values in ``keys_and_args``.
6136 Returns the result of the script.
6138 For more information, see https://redis.io/commands/eval_ro
6139 """
6140 return self._eval("EVAL_RO", script, numkeys, *keys_and_args)
6142 def _evalsha(
6143 self,
6144 command: str,
6145 sha: str,
6146 numkeys: int,
6147 *keys_and_args: Union[KeyT, EncodableT],
6148 ) -> Union[Awaitable[str], str]:
6149 return self.execute_command(command, sha, numkeys, *keys_and_args)
6151 def evalsha(
6152 self, sha: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT]
6153 ) -> Union[Awaitable[str], str]:
6154 """
6155 Use the ``sha`` to execute a Lua script already registered via EVAL
6156 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the
6157 key names and argument values in ``keys_and_args``. Returns the result
6158 of the script.
6160 In practice, use the object returned by ``register_script``. This
6161 function exists purely for Redis API completion.
6163 For more information, see https://redis.io/commands/evalsha
6164 """
6165 return self._evalsha("EVALSHA", sha, numkeys, *keys_and_args)
6167 def evalsha_ro(
6168 self, sha: str, numkeys: int, *keys_and_args: Union[KeyT, EncodableT]
6169 ) -> Union[Awaitable[str], str]:
6170 """
6171 The read-only variant of the EVALSHA command
6173 Use the ``sha`` to execute a read-only Lua script already registered via EVAL
6174 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the
6175 key names and argument values in ``keys_and_args``. Returns the result
6176 of the script.
6178 For more information, see https://redis.io/commands/evalsha_ro
6179 """
6180 return self._evalsha("EVALSHA_RO", sha, numkeys, *keys_and_args)
6182 def script_exists(self, *args: str) -> ResponseT:
6183 """
6184 Check if a script exists in the script cache by specifying the SHAs of
6185 each script as ``args``. Returns a list of boolean values indicating if
6186 if each already script exists in the cache_data.
6188 For more information, see https://redis.io/commands/script-exists
6189 """
6190 return self.execute_command("SCRIPT EXISTS", *args)
6192 def script_debug(self, *args) -> None:
6193 raise NotImplementedError(
6194 "SCRIPT DEBUG is intentionally not implemented in the client."
6195 )
6197 def script_flush(
6198 self, sync_type: Union[Literal["SYNC"], Literal["ASYNC"]] = None
6199 ) -> ResponseT:
6200 """Flush all scripts from the script cache_data.
6202 ``sync_type`` is by default SYNC (synchronous) but it can also be
6203 ASYNC.
6205 For more information, see https://redis.io/commands/script-flush
6206 """
6208 # Redis pre 6 had no sync_type.
6209 if sync_type not in ["SYNC", "ASYNC", None]:
6210 raise DataError(
6211 "SCRIPT FLUSH defaults to SYNC in redis > 6.2, or "
6212 "accepts SYNC/ASYNC. For older versions, "
6213 "of redis leave as None."
6214 )
6215 if sync_type is None:
6216 pieces = []
6217 else:
6218 pieces = [sync_type]
6219 return self.execute_command("SCRIPT FLUSH", *pieces)
6221 def script_kill(self) -> ResponseT:
6222 """
6223 Kill the currently executing Lua script
6225 For more information, see https://redis.io/commands/script-kill
6226 """
6227 return self.execute_command("SCRIPT KILL")
6229 def script_load(self, script: ScriptTextT) -> ResponseT:
6230 """
6231 Load a Lua ``script`` into the script cache_data. Returns the SHA.
6233 For more information, see https://redis.io/commands/script-load
6234 """
6235 return self.execute_command("SCRIPT LOAD", script)
6237 def register_script(self: "redis.client.Redis", script: ScriptTextT) -> Script:
6238 """
6239 Register a Lua ``script`` specifying the ``keys`` it will touch.
6240 Returns a Script object that is callable and hides the complexity of
6241 deal with scripts, keys, and shas. This is the preferred way to work
6242 with Lua scripts.
6243 """
6244 return Script(self, script)
6247class AsyncScriptCommands(ScriptCommands):
6248 async def script_debug(self, *args) -> None:
6249 return super().script_debug()
6251 def register_script(
6252 self: "redis.asyncio.client.Redis",
6253 script: ScriptTextT,
6254 ) -> AsyncScript:
6255 """
6256 Register a Lua ``script`` specifying the ``keys`` it will touch.
6257 Returns a Script object that is callable and hides the complexity of
6258 deal with scripts, keys, and shas. This is the preferred way to work
6259 with Lua scripts.
6260 """
6261 return AsyncScript(self, script)
6264class GeoCommands(CommandsProtocol):
6265 """
6266 Redis Geospatial commands.
6267 see: https://redis.com/redis-best-practices/indexing-patterns/geospatial/
6268 """
6270 def geoadd(
6271 self,
6272 name: KeyT,
6273 values: Sequence[EncodableT],
6274 nx: bool = False,
6275 xx: bool = False,
6276 ch: bool = False,
6277 ) -> ResponseT:
6278 """
6279 Add the specified geospatial items to the specified key identified
6280 by the ``name`` argument. The Geospatial items are given as ordered
6281 members of the ``values`` argument, each item or place is formed by
6282 the triad longitude, latitude and name.
6284 Note: You can use ZREM to remove elements.
6286 ``nx`` forces ZADD to only create new elements and not to update
6287 scores for elements that already exist.
6289 ``xx`` forces ZADD to only update scores of elements that already
6290 exist. New elements will not be added.
6292 ``ch`` modifies the return value to be the numbers of elements changed.
6293 Changed elements include new elements that were added and elements
6294 whose scores changed.
6296 For more information, see https://redis.io/commands/geoadd
6297 """
6298 if nx and xx:
6299 raise DataError("GEOADD allows either 'nx' or 'xx', not both")
6300 if len(values) % 3 != 0:
6301 raise DataError("GEOADD requires places with lon, lat and name values")
6302 pieces = [name]
6303 if nx:
6304 pieces.append("NX")
6305 if xx:
6306 pieces.append("XX")
6307 if ch:
6308 pieces.append("CH")
6309 pieces.extend(values)
6310 return self.execute_command("GEOADD", *pieces)
6312 def geodist(
6313 self, name: KeyT, place1: FieldT, place2: FieldT, unit: Optional[str] = None
6314 ) -> ResponseT:
6315 """
6316 Return the distance between ``place1`` and ``place2`` members of the
6317 ``name`` key.
6318 The units must be one of the following : m, km mi, ft. By default
6319 meters are used.
6321 For more information, see https://redis.io/commands/geodist
6322 """
6323 pieces: list[EncodableT] = [name, place1, place2]
6324 if unit and unit not in ("m", "km", "mi", "ft"):
6325 raise DataError("GEODIST invalid unit")
6326 elif unit:
6327 pieces.append(unit)
6328 return self.execute_command("GEODIST", *pieces, keys=[name])
6330 def geohash(self, name: KeyT, *values: FieldT) -> ResponseT:
6331 """
6332 Return the geo hash string for each item of ``values`` members of
6333 the specified key identified by the ``name`` argument.
6335 For more information, see https://redis.io/commands/geohash
6336 """
6337 return self.execute_command("GEOHASH", name, *values, keys=[name])
6339 def geopos(self, name: KeyT, *values: FieldT) -> ResponseT:
6340 """
6341 Return the positions of each item of ``values`` as members of
6342 the specified key identified by the ``name`` argument. Each position
6343 is represented by the pairs lon and lat.
6345 For more information, see https://redis.io/commands/geopos
6346 """
6347 return self.execute_command("GEOPOS", name, *values, keys=[name])
6349 def georadius(
6350 self,
6351 name: KeyT,
6352 longitude: float,
6353 latitude: float,
6354 radius: float,
6355 unit: Optional[str] = None,
6356 withdist: bool = False,
6357 withcoord: bool = False,
6358 withhash: bool = False,
6359 count: Optional[int] = None,
6360 sort: Optional[str] = None,
6361 store: Optional[KeyT] = None,
6362 store_dist: Optional[KeyT] = None,
6363 any: bool = False,
6364 ) -> ResponseT:
6365 """
6366 Return the members of the specified key identified by the
6367 ``name`` argument which are within the borders of the area specified
6368 with the ``latitude`` and ``longitude`` location and the maximum
6369 distance from the center specified by the ``radius`` value.
6371 The units must be one of the following : m, km mi, ft. By default
6373 ``withdist`` indicates to return the distances of each place.
6375 ``withcoord`` indicates to return the latitude and longitude of
6376 each place.
6378 ``withhash`` indicates to return the geohash string of each place.
6380 ``count`` indicates to return the number of elements up to N.
6382 ``sort`` indicates to return the places in a sorted way, ASC for
6383 nearest to fairest and DESC for fairest to nearest.
6385 ``store`` indicates to save the places names in a sorted set named
6386 with a specific key, each element of the destination sorted set is
6387 populated with the score got from the original geo sorted set.
6389 ``store_dist`` indicates to save the places names in a sorted set
6390 named with a specific key, instead of ``store`` the sorted set
6391 destination score is set with the distance.
6393 For more information, see https://redis.io/commands/georadius
6394 """
6395 return self._georadiusgeneric(
6396 "GEORADIUS",
6397 name,
6398 longitude,
6399 latitude,
6400 radius,
6401 unit=unit,
6402 withdist=withdist,
6403 withcoord=withcoord,
6404 withhash=withhash,
6405 count=count,
6406 sort=sort,
6407 store=store,
6408 store_dist=store_dist,
6409 any=any,
6410 )
6412 def georadiusbymember(
6413 self,
6414 name: KeyT,
6415 member: FieldT,
6416 radius: float,
6417 unit: Optional[str] = None,
6418 withdist: bool = False,
6419 withcoord: bool = False,
6420 withhash: bool = False,
6421 count: Optional[int] = None,
6422 sort: Optional[str] = None,
6423 store: Union[KeyT, None] = None,
6424 store_dist: Union[KeyT, None] = None,
6425 any: bool = False,
6426 ) -> ResponseT:
6427 """
6428 This command is exactly like ``georadius`` with the sole difference
6429 that instead of taking, as the center of the area to query, a longitude
6430 and latitude value, it takes the name of a member already existing
6431 inside the geospatial index represented by the sorted set.
6433 For more information, see https://redis.io/commands/georadiusbymember
6434 """
6435 return self._georadiusgeneric(
6436 "GEORADIUSBYMEMBER",
6437 name,
6438 member,
6439 radius,
6440 unit=unit,
6441 withdist=withdist,
6442 withcoord=withcoord,
6443 withhash=withhash,
6444 count=count,
6445 sort=sort,
6446 store=store,
6447 store_dist=store_dist,
6448 any=any,
6449 )
6451 def _georadiusgeneric(
6452 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None]
6453 ) -> ResponseT:
6454 pieces = list(args)
6455 if kwargs["unit"] and kwargs["unit"] not in ("m", "km", "mi", "ft"):
6456 raise DataError("GEORADIUS invalid unit")
6457 elif kwargs["unit"]:
6458 pieces.append(kwargs["unit"])
6459 else:
6460 pieces.append("m")
6462 if kwargs["any"] and kwargs["count"] is None:
6463 raise DataError("``any`` can't be provided without ``count``")
6465 for arg_name, byte_repr in (
6466 ("withdist", "WITHDIST"),
6467 ("withcoord", "WITHCOORD"),
6468 ("withhash", "WITHHASH"),
6469 ):
6470 if kwargs[arg_name]:
6471 pieces.append(byte_repr)
6473 if kwargs["count"] is not None:
6474 pieces.extend(["COUNT", kwargs["count"]])
6475 if kwargs["any"]:
6476 pieces.append("ANY")
6478 if kwargs["sort"]:
6479 if kwargs["sort"] == "ASC":
6480 pieces.append("ASC")
6481 elif kwargs["sort"] == "DESC":
6482 pieces.append("DESC")
6483 else:
6484 raise DataError("GEORADIUS invalid sort")
6486 if kwargs["store"] and kwargs["store_dist"]:
6487 raise DataError("GEORADIUS store and store_dist cant be set together")
6489 if kwargs["store"]:
6490 pieces.extend([b"STORE", kwargs["store"]])
6492 if kwargs["store_dist"]:
6493 pieces.extend([b"STOREDIST", kwargs["store_dist"]])
6495 return self.execute_command(command, *pieces, **kwargs)
6497 def geosearch(
6498 self,
6499 name: KeyT,
6500 member: Union[FieldT, None] = None,
6501 longitude: Union[float, None] = None,
6502 latitude: Union[float, None] = None,
6503 unit: str = "m",
6504 radius: Union[float, None] = None,
6505 width: Union[float, None] = None,
6506 height: Union[float, None] = None,
6507 sort: Optional[str] = None,
6508 count: Optional[int] = None,
6509 any: bool = False,
6510 withcoord: bool = False,
6511 withdist: bool = False,
6512 withhash: bool = False,
6513 ) -> ResponseT:
6514 """
6515 Return the members of specified key identified by the
6516 ``name`` argument, which are within the borders of the
6517 area specified by a given shape. This command extends the
6518 GEORADIUS command, so in addition to searching within circular
6519 areas, it supports searching within rectangular areas.
6521 This command should be used in place of the deprecated
6522 GEORADIUS and GEORADIUSBYMEMBER commands.
6524 ``member`` Use the position of the given existing
6525 member in the sorted set. Can't be given with ``longitude``
6526 and ``latitude``.
6528 ``longitude`` and ``latitude`` Use the position given by
6529 this coordinates. Can't be given with ``member``
6530 ``radius`` Similar to GEORADIUS, search inside circular
6531 area according the given radius. Can't be given with
6532 ``height`` and ``width``.
6533 ``height`` and ``width`` Search inside an axis-aligned
6534 rectangle, determined by the given height and width.
6535 Can't be given with ``radius``
6537 ``unit`` must be one of the following : m, km, mi, ft.
6538 `m` for meters (the default value), `km` for kilometers,
6539 `mi` for miles and `ft` for feet.
6541 ``sort`` indicates to return the places in a sorted way,
6542 ASC for nearest to furthest and DESC for furthest to nearest.
6544 ``count`` limit the results to the first count matching items.
6546 ``any`` is set to True, the command will return as soon as
6547 enough matches are found. Can't be provided without ``count``
6549 ``withdist`` indicates to return the distances of each place.
6550 ``withcoord`` indicates to return the latitude and longitude of
6551 each place.
6553 ``withhash`` indicates to return the geohash string of each place.
6555 For more information, see https://redis.io/commands/geosearch
6556 """
6558 return self._geosearchgeneric(
6559 "GEOSEARCH",
6560 name,
6561 member=member,
6562 longitude=longitude,
6563 latitude=latitude,
6564 unit=unit,
6565 radius=radius,
6566 width=width,
6567 height=height,
6568 sort=sort,
6569 count=count,
6570 any=any,
6571 withcoord=withcoord,
6572 withdist=withdist,
6573 withhash=withhash,
6574 store=None,
6575 store_dist=None,
6576 )
6578 def geosearchstore(
6579 self,
6580 dest: KeyT,
6581 name: KeyT,
6582 member: Optional[FieldT] = None,
6583 longitude: Optional[float] = None,
6584 latitude: Optional[float] = None,
6585 unit: str = "m",
6586 radius: Optional[float] = None,
6587 width: Optional[float] = None,
6588 height: Optional[float] = None,
6589 sort: Optional[str] = None,
6590 count: Optional[int] = None,
6591 any: bool = False,
6592 storedist: bool = False,
6593 ) -> ResponseT:
6594 """
6595 This command is like GEOSEARCH, but stores the result in
6596 ``dest``. By default, it stores the results in the destination
6597 sorted set with their geospatial information.
6598 if ``store_dist`` set to True, the command will stores the
6599 items in a sorted set populated with their distance from the
6600 center of the circle or box, as a floating-point number.
6602 For more information, see https://redis.io/commands/geosearchstore
6603 """
6604 return self._geosearchgeneric(
6605 "GEOSEARCHSTORE",
6606 dest,
6607 name,
6608 member=member,
6609 longitude=longitude,
6610 latitude=latitude,
6611 unit=unit,
6612 radius=radius,
6613 width=width,
6614 height=height,
6615 sort=sort,
6616 count=count,
6617 any=any,
6618 withcoord=None,
6619 withdist=None,
6620 withhash=None,
6621 store=None,
6622 store_dist=storedist,
6623 )
6625 def _geosearchgeneric(
6626 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None]
6627 ) -> ResponseT:
6628 pieces = list(args)
6630 # FROMMEMBER or FROMLONLAT
6631 if kwargs["member"] is None:
6632 if kwargs["longitude"] is None or kwargs["latitude"] is None:
6633 raise DataError("GEOSEARCH must have member or longitude and latitude")
6634 if kwargs["member"]:
6635 if kwargs["longitude"] or kwargs["latitude"]:
6636 raise DataError(
6637 "GEOSEARCH member and longitude or latitude cant be set together"
6638 )
6639 pieces.extend([b"FROMMEMBER", kwargs["member"]])
6640 if kwargs["longitude"] is not None and kwargs["latitude"] is not None:
6641 pieces.extend([b"FROMLONLAT", kwargs["longitude"], kwargs["latitude"]])
6643 # BYRADIUS or BYBOX
6644 if kwargs["radius"] is None:
6645 if kwargs["width"] is None or kwargs["height"] is None:
6646 raise DataError("GEOSEARCH must have radius or width and height")
6647 if kwargs["unit"] is None:
6648 raise DataError("GEOSEARCH must have unit")
6649 if kwargs["unit"].lower() not in ("m", "km", "mi", "ft"):
6650 raise DataError("GEOSEARCH invalid unit")
6651 if kwargs["radius"]:
6652 if kwargs["width"] or kwargs["height"]:
6653 raise DataError(
6654 "GEOSEARCH radius and width or height cant be set together"
6655 )
6656 pieces.extend([b"BYRADIUS", kwargs["radius"], kwargs["unit"]])
6657 if kwargs["width"] and kwargs["height"]:
6658 pieces.extend([b"BYBOX", kwargs["width"], kwargs["height"], kwargs["unit"]])
6660 # sort
6661 if kwargs["sort"]:
6662 if kwargs["sort"].upper() == "ASC":
6663 pieces.append(b"ASC")
6664 elif kwargs["sort"].upper() == "DESC":
6665 pieces.append(b"DESC")
6666 else:
6667 raise DataError("GEOSEARCH invalid sort")
6669 # count any
6670 if kwargs["count"]:
6671 pieces.extend([b"COUNT", kwargs["count"]])
6672 if kwargs["any"]:
6673 pieces.append(b"ANY")
6674 elif kwargs["any"]:
6675 raise DataError("GEOSEARCH ``any`` can't be provided without count")
6677 # other properties
6678 for arg_name, byte_repr in (
6679 ("withdist", b"WITHDIST"),
6680 ("withcoord", b"WITHCOORD"),
6681 ("withhash", b"WITHHASH"),
6682 ("store_dist", b"STOREDIST"),
6683 ):
6684 if kwargs[arg_name]:
6685 pieces.append(byte_repr)
6687 kwargs["keys"] = [args[0] if command == "GEOSEARCH" else args[1]]
6689 return self.execute_command(command, *pieces, **kwargs)
6692AsyncGeoCommands = GeoCommands
6695class ModuleCommands(CommandsProtocol):
6696 """
6697 Redis Module commands.
6698 see: https://redis.io/topics/modules-intro
6699 """
6701 def module_load(self, path, *args) -> ResponseT:
6702 """
6703 Loads the module from ``path``.
6704 Passes all ``*args`` to the module, during loading.
6705 Raises ``ModuleError`` if a module is not found at ``path``.
6707 For more information, see https://redis.io/commands/module-load
6708 """
6709 return self.execute_command("MODULE LOAD", path, *args)
6711 def module_loadex(
6712 self,
6713 path: str,
6714 options: Optional[List[str]] = None,
6715 args: Optional[List[str]] = None,
6716 ) -> ResponseT:
6717 """
6718 Loads a module from a dynamic library at runtime with configuration directives.
6720 For more information, see https://redis.io/commands/module-loadex
6721 """
6722 pieces = []
6723 if options is not None:
6724 pieces.append("CONFIG")
6725 pieces.extend(options)
6726 if args is not None:
6727 pieces.append("ARGS")
6728 pieces.extend(args)
6730 return self.execute_command("MODULE LOADEX", path, *pieces)
6732 def module_unload(self, name) -> ResponseT:
6733 """
6734 Unloads the module ``name``.
6735 Raises ``ModuleError`` if ``name`` is not in loaded modules.
6737 For more information, see https://redis.io/commands/module-unload
6738 """
6739 return self.execute_command("MODULE UNLOAD", name)
6741 def module_list(self) -> ResponseT:
6742 """
6743 Returns a list of dictionaries containing the name and version of
6744 all loaded modules.
6746 For more information, see https://redis.io/commands/module-list
6747 """
6748 return self.execute_command("MODULE LIST")
6750 def command_info(self) -> None:
6751 raise NotImplementedError(
6752 "COMMAND INFO is intentionally not implemented in the client."
6753 )
6755 def command_count(self) -> ResponseT:
6756 return self.execute_command("COMMAND COUNT")
6758 def command_getkeys(self, *args) -> ResponseT:
6759 return self.execute_command("COMMAND GETKEYS", *args)
6761 def command(self) -> ResponseT:
6762 return self.execute_command("COMMAND")
6765class AsyncModuleCommands(ModuleCommands):
6766 async def command_info(self) -> None:
6767 return super().command_info()
6770class ClusterCommands(CommandsProtocol):
6771 """
6772 Class for Redis Cluster commands
6773 """
6775 def cluster(self, cluster_arg, *args, **kwargs) -> ResponseT:
6776 return self.execute_command(f"CLUSTER {cluster_arg.upper()}", *args, **kwargs)
6778 def readwrite(self, **kwargs) -> ResponseT:
6779 """
6780 Disables read queries for a connection to a Redis Cluster slave node.
6782 For more information, see https://redis.io/commands/readwrite
6783 """
6784 return self.execute_command("READWRITE", **kwargs)
6786 def readonly(self, **kwargs) -> ResponseT:
6787 """
6788 Enables read queries for a connection to a Redis Cluster replica node.
6790 For more information, see https://redis.io/commands/readonly
6791 """
6792 return self.execute_command("READONLY", **kwargs)
6795AsyncClusterCommands = ClusterCommands
6798class FunctionCommands:
6799 """
6800 Redis Function commands
6801 """
6803 def function_load(
6804 self, code: str, replace: Optional[bool] = False
6805 ) -> Union[Awaitable[str], str]:
6806 """
6807 Load a library to Redis.
6808 :param code: the source code (must start with
6809 Shebang statement that provides a metadata about the library)
6810 :param replace: changes the behavior to overwrite the existing library
6811 with the new contents.
6812 Return the library name that was loaded.
6814 For more information, see https://redis.io/commands/function-load
6815 """
6816 pieces = ["REPLACE"] if replace else []
6817 pieces.append(code)
6818 return self.execute_command("FUNCTION LOAD", *pieces)
6820 def function_delete(self, library: str) -> Union[Awaitable[str], str]:
6821 """
6822 Delete the library called ``library`` and all its functions.
6824 For more information, see https://redis.io/commands/function-delete
6825 """
6826 return self.execute_command("FUNCTION DELETE", library)
6828 def function_flush(self, mode: str = "SYNC") -> Union[Awaitable[str], str]:
6829 """
6830 Deletes all the libraries.
6832 For more information, see https://redis.io/commands/function-flush
6833 """
6834 return self.execute_command("FUNCTION FLUSH", mode)
6836 def function_list(
6837 self, library: Optional[str] = "*", withcode: Optional[bool] = False
6838 ) -> Union[Awaitable[List], List]:
6839 """
6840 Return information about the functions and libraries.
6842 Args:
6844 library: specify a pattern for matching library names
6845 withcode: cause the server to include the libraries source implementation
6846 in the reply
6847 """
6848 args = ["LIBRARYNAME", library]
6849 if withcode:
6850 args.append("WITHCODE")
6851 return self.execute_command("FUNCTION LIST", *args)
6853 def _fcall(
6854 self, command: str, function, numkeys: int, *keys_and_args: Any
6855 ) -> Union[Awaitable[str], str]:
6856 return self.execute_command(command, function, numkeys, *keys_and_args)
6858 def fcall(
6859 self, function, numkeys: int, *keys_and_args: Any
6860 ) -> Union[Awaitable[str], str]:
6861 """
6862 Invoke a function.
6864 For more information, see https://redis.io/commands/fcall
6865 """
6866 return self._fcall("FCALL", function, numkeys, *keys_and_args)
6868 def fcall_ro(
6869 self, function, numkeys: int, *keys_and_args: Any
6870 ) -> Union[Awaitable[str], str]:
6871 """
6872 This is a read-only variant of the FCALL command that cannot
6873 execute commands that modify data.
6875 For more information, see https://redis.io/commands/fcall_ro
6876 """
6877 return self._fcall("FCALL_RO", function, numkeys, *keys_and_args)
6879 def function_dump(self) -> Union[Awaitable[str], str]:
6880 """
6881 Return the serialized payload of loaded libraries.
6883 For more information, see https://redis.io/commands/function-dump
6884 """
6885 from redis.client import NEVER_DECODE
6887 options = {}
6888 options[NEVER_DECODE] = []
6890 return self.execute_command("FUNCTION DUMP", **options)
6892 def function_restore(
6893 self, payload: str, policy: Optional[str] = "APPEND"
6894 ) -> Union[Awaitable[str], str]:
6895 """
6896 Restore libraries from the serialized ``payload``.
6897 You can use the optional policy argument to provide a policy
6898 for handling existing libraries.
6900 For more information, see https://redis.io/commands/function-restore
6901 """
6902 return self.execute_command("FUNCTION RESTORE", payload, policy)
6904 def function_kill(self) -> Union[Awaitable[str], str]:
6905 """
6906 Kill a function that is currently executing.
6908 For more information, see https://redis.io/commands/function-kill
6909 """
6910 return self.execute_command("FUNCTION KILL")
6912 def function_stats(self) -> Union[Awaitable[List], List]:
6913 """
6914 Return information about the function that's currently running
6915 and information about the available execution engines.
6917 For more information, see https://redis.io/commands/function-stats
6918 """
6919 return self.execute_command("FUNCTION STATS")
6922AsyncFunctionCommands = FunctionCommands
6925class DataAccessCommands(
6926 BasicKeyCommands,
6927 HyperlogCommands,
6928 HashCommands,
6929 GeoCommands,
6930 ListCommands,
6931 ScanCommands,
6932 SetCommands,
6933 StreamCommands,
6934 SortedSetCommands,
6935):
6936 """
6937 A class containing all of the implemented data access redis commands.
6938 This class is to be used as a mixin for synchronous Redis clients.
6939 """
6942class AsyncDataAccessCommands(
6943 AsyncBasicKeyCommands,
6944 AsyncHyperlogCommands,
6945 AsyncHashCommands,
6946 AsyncGeoCommands,
6947 AsyncListCommands,
6948 AsyncScanCommands,
6949 AsyncSetCommands,
6950 AsyncStreamCommands,
6951 AsyncSortedSetCommands,
6952):
6953 """
6954 A class containing all of the implemented data access redis commands.
6955 This class is to be used as a mixin for asynchronous Redis clients.
6956 """
6959class CoreCommands(
6960 ACLCommands,
6961 ClusterCommands,
6962 DataAccessCommands,
6963 ManagementCommands,
6964 ModuleCommands,
6965 PubSubCommands,
6966 ScriptCommands,
6967 FunctionCommands,
6968):
6969 """
6970 A class containing all of the implemented redis commands. This class is
6971 to be used as a mixin for synchronous Redis clients.
6972 """
6975class AsyncCoreCommands(
6976 AsyncACLCommands,
6977 AsyncClusterCommands,
6978 AsyncDataAccessCommands,
6979 AsyncManagementCommands,
6980 AsyncModuleCommands,
6981 AsyncPubSubCommands,
6982 AsyncScriptCommands,
6983 AsyncFunctionCommands,
6984):
6985 """
6986 A class containing all of the implemented redis commands. This class is
6987 to be used as a mixin for asynchronous Redis clients.
6988 """