Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/redis/commands/core.py: 22%
1810 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 07:16 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 07:16 +0000
1# from __future__ import annotations
3import datetime
4import hashlib
5import warnings
6from typing import (
7 TYPE_CHECKING,
8 Any,
9 AsyncIterator,
10 Awaitable,
11 Callable,
12 Dict,
13 Iterable,
14 Iterator,
15 List,
16 Mapping,
17 Optional,
18 Sequence,
19 Set,
20 Tuple,
21 Union,
22)
24from redis.compat import Literal
25from redis.exceptions import ConnectionError, DataError, NoScriptError, RedisError
26from redis.typing import (
27 AbsExpiryT,
28 AnyKeyT,
29 BitfieldOffsetT,
30 ChannelT,
31 CommandsProtocol,
32 ConsumerT,
33 EncodableT,
34 ExpiryT,
35 FieldT,
36 GroupT,
37 KeysT,
38 KeyT,
39 PatternT,
40 ScriptTextT,
41 StreamIdT,
42 TimeoutSecT,
43 ZScoreBoundT,
44)
46from .helpers import list_or_args
48if TYPE_CHECKING:
49 from redis.asyncio.client import Redis as AsyncRedis
50 from redis.client import Redis
52ResponseT = Union[Awaitable, Any]
55class ACLCommands(CommandsProtocol):
56 """
57 Redis Access Control List (ACL) commands.
58 see: https://redis.io/topics/acl
59 """
61 def acl_cat(self, category: Union[str, None] = None, **kwargs) -> ResponseT:
62 """
63 Returns a list of categories or commands within a category.
65 If ``category`` is not supplied, returns a list of all categories.
66 If ``category`` is supplied, returns a list of all commands within
67 that category.
69 For more information see https://redis.io/commands/acl-cat
70 """
71 pieces: list[EncodableT] = [category] if category else []
72 return self.execute_command("ACL CAT", *pieces, **kwargs)
74 def acl_dryrun(self, username, *args, **kwargs):
75 """
76 Simulate the execution of a given command by a given ``username``.
78 For more information see https://redis.io/commands/acl-dryrun
79 """
80 return self.execute_command("ACL DRYRUN", username, *args, **kwargs)
82 def acl_deluser(self, *username: str, **kwargs) -> ResponseT:
83 """
84 Delete the ACL for the specified ``username``s
86 For more information see https://redis.io/commands/acl-deluser
87 """
88 return self.execute_command("ACL DELUSER", *username, **kwargs)
90 def acl_genpass(self, bits: Union[int, None] = None, **kwargs) -> ResponseT:
91 """Generate a random password value.
92 If ``bits`` is supplied then use this number of bits, rounded to
93 the next multiple of 4.
94 See: https://redis.io/commands/acl-genpass
95 """
96 pieces = []
97 if bits is not None:
98 try:
99 b = int(bits)
100 if b < 0 or b > 4096:
101 raise ValueError
102 except ValueError:
103 raise DataError(
104 "genpass optionally accepts a bits argument, between 0 and 4096."
105 )
106 return self.execute_command("ACL GENPASS", *pieces, **kwargs)
108 def acl_getuser(self, username: str, **kwargs) -> ResponseT:
109 """
110 Get the ACL details for the specified ``username``.
112 If ``username`` does not exist, return None
114 For more information see https://redis.io/commands/acl-getuser
115 """
116 return self.execute_command("ACL GETUSER", username, **kwargs)
118 def acl_help(self, **kwargs) -> ResponseT:
119 """The ACL HELP command returns helpful text describing
120 the different subcommands.
122 For more information see https://redis.io/commands/acl-help
123 """
124 return self.execute_command("ACL HELP", **kwargs)
126 def acl_list(self, **kwargs) -> ResponseT:
127 """
128 Return a list of all ACLs on the server
130 For more information see https://redis.io/commands/acl-list
131 """
132 return self.execute_command("ACL LIST", **kwargs)
134 def acl_log(self, count: Union[int, None] = None, **kwargs) -> ResponseT:
135 """
136 Get ACL logs as a list.
137 :param int count: Get logs[0:count].
138 :rtype: List.
140 For more information see https://redis.io/commands/acl-log
141 """
142 args = []
143 if count is not None:
144 if not isinstance(count, int):
145 raise DataError("ACL LOG count must be an integer")
146 args.append(count)
148 return self.execute_command("ACL LOG", *args, **kwargs)
150 def acl_log_reset(self, **kwargs) -> ResponseT:
151 """
152 Reset ACL logs.
153 :rtype: Boolean.
155 For more information see https://redis.io/commands/acl-log
156 """
157 args = [b"RESET"]
158 return self.execute_command("ACL LOG", *args, **kwargs)
160 def acl_load(self, **kwargs) -> ResponseT:
161 """
162 Load ACL rules from the configured ``aclfile``.
164 Note that the server must be configured with the ``aclfile``
165 directive to be able to load ACL rules from an aclfile.
167 For more information see https://redis.io/commands/acl-load
168 """
169 return self.execute_command("ACL LOAD", **kwargs)
171 def acl_save(self, **kwargs) -> ResponseT:
172 """
173 Save ACL rules to the configured ``aclfile``.
175 Note that the server must be configured with the ``aclfile``
176 directive to be able to save ACL rules to an aclfile.
178 For more information see https://redis.io/commands/acl-save
179 """
180 return self.execute_command("ACL SAVE", **kwargs)
182 def acl_setuser(
183 self,
184 username: str,
185 enabled: bool = False,
186 nopass: bool = False,
187 passwords: Union[str, Iterable[str], None] = None,
188 hashed_passwords: Union[str, Iterable[str], None] = None,
189 categories: Optional[Iterable[str]] = None,
190 commands: Optional[Iterable[str]] = None,
191 keys: Optional[Iterable[KeyT]] = None,
192 channels: Optional[Iterable[ChannelT]] = None,
193 selectors: Optional[Iterable[Tuple[str, KeyT]]] = None,
194 reset: bool = False,
195 reset_keys: bool = False,
196 reset_channels: bool = False,
197 reset_passwords: bool = False,
198 **kwargs,
199 ) -> ResponseT:
200 """
201 Create or update an ACL user.
203 Create or update the ACL for ``username``. If the user already exists,
204 the existing ACL is completely overwritten and replaced with the
205 specified values.
207 ``enabled`` is a boolean indicating whether the user should be allowed
208 to authenticate or not. Defaults to ``False``.
210 ``nopass`` is a boolean indicating whether the can authenticate without
211 a password. This cannot be True if ``passwords`` are also specified.
213 ``passwords`` if specified is a list of plain text passwords
214 to add to or remove from the user. Each password must be prefixed with
215 a '+' to add or a '-' to remove. For convenience, the value of
216 ``passwords`` can be a simple prefixed string when adding or
217 removing a single password.
219 ``hashed_passwords`` if specified is a list of SHA-256 hashed passwords
220 to add to or remove from the user. Each hashed password must be
221 prefixed with a '+' to add or a '-' to remove. For convenience,
222 the value of ``hashed_passwords`` can be a simple prefixed string when
223 adding or removing a single password.
225 ``categories`` if specified is a list of strings representing category
226 permissions. Each string must be prefixed with either a '+' to add the
227 category permission or a '-' to remove the category permission.
229 ``commands`` if specified is a list of strings representing command
230 permissions. Each string must be prefixed with either a '+' to add the
231 command permission or a '-' to remove the command permission.
233 ``keys`` if specified is a list of key patterns to grant the user
234 access to. Keys patterns allow '*' to support wildcard matching. For
235 example, '*' grants access to all keys while 'cache:*' grants access
236 to all keys that are prefixed with 'cache:'. ``keys`` should not be
237 prefixed with a '~'.
239 ``reset`` is a boolean indicating whether the user should be fully
240 reset prior to applying the new ACL. Setting this to True will
241 remove all existing passwords, flags and privileges from the user and
242 then apply the specified rules. If this is False, the user's existing
243 passwords, flags and privileges will be kept and any new specified
244 rules will be applied on top.
246 ``reset_keys`` is a boolean indicating whether the user's key
247 permissions should be reset prior to applying any new key permissions
248 specified in ``keys``. If this is False, the user's existing
249 key permissions will be kept and any new specified key permissions
250 will be applied on top.
252 ``reset_channels`` is a boolean indicating whether the user's channel
253 permissions should be reset prior to applying any new channel permissions
254 specified in ``channels``.If this is False, the user's existing
255 channel permissions will be kept and any new specified channel permissions
256 will be applied on top.
258 ``reset_passwords`` is a boolean indicating whether to remove all
259 existing passwords and the 'nopass' flag from the user prior to
260 applying any new passwords specified in 'passwords' or
261 'hashed_passwords'. If this is False, the user's existing passwords
262 and 'nopass' status will be kept and any new specified passwords
263 or hashed_passwords will be applied on top.
265 For more information see https://redis.io/commands/acl-setuser
266 """
267 encoder = self.get_encoder()
268 pieces: List[EncodableT] = [username]
270 if reset:
271 pieces.append(b"reset")
273 if reset_keys:
274 pieces.append(b"resetkeys")
276 if reset_channels:
277 pieces.append(b"resetchannels")
279 if reset_passwords:
280 pieces.append(b"resetpass")
282 if enabled:
283 pieces.append(b"on")
284 else:
285 pieces.append(b"off")
287 if (passwords or hashed_passwords) and nopass:
288 raise DataError(
289 "Cannot set 'nopass' and supply 'passwords' or 'hashed_passwords'"
290 )
292 if passwords:
293 # as most users will have only one password, allow remove_passwords
294 # to be specified as a simple string or a list
295 passwords = list_or_args(passwords, [])
296 for i, password in enumerate(passwords):
297 password = encoder.encode(password)
298 if password.startswith(b"+"):
299 pieces.append(b">%s" % password[1:])
300 elif password.startswith(b"-"):
301 pieces.append(b"<%s" % password[1:])
302 else:
303 raise DataError(
304 f"Password {i} must be prefixed with a "
305 f'"+" to add or a "-" to remove'
306 )
308 if hashed_passwords:
309 # as most users will have only one password, allow remove_passwords
310 # to be specified as a simple string or a list
311 hashed_passwords = list_or_args(hashed_passwords, [])
312 for i, hashed_password in enumerate(hashed_passwords):
313 hashed_password = encoder.encode(hashed_password)
314 if hashed_password.startswith(b"+"):
315 pieces.append(b"#%s" % hashed_password[1:])
316 elif hashed_password.startswith(b"-"):
317 pieces.append(b"!%s" % hashed_password[1:])
318 else:
319 raise DataError(
320 f"Hashed password {i} must be prefixed with a "
321 f'"+" to add or a "-" to remove'
322 )
324 if nopass:
325 pieces.append(b"nopass")
327 if categories:
328 for category in categories:
329 category = encoder.encode(category)
330 # categories can be prefixed with one of (+@, +, -@, -)
331 if category.startswith(b"+@"):
332 pieces.append(category)
333 elif category.startswith(b"+"):
334 pieces.append(b"+@%s" % category[1:])
335 elif category.startswith(b"-@"):
336 pieces.append(category)
337 elif category.startswith(b"-"):
338 pieces.append(b"-@%s" % category[1:])
339 else:
340 raise DataError(
341 f'Category "{encoder.decode(category, force=True)}" '
342 'must be prefixed with "+" or "-"'
343 )
344 if commands:
345 for cmd in commands:
346 cmd = encoder.encode(cmd)
347 if not cmd.startswith(b"+") and not cmd.startswith(b"-"):
348 raise DataError(
349 f'Command "{encoder.decode(cmd, force=True)}" '
350 'must be prefixed with "+" or "-"'
351 )
352 pieces.append(cmd)
354 if keys:
355 for key in keys:
356 key = encoder.encode(key)
357 if not key.startswith(b"%") and not key.startswith(b"~"):
358 key = b"~%s" % key
359 pieces.append(key)
361 if channels:
362 for channel in channels:
363 channel = encoder.encode(channel)
364 pieces.append(b"&%s" % channel)
366 if selectors:
367 for cmd, key in selectors:
368 cmd = encoder.encode(cmd)
369 if not cmd.startswith(b"+") and not cmd.startswith(b"-"):
370 raise DataError(
371 f'Command "{encoder.decode(cmd, force=True)}" '
372 'must be prefixed with "+" or "-"'
373 )
375 key = encoder.encode(key)
376 if not key.startswith(b"%") and not key.startswith(b"~"):
377 key = b"~%s" % key
379 pieces.append(b"(%s %s)" % (cmd, key))
381 return self.execute_command("ACL SETUSER", *pieces, **kwargs)
383 def acl_users(self, **kwargs) -> ResponseT:
384 """Returns a list of all registered users on the server.
386 For more information see https://redis.io/commands/acl-users
387 """
388 return self.execute_command("ACL USERS", **kwargs)
390 def acl_whoami(self, **kwargs) -> ResponseT:
391 """Get the username for the current connection
393 For more information see https://redis.io/commands/acl-whoami
394 """
395 return self.execute_command("ACL WHOAMI", **kwargs)
398AsyncACLCommands = ACLCommands
401class ManagementCommands(CommandsProtocol):
402 """
403 Redis management commands
404 """
406 def auth(self, password, username=None, **kwargs):
407 """
408 Authenticates the user. If you do not pass username, Redis will try to
409 authenticate for the "default" user. If you do pass username, it will
410 authenticate for the given user.
411 For more information see https://redis.io/commands/auth
412 """
413 pieces = []
414 if username is not None:
415 pieces.append(username)
416 pieces.append(password)
417 return self.execute_command("AUTH", *pieces, **kwargs)
419 def bgrewriteaof(self, **kwargs):
420 """Tell the Redis server to rewrite the AOF file from data in memory.
422 For more information see https://redis.io/commands/bgrewriteaof
423 """
424 return self.execute_command("BGREWRITEAOF", **kwargs)
426 def bgsave(self, schedule: bool = True, **kwargs) -> ResponseT:
427 """
428 Tell the Redis server to save its data to disk. Unlike save(),
429 this method is asynchronous and returns immediately.
431 For more information see https://redis.io/commands/bgsave
432 """
433 pieces = []
434 if schedule:
435 pieces.append("SCHEDULE")
436 return self.execute_command("BGSAVE", *pieces, **kwargs)
438 def role(self) -> ResponseT:
439 """
440 Provide information on the role of a Redis instance in
441 the context of replication, by returning if the instance
442 is currently a master, slave, or sentinel.
444 For more information see https://redis.io/commands/role
445 """
446 return self.execute_command("ROLE")
448 def client_kill(self, address: str, **kwargs) -> ResponseT:
449 """Disconnects the client at ``address`` (ip:port)
451 For more information see https://redis.io/commands/client-kill
452 """
453 return self.execute_command("CLIENT KILL", address, **kwargs)
455 def client_kill_filter(
456 self,
457 _id: Union[str, None] = None,
458 _type: Union[str, None] = None,
459 addr: Union[str, None] = None,
460 skipme: Union[bool, None] = None,
461 laddr: Union[bool, None] = None,
462 user: str = None,
463 **kwargs,
464 ) -> ResponseT:
465 """
466 Disconnects client(s) using a variety of filter options
467 :param _id: Kills a client by its unique ID field
468 :param _type: Kills a client by type where type is one of 'normal',
469 'master', 'slave' or 'pubsub'
470 :param addr: Kills a client by its 'address:port'
471 :param skipme: If True, then the client calling the command
472 will not get killed even if it is identified by one of the filter
473 options. If skipme is not provided, the server defaults to skipme=True
474 :param laddr: Kills a client by its 'local (bind) address:port'
475 :param user: Kills a client for a specific user name
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 not args:
499 raise DataError(
500 "CLIENT KILL <filter> <value> ... ... <filter> "
501 "<value> must specify at least one filter"
502 )
503 return self.execute_command("CLIENT KILL", *args, **kwargs)
505 def client_info(self, **kwargs) -> ResponseT:
506 """
507 Returns information and statistics about the current
508 client connection.
510 For more information see https://redis.io/commands/client-info
511 """
512 return self.execute_command("CLIENT INFO", **kwargs)
514 def client_list(
515 self, _type: Union[str, None] = None, client_id: List[EncodableT] = [], **kwargs
516 ) -> ResponseT:
517 """
518 Returns a list of currently connected clients.
519 If type of client specified, only that type will be returned.
520 :param _type: optional. one of the client types (normal, master,
521 replica, pubsub)
522 :param client_id: optional. a list of client ids
524 For more information see https://redis.io/commands/client-list
525 """
526 args = []
527 if _type is not None:
528 client_types = ("normal", "master", "replica", "pubsub")
529 if str(_type).lower() not in client_types:
530 raise DataError(f"CLIENT LIST _type must be one of {client_types!r}")
531 args.append(b"TYPE")
532 args.append(_type)
533 if not isinstance(client_id, list):
534 raise DataError("client_id must be a list")
535 if client_id:
536 args.append(b"ID")
537 args.append(" ".join(client_id))
538 return self.execute_command("CLIENT LIST", *args, **kwargs)
540 def client_getname(self, **kwargs) -> ResponseT:
541 """
542 Returns the current connection name
544 For more information see https://redis.io/commands/client-getname
545 """
546 return self.execute_command("CLIENT GETNAME", **kwargs)
548 def client_getredir(self, **kwargs) -> ResponseT:
549 """
550 Returns the ID (an integer) of the client to whom we are
551 redirecting tracking notifications.
553 see: https://redis.io/commands/client-getredir
554 """
555 return self.execute_command("CLIENT GETREDIR", **kwargs)
557 def client_reply(
558 self, reply: Union[Literal["ON"], Literal["OFF"], Literal["SKIP"]], **kwargs
559 ) -> ResponseT:
560 """
561 Enable and disable redis server replies.
562 ``reply`` Must be ON OFF or SKIP,
563 ON - The default most with server replies to commands
564 OFF - Disable server responses to commands
565 SKIP - Skip the response of the immediately following command.
567 Note: When setting OFF or SKIP replies, you will need a client object
568 with a timeout specified in seconds, and will need to catch the
569 TimeoutError.
570 The test_client_reply unit test illustrates this, and
571 conftest.py has a client with a timeout.
573 See https://redis.io/commands/client-reply
574 """
575 replies = ["ON", "OFF", "SKIP"]
576 if reply not in replies:
577 raise DataError(f"CLIENT REPLY must be one of {replies!r}")
578 return self.execute_command("CLIENT REPLY", reply, **kwargs)
580 def client_id(self, **kwargs) -> ResponseT:
581 """
582 Returns the current connection id
584 For more information see https://redis.io/commands/client-id
585 """
586 return self.execute_command("CLIENT ID", **kwargs)
588 def client_tracking_on(
589 self,
590 clientid: Union[int, None] = None,
591 prefix: Sequence[KeyT] = [],
592 bcast: bool = False,
593 optin: bool = False,
594 optout: bool = False,
595 noloop: bool = False,
596 ) -> ResponseT:
597 """
598 Turn on the tracking mode.
599 For more information about the options look at client_tracking func.
601 See https://redis.io/commands/client-tracking
602 """
603 return self.client_tracking(
604 True, clientid, prefix, bcast, optin, optout, noloop
605 )
607 def client_tracking_off(
608 self,
609 clientid: Union[int, None] = None,
610 prefix: Sequence[KeyT] = [],
611 bcast: bool = False,
612 optin: bool = False,
613 optout: bool = False,
614 noloop: bool = False,
615 ) -> ResponseT:
616 """
617 Turn off the tracking mode.
618 For more information about the options look at client_tracking func.
620 See https://redis.io/commands/client-tracking
621 """
622 return self.client_tracking(
623 False, clientid, prefix, bcast, optin, optout, noloop
624 )
626 def client_tracking(
627 self,
628 on: bool = True,
629 clientid: Union[int, None] = None,
630 prefix: Sequence[KeyT] = [],
631 bcast: bool = False,
632 optin: bool = False,
633 optout: bool = False,
634 noloop: bool = False,
635 **kwargs,
636 ) -> ResponseT:
637 """
638 Enables the tracking feature of the Redis server, that is used
639 for server assisted client side caching.
641 ``on`` indicate for tracking on or tracking off. The dafualt is on.
643 ``clientid`` send invalidation messages to the connection with
644 the specified ID.
646 ``bcast`` enable tracking in broadcasting mode. In this mode
647 invalidation messages are reported for all the prefixes
648 specified, regardless of the keys requested by the connection.
650 ``optin`` when broadcasting is NOT active, normally don't track
651 keys in read only commands, unless they are called immediately
652 after a CLIENT CACHING yes command.
654 ``optout`` when broadcasting is NOT active, normally track keys in
655 read only commands, unless they are called immediately after a
656 CLIENT CACHING no command.
658 ``noloop`` don't send notifications about keys modified by this
659 connection itself.
661 ``prefix`` for broadcasting, register a given key prefix, so that
662 notifications will be provided only for keys starting with this string.
664 See https://redis.io/commands/client-tracking
665 """
667 if len(prefix) != 0 and bcast is False:
668 raise DataError("Prefix can only be used with bcast")
670 pieces = ["ON"] if on else ["OFF"]
671 if clientid is not None:
672 pieces.extend(["REDIRECT", clientid])
673 for p in prefix:
674 pieces.extend(["PREFIX", p])
675 if bcast:
676 pieces.append("BCAST")
677 if optin:
678 pieces.append("OPTIN")
679 if optout:
680 pieces.append("OPTOUT")
681 if noloop:
682 pieces.append("NOLOOP")
684 return self.execute_command("CLIENT TRACKING", *pieces)
686 def client_trackinginfo(self, **kwargs) -> ResponseT:
687 """
688 Returns the information about the current client connection's
689 use of the server assisted client side cache.
691 See https://redis.io/commands/client-trackinginfo
692 """
693 return self.execute_command("CLIENT TRACKINGINFO", **kwargs)
695 def client_setname(self, name: str, **kwargs) -> ResponseT:
696 """
697 Sets the current connection name
699 For more information see https://redis.io/commands/client-setname
701 .. note::
702 This method sets client name only for **current** connection.
704 If you want to set a common name for all connections managed
705 by this client, use ``client_name`` constructor argument.
706 """
707 return self.execute_command("CLIENT SETNAME", name, **kwargs)
709 def client_unblock(
710 self, client_id: int, error: bool = False, **kwargs
711 ) -> ResponseT:
712 """
713 Unblocks a connection by its client id.
714 If ``error`` is True, unblocks the client with a special error message.
715 If ``error`` is False (default), the client is unblocked using the
716 regular timeout mechanism.
718 For more information see https://redis.io/commands/client-unblock
719 """
720 args = ["CLIENT UNBLOCK", int(client_id)]
721 if error:
722 args.append(b"ERROR")
723 return self.execute_command(*args, **kwargs)
725 def client_pause(self, timeout: int, all: bool = True, **kwargs) -> ResponseT:
726 """
727 Suspend all the Redis clients for the specified amount of time
728 :param timeout: milliseconds to pause clients
730 For more information see https://redis.io/commands/client-pause
731 :param all: If true (default) all client commands are blocked.
732 otherwise, clients are only blocked if they attempt to execute
733 a write command.
734 For the WRITE mode, some commands have special behavior:
735 EVAL/EVALSHA: Will block client for all scripts.
736 PUBLISH: Will block client.
737 PFCOUNT: Will block client.
738 WAIT: Acknowledgments will be delayed, so this command will
739 appear blocked.
740 """
741 args = ["CLIENT PAUSE", str(timeout)]
742 if not isinstance(timeout, int):
743 raise DataError("CLIENT PAUSE timeout must be an integer")
744 if not all:
745 args.append("WRITE")
746 return self.execute_command(*args, **kwargs)
748 def client_unpause(self, **kwargs) -> ResponseT:
749 """
750 Unpause all redis clients
752 For more information see https://redis.io/commands/client-unpause
753 """
754 return self.execute_command("CLIENT UNPAUSE", **kwargs)
756 def client_no_evict(self, mode: str) -> Union[Awaitable[str], str]:
757 """
758 Sets the client eviction mode for the current connection.
760 For more information see https://redis.io/commands/client-no-evict
761 """
762 return self.execute_command("CLIENT NO-EVICT", mode)
764 def client_no_touch(self, mode: str) -> Union[Awaitable[str], str]:
765 """
766 # The command controls whether commands sent by the client will alter
767 # the LRU/LFU of the keys they access.
768 # When turned on, the current client will not change LFU/LRU stats,
769 # unless it sends the TOUCH command.
771 For more information see https://redis.io/commands/client-no-touch
772 """
773 return self.execute_command("CLIENT NO-TOUCH", mode)
775 def command(self, **kwargs):
776 """
777 Returns dict reply of details about all Redis commands.
779 For more information see https://redis.io/commands/command
780 """
781 return self.execute_command("COMMAND", **kwargs)
783 def command_info(self, **kwargs) -> None:
784 raise NotImplementedError(
785 "COMMAND INFO is intentionally not implemented in the client."
786 )
788 def command_count(self, **kwargs) -> ResponseT:
789 return self.execute_command("COMMAND COUNT", **kwargs)
791 def command_list(
792 self,
793 module: Optional[str] = None,
794 category: Optional[str] = None,
795 pattern: Optional[str] = None,
796 ) -> ResponseT:
797 """
798 Return an array of the server's command names.
799 You can use one of the following filters:
800 ``module``: get the commands that belong to the module
801 ``category``: get the commands in the ACL category
802 ``pattern``: get the commands that match the given pattern
804 For more information see https://redis.io/commands/command-list/
805 """
806 pieces = []
807 if module is not None:
808 pieces.extend(["MODULE", module])
809 if category is not None:
810 pieces.extend(["ACLCAT", category])
811 if pattern is not None:
812 pieces.extend(["PATTERN", pattern])
814 if pieces:
815 pieces.insert(0, "FILTERBY")
817 return self.execute_command("COMMAND LIST", *pieces)
819 def command_getkeysandflags(self, *args: List[str]) -> List[Union[str, List[str]]]:
820 """
821 Returns array of keys from a full Redis command and their usage flags.
823 For more information see https://redis.io/commands/command-getkeysandflags
824 """
825 return self.execute_command("COMMAND GETKEYSANDFLAGS", *args)
827 def command_docs(self, *args):
828 """
829 This function throws a NotImplementedError since it is intentionally
830 not supported.
831 """
832 raise NotImplementedError(
833 "COMMAND DOCS is intentionally not implemented in the client."
834 )
836 def config_get(
837 self, pattern: PatternT = "*", *args: List[PatternT], **kwargs
838 ) -> ResponseT:
839 """
840 Return a dictionary of configuration based on the ``pattern``
842 For more information see https://redis.io/commands/config-get
843 """
844 return self.execute_command("CONFIG GET", pattern, *args, **kwargs)
846 def config_set(
847 self,
848 name: KeyT,
849 value: EncodableT,
850 *args: List[Union[KeyT, EncodableT]],
851 **kwargs,
852 ) -> ResponseT:
853 """Set config item ``name`` with ``value``
855 For more information see https://redis.io/commands/config-set
856 """
857 return self.execute_command("CONFIG SET", name, value, *args, **kwargs)
859 def config_resetstat(self, **kwargs) -> ResponseT:
860 """
861 Reset runtime statistics
863 For more information see https://redis.io/commands/config-resetstat
864 """
865 return self.execute_command("CONFIG RESETSTAT", **kwargs)
867 def config_rewrite(self, **kwargs) -> ResponseT:
868 """
869 Rewrite config file with the minimal change to reflect running config.
871 For more information see https://redis.io/commands/config-rewrite
872 """
873 return self.execute_command("CONFIG REWRITE", **kwargs)
875 def dbsize(self, **kwargs) -> ResponseT:
876 """
877 Returns the number of keys in the current database
879 For more information see https://redis.io/commands/dbsize
880 """
881 return self.execute_command("DBSIZE", **kwargs)
883 def debug_object(self, key: KeyT, **kwargs) -> ResponseT:
884 """
885 Returns version specific meta information about a given key
887 For more information see https://redis.io/commands/debug-object
888 """
889 return self.execute_command("DEBUG OBJECT", key, **kwargs)
891 def debug_segfault(self, **kwargs) -> None:
892 raise NotImplementedError(
893 """
894 DEBUG SEGFAULT is intentionally not implemented in the client.
896 For more information see https://redis.io/commands/debug-segfault
897 """
898 )
900 def echo(self, value: EncodableT, **kwargs) -> ResponseT:
901 """
902 Echo the string back from the server
904 For more information see https://redis.io/commands/echo
905 """
906 return self.execute_command("ECHO", value, **kwargs)
908 def flushall(self, asynchronous: bool = False, **kwargs) -> ResponseT:
909 """
910 Delete all keys in all databases on the current host.
912 ``asynchronous`` indicates whether the operation is
913 executed asynchronously by the server.
915 For more information see https://redis.io/commands/flushall
916 """
917 args = []
918 if asynchronous:
919 args.append(b"ASYNC")
920 return self.execute_command("FLUSHALL", *args, **kwargs)
922 def flushdb(self, asynchronous: bool = False, **kwargs) -> ResponseT:
923 """
924 Delete all keys in the current database.
926 ``asynchronous`` indicates whether the operation is
927 executed asynchronously by the server.
929 For more information see https://redis.io/commands/flushdb
930 """
931 args = []
932 if asynchronous:
933 args.append(b"ASYNC")
934 return self.execute_command("FLUSHDB", *args, **kwargs)
936 def sync(self) -> ResponseT:
937 """
938 Initiates a replication stream from the master.
940 For more information see https://redis.io/commands/sync
941 """
942 from redis.client import NEVER_DECODE
944 options = {}
945 options[NEVER_DECODE] = []
946 return self.execute_command("SYNC", **options)
948 def psync(self, replicationid: str, offset: int):
949 """
950 Initiates a replication stream from the master.
951 Newer version for `sync`.
953 For more information see https://redis.io/commands/sync
954 """
955 from redis.client import NEVER_DECODE
957 options = {}
958 options[NEVER_DECODE] = []
959 return self.execute_command("PSYNC", replicationid, offset, **options)
961 def swapdb(self, first: int, second: int, **kwargs) -> ResponseT:
962 """
963 Swap two databases
965 For more information see https://redis.io/commands/swapdb
966 """
967 return self.execute_command("SWAPDB", first, second, **kwargs)
969 def select(self, index: int, **kwargs) -> ResponseT:
970 """Select the Redis logical database at index.
972 See: https://redis.io/commands/select
973 """
974 return self.execute_command("SELECT", index, **kwargs)
976 def info(
977 self, section: Union[str, None] = None, *args: List[str], **kwargs
978 ) -> ResponseT:
979 """
980 Returns a dictionary containing information about the Redis server
982 The ``section`` option can be used to select a specific section
983 of information
985 The section option is not supported by older versions of Redis Server,
986 and will generate ResponseError
988 For more information see https://redis.io/commands/info
989 """
990 if section is None:
991 return self.execute_command("INFO", **kwargs)
992 else:
993 return self.execute_command("INFO", section, *args, **kwargs)
995 def lastsave(self, **kwargs) -> ResponseT:
996 """
997 Return a Python datetime object representing the last time the
998 Redis database was saved to disk
1000 For more information see https://redis.io/commands/lastsave
1001 """
1002 return self.execute_command("LASTSAVE", **kwargs)
1004 def latency_doctor(self):
1005 """Raise a NotImplementedError, as the client will not support LATENCY DOCTOR.
1006 This funcion is best used within the redis-cli.
1008 For more information see https://redis.io/commands/latency-doctor
1009 """
1010 raise NotImplementedError(
1011 """
1012 LATENCY DOCTOR is intentionally not implemented in the client.
1014 For more information see https://redis.io/commands/latency-doctor
1015 """
1016 )
1018 def latency_graph(self):
1019 """Raise a NotImplementedError, as the client will not support LATENCY GRAPH.
1020 This funcion is best used within the redis-cli.
1022 For more information see https://redis.io/commands/latency-graph.
1023 """
1024 raise NotImplementedError(
1025 """
1026 LATENCY GRAPH is intentionally not implemented in the client.
1028 For more information see https://redis.io/commands/latency-graph
1029 """
1030 )
1032 def lolwut(self, *version_numbers: Union[str, float], **kwargs) -> ResponseT:
1033 """
1034 Get the Redis version and a piece of generative computer art
1036 See: https://redis.io/commands/lolwut
1037 """
1038 if version_numbers:
1039 return self.execute_command("LOLWUT VERSION", *version_numbers, **kwargs)
1040 else:
1041 return self.execute_command("LOLWUT", **kwargs)
1043 def reset(self) -> ResponseT:
1044 """Perform a full reset on the connection's server side contenxt.
1046 See: https://redis.io/commands/reset
1047 """
1048 return self.execute_command("RESET")
1050 def migrate(
1051 self,
1052 host: str,
1053 port: int,
1054 keys: KeysT,
1055 destination_db: int,
1056 timeout: int,
1057 copy: bool = False,
1058 replace: bool = False,
1059 auth: Union[str, None] = None,
1060 **kwargs,
1061 ) -> ResponseT:
1062 """
1063 Migrate 1 or more keys from the current Redis server to a different
1064 server specified by the ``host``, ``port`` and ``destination_db``.
1066 The ``timeout``, specified in milliseconds, indicates the maximum
1067 time the connection between the two servers can be idle before the
1068 command is interrupted.
1070 If ``copy`` is True, the specified ``keys`` are NOT deleted from
1071 the source server.
1073 If ``replace`` is True, this operation will overwrite the keys
1074 on the destination server if they exist.
1076 If ``auth`` is specified, authenticate to the destination server with
1077 the password provided.
1079 For more information see https://redis.io/commands/migrate
1080 """
1081 keys = list_or_args(keys, [])
1082 if not keys:
1083 raise DataError("MIGRATE requires at least one key")
1084 pieces = []
1085 if copy:
1086 pieces.append(b"COPY")
1087 if replace:
1088 pieces.append(b"REPLACE")
1089 if auth:
1090 pieces.append(b"AUTH")
1091 pieces.append(auth)
1092 pieces.append(b"KEYS")
1093 pieces.extend(keys)
1094 return self.execute_command(
1095 "MIGRATE", host, port, "", destination_db, timeout, *pieces, **kwargs
1096 )
1098 def object(self, infotype: str, key: KeyT, **kwargs) -> ResponseT:
1099 """
1100 Return the encoding, idletime, or refcount about the key
1101 """
1102 return self.execute_command(
1103 "OBJECT", infotype, key, infotype=infotype, **kwargs
1104 )
1106 def memory_doctor(self, **kwargs) -> None:
1107 raise NotImplementedError(
1108 """
1109 MEMORY DOCTOR is intentionally not implemented in the client.
1111 For more information see https://redis.io/commands/memory-doctor
1112 """
1113 )
1115 def memory_help(self, **kwargs) -> None:
1116 raise NotImplementedError(
1117 """
1118 MEMORY HELP is intentionally not implemented in the client.
1120 For more information see https://redis.io/commands/memory-help
1121 """
1122 )
1124 def memory_stats(self, **kwargs) -> ResponseT:
1125 """
1126 Return a dictionary of memory stats
1128 For more information see https://redis.io/commands/memory-stats
1129 """
1130 return self.execute_command("MEMORY STATS", **kwargs)
1132 def memory_malloc_stats(self, **kwargs) -> ResponseT:
1133 """
1134 Return an internal statistics report from the memory allocator.
1136 See: https://redis.io/commands/memory-malloc-stats
1137 """
1138 return self.execute_command("MEMORY MALLOC-STATS", **kwargs)
1140 def memory_usage(
1141 self, key: KeyT, samples: Union[int, None] = None, **kwargs
1142 ) -> ResponseT:
1143 """
1144 Return the total memory usage for key, its value and associated
1145 administrative overheads.
1147 For nested data structures, ``samples`` is the number of elements to
1148 sample. If left unspecified, the server's default is 5. Use 0 to sample
1149 all elements.
1151 For more information see https://redis.io/commands/memory-usage
1152 """
1153 args = []
1154 if isinstance(samples, int):
1155 args.extend([b"SAMPLES", samples])
1156 return self.execute_command("MEMORY USAGE", key, *args, **kwargs)
1158 def memory_purge(self, **kwargs) -> ResponseT:
1159 """
1160 Attempts to purge dirty pages for reclamation by allocator
1162 For more information see https://redis.io/commands/memory-purge
1163 """
1164 return self.execute_command("MEMORY PURGE", **kwargs)
1166 def latency_histogram(self, *args):
1167 """
1168 This function throws a NotImplementedError since it is intentionally
1169 not supported.
1170 """
1171 raise NotImplementedError(
1172 "LATENCY HISTOGRAM is intentionally not implemented in the client."
1173 )
1175 def latency_history(self, event: str) -> ResponseT:
1176 """
1177 Returns the raw data of the ``event``'s latency spikes time series.
1179 For more information see https://redis.io/commands/latency-history
1180 """
1181 return self.execute_command("LATENCY HISTORY", event)
1183 def latency_latest(self) -> ResponseT:
1184 """
1185 Reports the latest latency events logged.
1187 For more information see https://redis.io/commands/latency-latest
1188 """
1189 return self.execute_command("LATENCY LATEST")
1191 def latency_reset(self, *events: str) -> ResponseT:
1192 """
1193 Resets the latency spikes time series of all, or only some, events.
1195 For more information see https://redis.io/commands/latency-reset
1196 """
1197 return self.execute_command("LATENCY RESET", *events)
1199 def ping(self, **kwargs) -> ResponseT:
1200 """
1201 Ping the Redis server
1203 For more information see https://redis.io/commands/ping
1204 """
1205 return self.execute_command("PING", **kwargs)
1207 def quit(self, **kwargs) -> ResponseT:
1208 """
1209 Ask the server to close the connection.
1211 For more information see https://redis.io/commands/quit
1212 """
1213 return self.execute_command("QUIT", **kwargs)
1215 def replicaof(self, *args, **kwargs) -> ResponseT:
1216 """
1217 Update the replication settings of a redis replica, on the fly.
1218 Examples of valid arguments include:
1219 NO ONE (set no replication)
1220 host port (set to the host and port of a redis server)
1222 For more information see https://redis.io/commands/replicaof
1223 """
1224 return self.execute_command("REPLICAOF", *args, **kwargs)
1226 def save(self, **kwargs) -> ResponseT:
1227 """
1228 Tell the Redis server to save its data to disk,
1229 blocking until the save is complete
1231 For more information see https://redis.io/commands/save
1232 """
1233 return self.execute_command("SAVE", **kwargs)
1235 def shutdown(
1236 self,
1237 save: bool = False,
1238 nosave: bool = False,
1239 now: bool = False,
1240 force: bool = False,
1241 abort: bool = False,
1242 **kwargs,
1243 ) -> None:
1244 """Shutdown the Redis server. If Redis has persistence configured,
1245 data will be flushed before shutdown.
1246 It is possible to specify modifiers to alter the behavior of the command:
1247 ``save`` will force a DB saving operation even if no save points are configured.
1248 ``nosave`` will prevent a DB saving operation even if one or more save points
1249 are configured.
1250 ``now`` skips waiting for lagging replicas, i.e. it bypasses the first step in
1251 the shutdown sequence.
1252 ``force`` ignores any errors that would normally prevent the server from exiting
1253 ``abort`` cancels an ongoing shutdown and cannot be combined with other flags.
1255 For more information see https://redis.io/commands/shutdown
1256 """
1257 if save and nosave:
1258 raise DataError("SHUTDOWN save and nosave cannot both be set")
1259 args = ["SHUTDOWN"]
1260 if save:
1261 args.append("SAVE")
1262 if nosave:
1263 args.append("NOSAVE")
1264 if now:
1265 args.append("NOW")
1266 if force:
1267 args.append("FORCE")
1268 if abort:
1269 args.append("ABORT")
1270 try:
1271 self.execute_command(*args, **kwargs)
1272 except ConnectionError:
1273 # a ConnectionError here is expected
1274 return
1275 raise RedisError("SHUTDOWN seems to have failed.")
1277 def slaveof(
1278 self, host: Union[str, None] = None, port: Union[int, None] = None, **kwargs
1279 ) -> ResponseT:
1280 """
1281 Set the server to be a replicated slave of the instance identified
1282 by the ``host`` and ``port``. If called without arguments, the
1283 instance is promoted to a master instead.
1285 For more information see https://redis.io/commands/slaveof
1286 """
1287 if host is None and port is None:
1288 return self.execute_command("SLAVEOF", b"NO", b"ONE", **kwargs)
1289 return self.execute_command("SLAVEOF", host, port, **kwargs)
1291 def slowlog_get(self, num: Union[int, None] = None, **kwargs) -> ResponseT:
1292 """
1293 Get the entries from the slowlog. If ``num`` is specified, get the
1294 most recent ``num`` items.
1296 For more information see https://redis.io/commands/slowlog-get
1297 """
1298 from redis.client import NEVER_DECODE
1300 args = ["SLOWLOG GET"]
1301 if num is not None:
1302 args.append(num)
1303 decode_responses = self.get_connection_kwargs().get("decode_responses", False)
1304 if decode_responses is True:
1305 kwargs[NEVER_DECODE] = []
1306 return self.execute_command(*args, **kwargs)
1308 def slowlog_len(self, **kwargs) -> ResponseT:
1309 """
1310 Get the number of items in the slowlog
1312 For more information see https://redis.io/commands/slowlog-len
1313 """
1314 return self.execute_command("SLOWLOG LEN", **kwargs)
1316 def slowlog_reset(self, **kwargs) -> ResponseT:
1317 """
1318 Remove all items in the slowlog
1320 For more information see https://redis.io/commands/slowlog-reset
1321 """
1322 return self.execute_command("SLOWLOG RESET", **kwargs)
1324 def time(self, **kwargs) -> ResponseT:
1325 """
1326 Returns the server time as a 2-item tuple of ints:
1327 (seconds since epoch, microseconds into this second).
1329 For more information see https://redis.io/commands/time
1330 """
1331 return self.execute_command("TIME", **kwargs)
1333 def wait(self, num_replicas: int, timeout: int, **kwargs) -> ResponseT:
1334 """
1335 Redis synchronous replication
1336 That returns the number of replicas that processed the query when
1337 we finally have at least ``num_replicas``, or when the ``timeout`` was
1338 reached.
1340 For more information see https://redis.io/commands/wait
1341 """
1342 return self.execute_command("WAIT", num_replicas, timeout, **kwargs)
1344 def hello(self):
1345 """
1346 This function throws a NotImplementedError since it is intentionally
1347 not supported.
1348 """
1349 raise NotImplementedError(
1350 "HELLO is intentionally not implemented in the client."
1351 )
1353 def failover(self):
1354 """
1355 This function throws a NotImplementedError since it is intentionally
1356 not supported.
1357 """
1358 raise NotImplementedError(
1359 "FAILOVER is intentionally not implemented in the client."
1360 )
1363AsyncManagementCommands = ManagementCommands
1366class AsyncManagementCommands(ManagementCommands):
1367 async def command_info(self, **kwargs) -> None:
1368 return super().command_info(**kwargs)
1370 async def debug_segfault(self, **kwargs) -> None:
1371 return super().debug_segfault(**kwargs)
1373 async def memory_doctor(self, **kwargs) -> None:
1374 return super().memory_doctor(**kwargs)
1376 async def memory_help(self, **kwargs) -> None:
1377 return super().memory_help(**kwargs)
1379 async def shutdown(
1380 self,
1381 save: bool = False,
1382 nosave: bool = False,
1383 now: bool = False,
1384 force: bool = False,
1385 abort: bool = False,
1386 **kwargs,
1387 ) -> None:
1388 """Shutdown the Redis server. If Redis has persistence configured,
1389 data will be flushed before shutdown. If the "save" option is set,
1390 a data flush will be attempted even if there is no persistence
1391 configured. If the "nosave" option is set, no data flush will be
1392 attempted. The "save" and "nosave" options cannot both be set.
1394 For more information see https://redis.io/commands/shutdown
1395 """
1396 if save and nosave:
1397 raise DataError("SHUTDOWN save and nosave cannot both be set")
1398 args = ["SHUTDOWN"]
1399 if save:
1400 args.append("SAVE")
1401 if nosave:
1402 args.append("NOSAVE")
1403 if now:
1404 args.append("NOW")
1405 if force:
1406 args.append("FORCE")
1407 if abort:
1408 args.append("ABORT")
1409 try:
1410 await self.execute_command(*args, **kwargs)
1411 except ConnectionError:
1412 # a ConnectionError here is expected
1413 return
1414 raise RedisError("SHUTDOWN seems to have failed.")
1417class BitFieldOperation:
1418 """
1419 Command builder for BITFIELD commands.
1420 """
1422 def __init__(
1423 self,
1424 client: Union["Redis", "AsyncRedis"],
1425 key: str,
1426 default_overflow: Union[str, None] = None,
1427 ):
1428 self.client = client
1429 self.key = key
1430 self._default_overflow = default_overflow
1431 # for typing purposes, run the following in constructor and in reset()
1432 self.operations: list[tuple[EncodableT, ...]] = []
1433 self._last_overflow = "WRAP"
1434 self.reset()
1436 def reset(self):
1437 """
1438 Reset the state of the instance to when it was constructed
1439 """
1440 self.operations = []
1441 self._last_overflow = "WRAP"
1442 self.overflow(self._default_overflow or self._last_overflow)
1444 def overflow(self, overflow: str):
1445 """
1446 Update the overflow algorithm of successive INCRBY operations
1447 :param overflow: Overflow algorithm, one of WRAP, SAT, FAIL. See the
1448 Redis docs for descriptions of these algorithmsself.
1449 :returns: a :py:class:`BitFieldOperation` instance.
1450 """
1451 overflow = overflow.upper()
1452 if overflow != self._last_overflow:
1453 self._last_overflow = overflow
1454 self.operations.append(("OVERFLOW", overflow))
1455 return self
1457 def incrby(
1458 self,
1459 fmt: str,
1460 offset: BitfieldOffsetT,
1461 increment: int,
1462 overflow: Union[str, None] = None,
1463 ):
1464 """
1465 Increment a bitfield by a given amount.
1466 :param fmt: format-string for the bitfield being updated, e.g. 'u8'
1467 for an unsigned 8-bit integer.
1468 :param offset: offset (in number of bits). If prefixed with a
1469 '#', this is an offset multiplier, e.g. given the arguments
1470 fmt='u8', offset='#2', the offset will be 16.
1471 :param int increment: value to increment the bitfield by.
1472 :param str overflow: overflow algorithm. Defaults to WRAP, but other
1473 acceptable values are SAT and FAIL. See the Redis docs for
1474 descriptions of these algorithms.
1475 :returns: a :py:class:`BitFieldOperation` instance.
1476 """
1477 if overflow is not None:
1478 self.overflow(overflow)
1480 self.operations.append(("INCRBY", fmt, offset, increment))
1481 return self
1483 def get(self, fmt: str, offset: BitfieldOffsetT):
1484 """
1485 Get the value of a given bitfield.
1486 :param fmt: format-string for the bitfield being read, e.g. 'u8' for
1487 an unsigned 8-bit integer.
1488 :param offset: offset (in number of bits). If prefixed with a
1489 '#', this is an offset multiplier, e.g. given the arguments
1490 fmt='u8', offset='#2', the offset will be 16.
1491 :returns: a :py:class:`BitFieldOperation` instance.
1492 """
1493 self.operations.append(("GET", fmt, offset))
1494 return self
1496 def set(self, fmt: str, offset: BitfieldOffsetT, value: int):
1497 """
1498 Set the value of a given bitfield.
1499 :param fmt: format-string for the bitfield being read, e.g. 'u8' for
1500 an unsigned 8-bit integer.
1501 :param offset: offset (in number of bits). If prefixed with a
1502 '#', this is an offset multiplier, e.g. given the arguments
1503 fmt='u8', offset='#2', the offset will be 16.
1504 :param int value: value to set at the given position.
1505 :returns: a :py:class:`BitFieldOperation` instance.
1506 """
1507 self.operations.append(("SET", fmt, offset, value))
1508 return self
1510 @property
1511 def command(self):
1512 cmd = ["BITFIELD", self.key]
1513 for ops in self.operations:
1514 cmd.extend(ops)
1515 return cmd
1517 def execute(self) -> ResponseT:
1518 """
1519 Execute the operation(s) in a single BITFIELD command. The return value
1520 is a list of values corresponding to each operation. If the client
1521 used to create this instance was a pipeline, the list of values
1522 will be present within the pipeline's execute.
1523 """
1524 command = self.command
1525 self.reset()
1526 return self.client.execute_command(*command)
1529class BasicKeyCommands(CommandsProtocol):
1530 """
1531 Redis basic key-based commands
1532 """
1534 def append(self, key: KeyT, value: EncodableT) -> ResponseT:
1535 """
1536 Appends the string ``value`` to the value at ``key``. If ``key``
1537 doesn't already exist, create it with a value of ``value``.
1538 Returns the new length of the value at ``key``.
1540 For more information see https://redis.io/commands/append
1541 """
1542 return self.execute_command("APPEND", key, value)
1544 def bitcount(
1545 self,
1546 key: KeyT,
1547 start: Union[int, None] = None,
1548 end: Union[int, None] = None,
1549 mode: Optional[str] = None,
1550 ) -> ResponseT:
1551 """
1552 Returns the count of set bits in the value of ``key``. Optional
1553 ``start`` and ``end`` parameters indicate which bytes to consider
1555 For more information see https://redis.io/commands/bitcount
1556 """
1557 params = [key]
1558 if start is not None and end is not None:
1559 params.append(start)
1560 params.append(end)
1561 elif (start is not None and end is None) or (end is not None and start is None):
1562 raise DataError("Both start and end must be specified")
1563 if mode is not None:
1564 params.append(mode)
1565 return self.execute_command("BITCOUNT", *params)
1567 def bitfield(
1568 self: Union["Redis", "AsyncRedis"],
1569 key: KeyT,
1570 default_overflow: Union[str, None] = None,
1571 ) -> BitFieldOperation:
1572 """
1573 Return a BitFieldOperation instance to conveniently construct one or
1574 more bitfield operations on ``key``.
1576 For more information see https://redis.io/commands/bitfield
1577 """
1578 return BitFieldOperation(self, key, default_overflow=default_overflow)
1580 def bitfield_ro(
1581 self: Union["Redis", "AsyncRedis"],
1582 key: KeyT,
1583 encoding: str,
1584 offset: BitfieldOffsetT,
1585 items: Optional[list] = None,
1586 ) -> ResponseT:
1587 """
1588 Return an array of the specified bitfield values
1589 where the first value is found using ``encoding`` and ``offset``
1590 parameters and remaining values are result of corresponding
1591 encoding/offset pairs in optional list ``items``
1592 Read-only variant of the BITFIELD command.
1594 For more information see https://redis.io/commands/bitfield_ro
1595 """
1596 params = [key, "GET", encoding, offset]
1598 items = items or []
1599 for encoding, offset in items:
1600 params.extend(["GET", encoding, offset])
1601 return self.execute_command("BITFIELD_RO", *params)
1603 def bitop(self, operation: str, dest: KeyT, *keys: KeyT) -> ResponseT:
1604 """
1605 Perform a bitwise operation using ``operation`` between ``keys`` and
1606 store the result in ``dest``.
1608 For more information see https://redis.io/commands/bitop
1609 """
1610 return self.execute_command("BITOP", operation, dest, *keys)
1612 def bitpos(
1613 self,
1614 key: KeyT,
1615 bit: int,
1616 start: Union[int, None] = None,
1617 end: Union[int, None] = None,
1618 mode: Optional[str] = None,
1619 ) -> ResponseT:
1620 """
1621 Return the position of the first bit set to 1 or 0 in a string.
1622 ``start`` and ``end`` defines search range. The range is interpreted
1623 as a range of bytes and not a range of bits, so start=0 and end=2
1624 means to look at the first three bytes.
1626 For more information see https://redis.io/commands/bitpos
1627 """
1628 if bit not in (0, 1):
1629 raise DataError("bit must be 0 or 1")
1630 params = [key, bit]
1632 start is not None and params.append(start)
1634 if start is not None and end is not None:
1635 params.append(end)
1636 elif start is None and end is not None:
1637 raise DataError("start argument is not set, when end is specified")
1639 if mode is not None:
1640 params.append(mode)
1641 return self.execute_command("BITPOS", *params)
1643 def copy(
1644 self,
1645 source: str,
1646 destination: str,
1647 destination_db: Union[str, None] = None,
1648 replace: bool = False,
1649 ) -> ResponseT:
1650 """
1651 Copy the value stored in the ``source`` key to the ``destination`` key.
1653 ``destination_db`` an alternative destination database. By default,
1654 the ``destination`` key is created in the source Redis database.
1656 ``replace`` whether the ``destination`` key should be removed before
1657 copying the value to it. By default, the value is not copied if
1658 the ``destination`` key already exists.
1660 For more information see https://redis.io/commands/copy
1661 """
1662 params = [source, destination]
1663 if destination_db is not None:
1664 params.extend(["DB", destination_db])
1665 if replace:
1666 params.append("REPLACE")
1667 return self.execute_command("COPY", *params)
1669 def decrby(self, name: KeyT, amount: int = 1) -> ResponseT:
1670 """
1671 Decrements the value of ``key`` by ``amount``. If no key exists,
1672 the value will be initialized as 0 - ``amount``
1674 For more information see https://redis.io/commands/decrby
1675 """
1676 return self.execute_command("DECRBY", name, amount)
1678 decr = decrby
1680 def delete(self, *names: KeyT) -> ResponseT:
1681 """
1682 Delete one or more keys specified by ``names``
1683 """
1684 return self.execute_command("DEL", *names)
1686 def __delitem__(self, name: KeyT):
1687 self.delete(name)
1689 def dump(self, name: KeyT) -> ResponseT:
1690 """
1691 Return a serialized version of the value stored at the specified key.
1692 If key does not exist a nil bulk reply is returned.
1694 For more information see https://redis.io/commands/dump
1695 """
1696 from redis.client import NEVER_DECODE
1698 options = {}
1699 options[NEVER_DECODE] = []
1700 return self.execute_command("DUMP", name, **options)
1702 def exists(self, *names: KeyT) -> ResponseT:
1703 """
1704 Returns the number of ``names`` that exist
1706 For more information see https://redis.io/commands/exists
1707 """
1708 return self.execute_command("EXISTS", *names)
1710 __contains__ = exists
1712 def expire(
1713 self,
1714 name: KeyT,
1715 time: ExpiryT,
1716 nx: bool = False,
1717 xx: bool = False,
1718 gt: bool = False,
1719 lt: bool = False,
1720 ) -> ResponseT:
1721 """
1722 Set an expire flag on key ``name`` for ``time`` seconds with given
1723 ``option``. ``time`` can be represented by an integer or a Python timedelta
1724 object.
1726 Valid options are:
1727 NX -> Set expiry only when the key has no expiry
1728 XX -> Set expiry only when the key has an existing expiry
1729 GT -> Set expiry only when the new expiry is greater than current one
1730 LT -> Set expiry only when the new expiry is less than current one
1732 For more information see https://redis.io/commands/expire
1733 """
1734 if isinstance(time, datetime.timedelta):
1735 time = int(time.total_seconds())
1737 exp_option = list()
1738 if nx:
1739 exp_option.append("NX")
1740 if xx:
1741 exp_option.append("XX")
1742 if gt:
1743 exp_option.append("GT")
1744 if lt:
1745 exp_option.append("LT")
1747 return self.execute_command("EXPIRE", name, time, *exp_option)
1749 def expireat(
1750 self,
1751 name: KeyT,
1752 when: AbsExpiryT,
1753 nx: bool = False,
1754 xx: bool = False,
1755 gt: bool = False,
1756 lt: bool = False,
1757 ) -> ResponseT:
1758 """
1759 Set an expire flag on key ``name`` with given ``option``. ``when``
1760 can be represented as an integer indicating unix time or a Python
1761 datetime object.
1763 Valid options are:
1764 -> NX -- Set expiry only when the key has no expiry
1765 -> XX -- Set expiry only when the key has an existing expiry
1766 -> GT -- Set expiry only when the new expiry is greater than current one
1767 -> LT -- Set expiry only when the new expiry is less than current one
1769 For more information see https://redis.io/commands/expireat
1770 """
1771 if isinstance(when, datetime.datetime):
1772 when = int(when.timestamp())
1774 exp_option = list()
1775 if nx:
1776 exp_option.append("NX")
1777 if xx:
1778 exp_option.append("XX")
1779 if gt:
1780 exp_option.append("GT")
1781 if lt:
1782 exp_option.append("LT")
1784 return self.execute_command("EXPIREAT", name, when, *exp_option)
1786 def expiretime(self, key: str) -> int:
1787 """
1788 Returns the absolute Unix timestamp (since January 1, 1970) in seconds
1789 at which the given key will expire.
1791 For more information see https://redis.io/commands/expiretime
1792 """
1793 return self.execute_command("EXPIRETIME", key)
1795 def get(self, name: KeyT) -> ResponseT:
1796 """
1797 Return the value at key ``name``, or None if the key doesn't exist
1799 For more information see https://redis.io/commands/get
1800 """
1801 return self.execute_command("GET", name)
1803 def getdel(self, name: KeyT) -> ResponseT:
1804 """
1805 Get the value at key ``name`` and delete the key. This command
1806 is similar to GET, except for the fact that it also deletes
1807 the key on success (if and only if the key's value type
1808 is a string).
1810 For more information see https://redis.io/commands/getdel
1811 """
1812 return self.execute_command("GETDEL", name)
1814 def getex(
1815 self,
1816 name: KeyT,
1817 ex: Union[ExpiryT, None] = None,
1818 px: Union[ExpiryT, None] = None,
1819 exat: Union[AbsExpiryT, None] = None,
1820 pxat: Union[AbsExpiryT, None] = None,
1821 persist: bool = False,
1822 ) -> ResponseT:
1823 """
1824 Get the value of key and optionally set its expiration.
1825 GETEX is similar to GET, but is a write command with
1826 additional options. All time parameters can be given as
1827 datetime.timedelta or integers.
1829 ``ex`` sets an expire flag on key ``name`` for ``ex`` seconds.
1831 ``px`` sets an expire flag on key ``name`` for ``px`` milliseconds.
1833 ``exat`` sets an expire flag on key ``name`` for ``ex`` seconds,
1834 specified in unix time.
1836 ``pxat`` sets an expire flag on key ``name`` for ``ex`` milliseconds,
1837 specified in unix time.
1839 ``persist`` remove the time to live associated with ``name``.
1841 For more information see https://redis.io/commands/getex
1842 """
1844 opset = {ex, px, exat, pxat}
1845 if len(opset) > 2 or len(opset) > 1 and persist:
1846 raise DataError(
1847 "``ex``, ``px``, ``exat``, ``pxat``, "
1848 "and ``persist`` are mutually exclusive."
1849 )
1851 pieces: list[EncodableT] = []
1852 # similar to set command
1853 if ex is not None:
1854 pieces.append("EX")
1855 if isinstance(ex, datetime.timedelta):
1856 ex = int(ex.total_seconds())
1857 pieces.append(ex)
1858 if px is not None:
1859 pieces.append("PX")
1860 if isinstance(px, datetime.timedelta):
1861 px = int(px.total_seconds() * 1000)
1862 pieces.append(px)
1863 # similar to pexpireat command
1864 if exat is not None:
1865 pieces.append("EXAT")
1866 if isinstance(exat, datetime.datetime):
1867 exat = int(exat.timestamp())
1868 pieces.append(exat)
1869 if pxat is not None:
1870 pieces.append("PXAT")
1871 if isinstance(pxat, datetime.datetime):
1872 pxat = int(pxat.timestamp() * 1000)
1873 pieces.append(pxat)
1874 if persist:
1875 pieces.append("PERSIST")
1877 return self.execute_command("GETEX", name, *pieces)
1879 def __getitem__(self, name: KeyT):
1880 """
1881 Return the value at key ``name``, raises a KeyError if the key
1882 doesn't exist.
1883 """
1884 value = self.get(name)
1885 if value is not None:
1886 return value
1887 raise KeyError(name)
1889 def getbit(self, name: KeyT, offset: int) -> ResponseT:
1890 """
1891 Returns an integer indicating the value of ``offset`` in ``name``
1893 For more information see https://redis.io/commands/getbit
1894 """
1895 return self.execute_command("GETBIT", name, offset)
1897 def getrange(self, key: KeyT, start: int, end: int) -> ResponseT:
1898 """
1899 Returns the substring of the string value stored at ``key``,
1900 determined by the offsets ``start`` and ``end`` (both are inclusive)
1902 For more information see https://redis.io/commands/getrange
1903 """
1904 return self.execute_command("GETRANGE", key, start, end)
1906 def getset(self, name: KeyT, value: EncodableT) -> ResponseT:
1907 """
1908 Sets the value at key ``name`` to ``value``
1909 and returns the old value at key ``name`` atomically.
1911 As per Redis 6.2, GETSET is considered deprecated.
1912 Please use SET with GET parameter in new code.
1914 For more information see https://redis.io/commands/getset
1915 """
1916 return self.execute_command("GETSET", name, value)
1918 def incrby(self, name: KeyT, amount: int = 1) -> ResponseT:
1919 """
1920 Increments the value of ``key`` by ``amount``. If no key exists,
1921 the value will be initialized as ``amount``
1923 For more information see https://redis.io/commands/incrby
1924 """
1925 return self.execute_command("INCRBY", name, amount)
1927 incr = incrby
1929 def incrbyfloat(self, name: KeyT, amount: float = 1.0) -> ResponseT:
1930 """
1931 Increments the value at key ``name`` by floating ``amount``.
1932 If no key exists, the value will be initialized as ``amount``
1934 For more information see https://redis.io/commands/incrbyfloat
1935 """
1936 return self.execute_command("INCRBYFLOAT", name, amount)
1938 def keys(self, pattern: PatternT = "*", **kwargs) -> ResponseT:
1939 """
1940 Returns a list of keys matching ``pattern``
1942 For more information see https://redis.io/commands/keys
1943 """
1944 return self.execute_command("KEYS", pattern, **kwargs)
1946 def lmove(
1947 self, first_list: str, second_list: str, src: str = "LEFT", dest: str = "RIGHT"
1948 ) -> ResponseT:
1949 """
1950 Atomically returns and removes the first/last element of a list,
1951 pushing it as the first/last element on the destination list.
1952 Returns the element being popped and pushed.
1954 For more information see https://redis.io/commands/lmove
1955 """
1956 params = [first_list, second_list, src, dest]
1957 return self.execute_command("LMOVE", *params)
1959 def blmove(
1960 self,
1961 first_list: str,
1962 second_list: str,
1963 timeout: int,
1964 src: str = "LEFT",
1965 dest: str = "RIGHT",
1966 ) -> ResponseT:
1967 """
1968 Blocking version of lmove.
1970 For more information see https://redis.io/commands/blmove
1971 """
1972 params = [first_list, second_list, src, dest, timeout]
1973 return self.execute_command("BLMOVE", *params)
1975 def mget(self, keys: KeysT, *args: EncodableT) -> ResponseT:
1976 """
1977 Returns a list of values ordered identically to ``keys``
1979 For more information see https://redis.io/commands/mget
1980 """
1981 from redis.client import EMPTY_RESPONSE
1983 args = list_or_args(keys, args)
1984 options = {}
1985 if not args:
1986 options[EMPTY_RESPONSE] = []
1987 return self.execute_command("MGET", *args, **options)
1989 def mset(self, mapping: Mapping[AnyKeyT, EncodableT]) -> ResponseT:
1990 """
1991 Sets key/values based on a mapping. Mapping is a dictionary of
1992 key/value pairs. Both keys and values should be strings or types that
1993 can be cast to a string via str().
1995 For more information see https://redis.io/commands/mset
1996 """
1997 items = []
1998 for pair in mapping.items():
1999 items.extend(pair)
2000 return self.execute_command("MSET", *items)
2002 def msetnx(self, mapping: Mapping[AnyKeyT, EncodableT]) -> ResponseT:
2003 """
2004 Sets key/values based on a mapping if none of the keys are already set.
2005 Mapping is a dictionary of key/value pairs. Both keys and values
2006 should be strings or types that can be cast to a string via str().
2007 Returns a boolean indicating if the operation was successful.
2009 For more information see https://redis.io/commands/msetnx
2010 """
2011 items = []
2012 for pair in mapping.items():
2013 items.extend(pair)
2014 return self.execute_command("MSETNX", *items)
2016 def move(self, name: KeyT, db: int) -> ResponseT:
2017 """
2018 Moves the key ``name`` to a different Redis database ``db``
2020 For more information see https://redis.io/commands/move
2021 """
2022 return self.execute_command("MOVE", name, db)
2024 def persist(self, name: KeyT) -> ResponseT:
2025 """
2026 Removes an expiration on ``name``
2028 For more information see https://redis.io/commands/persist
2029 """
2030 return self.execute_command("PERSIST", name)
2032 def pexpire(
2033 self,
2034 name: KeyT,
2035 time: ExpiryT,
2036 nx: bool = False,
2037 xx: bool = False,
2038 gt: bool = False,
2039 lt: bool = False,
2040 ) -> ResponseT:
2041 """
2042 Set an expire flag on key ``name`` for ``time`` milliseconds
2043 with given ``option``. ``time`` can be represented by an
2044 integer or a Python timedelta object.
2046 Valid options are:
2047 NX -> Set expiry only when the key has no expiry
2048 XX -> Set expiry only when the key has an existing expiry
2049 GT -> Set expiry only when the new expiry is greater than current one
2050 LT -> Set expiry only when the new expiry is less than current one
2052 For more information see https://redis.io/commands/pexpire
2053 """
2054 if isinstance(time, datetime.timedelta):
2055 time = int(time.total_seconds() * 1000)
2057 exp_option = list()
2058 if nx:
2059 exp_option.append("NX")
2060 if xx:
2061 exp_option.append("XX")
2062 if gt:
2063 exp_option.append("GT")
2064 if lt:
2065 exp_option.append("LT")
2066 return self.execute_command("PEXPIRE", name, time, *exp_option)
2068 def pexpireat(
2069 self,
2070 name: KeyT,
2071 when: AbsExpiryT,
2072 nx: bool = False,
2073 xx: bool = False,
2074 gt: bool = False,
2075 lt: bool = False,
2076 ) -> ResponseT:
2077 """
2078 Set an expire flag on key ``name`` with given ``option``. ``when``
2079 can be represented as an integer representing unix time in
2080 milliseconds (unix time * 1000) or a Python datetime object.
2082 Valid options are:
2083 NX -> Set expiry only when the key has no expiry
2084 XX -> Set expiry only when the key has an existing expiry
2085 GT -> Set expiry only when the new expiry is greater than current one
2086 LT -> Set expiry only when the new expiry is less than current one
2088 For more information see https://redis.io/commands/pexpireat
2089 """
2090 if isinstance(when, datetime.datetime):
2091 when = int(when.timestamp() * 1000)
2092 exp_option = list()
2093 if nx:
2094 exp_option.append("NX")
2095 if xx:
2096 exp_option.append("XX")
2097 if gt:
2098 exp_option.append("GT")
2099 if lt:
2100 exp_option.append("LT")
2101 return self.execute_command("PEXPIREAT", name, when, *exp_option)
2103 def pexpiretime(self, key: str) -> int:
2104 """
2105 Returns the absolute Unix timestamp (since January 1, 1970) in milliseconds
2106 at which the given key will expire.
2108 For more information see https://redis.io/commands/pexpiretime
2109 """
2110 return self.execute_command("PEXPIRETIME", key)
2112 def psetex(self, name: KeyT, time_ms: ExpiryT, value: EncodableT):
2113 """
2114 Set the value of key ``name`` to ``value`` that expires in ``time_ms``
2115 milliseconds. ``time_ms`` can be represented by an integer or a Python
2116 timedelta object
2118 For more information see https://redis.io/commands/psetex
2119 """
2120 if isinstance(time_ms, datetime.timedelta):
2121 time_ms = int(time_ms.total_seconds() * 1000)
2122 return self.execute_command("PSETEX", name, time_ms, value)
2124 def pttl(self, name: KeyT) -> ResponseT:
2125 """
2126 Returns the number of milliseconds until the key ``name`` will expire
2128 For more information see https://redis.io/commands/pttl
2129 """
2130 return self.execute_command("PTTL", name)
2132 def hrandfield(
2133 self, key: str, count: int = None, withvalues: bool = False
2134 ) -> ResponseT:
2135 """
2136 Return a random field from the hash value stored at key.
2138 count: if the argument is positive, return an array of distinct fields.
2139 If called with a negative count, the behavior changes and the command
2140 is allowed to return the same field multiple times. In this case,
2141 the number of returned fields is the absolute value of the
2142 specified count.
2143 withvalues: The optional WITHVALUES modifier changes the reply so it
2144 includes the respective values of the randomly selected hash fields.
2146 For more information see https://redis.io/commands/hrandfield
2147 """
2148 params = []
2149 if count is not None:
2150 params.append(count)
2151 if withvalues:
2152 params.append("WITHVALUES")
2154 return self.execute_command("HRANDFIELD", key, *params)
2156 def randomkey(self, **kwargs) -> ResponseT:
2157 """
2158 Returns the name of a random key
2160 For more information see https://redis.io/commands/randomkey
2161 """
2162 return self.execute_command("RANDOMKEY", **kwargs)
2164 def rename(self, src: KeyT, dst: KeyT) -> ResponseT:
2165 """
2166 Rename key ``src`` to ``dst``
2168 For more information see https://redis.io/commands/rename
2169 """
2170 return self.execute_command("RENAME", src, dst)
2172 def renamenx(self, src: KeyT, dst: KeyT):
2173 """
2174 Rename key ``src`` to ``dst`` if ``dst`` doesn't already exist
2176 For more information see https://redis.io/commands/renamenx
2177 """
2178 return self.execute_command("RENAMENX", src, dst)
2180 def restore(
2181 self,
2182 name: KeyT,
2183 ttl: float,
2184 value: EncodableT,
2185 replace: bool = False,
2186 absttl: bool = False,
2187 idletime: Union[int, None] = None,
2188 frequency: Union[int, None] = None,
2189 ) -> ResponseT:
2190 """
2191 Create a key using the provided serialized value, previously obtained
2192 using DUMP.
2194 ``replace`` allows an existing key on ``name`` to be overridden. If
2195 it's not specified an error is raised on collision.
2197 ``absttl`` if True, specified ``ttl`` should represent an absolute Unix
2198 timestamp in milliseconds in which the key will expire. (Redis 5.0 or
2199 greater).
2201 ``idletime`` Used for eviction, this is the number of seconds the
2202 key must be idle, prior to execution.
2204 ``frequency`` Used for eviction, this is the frequency counter of
2205 the object stored at the key, prior to execution.
2207 For more information see https://redis.io/commands/restore
2208 """
2209 params = [name, ttl, value]
2210 if replace:
2211 params.append("REPLACE")
2212 if absttl:
2213 params.append("ABSTTL")
2214 if idletime is not None:
2215 params.append("IDLETIME")
2216 try:
2217 params.append(int(idletime))
2218 except ValueError:
2219 raise DataError("idletimemust be an integer")
2221 if frequency is not None:
2222 params.append("FREQ")
2223 try:
2224 params.append(int(frequency))
2225 except ValueError:
2226 raise DataError("frequency must be an integer")
2228 return self.execute_command("RESTORE", *params)
2230 def set(
2231 self,
2232 name: KeyT,
2233 value: EncodableT,
2234 ex: Union[ExpiryT, None] = None,
2235 px: Union[ExpiryT, None] = None,
2236 nx: bool = False,
2237 xx: bool = False,
2238 keepttl: bool = False,
2239 get: bool = False,
2240 exat: Union[AbsExpiryT, None] = None,
2241 pxat: Union[AbsExpiryT, None] = None,
2242 ) -> ResponseT:
2243 """
2244 Set the value at key ``name`` to ``value``
2246 ``ex`` sets an expire flag on key ``name`` for ``ex`` seconds.
2248 ``px`` sets an expire flag on key ``name`` for ``px`` milliseconds.
2250 ``nx`` if set to True, set the value at key ``name`` to ``value`` only
2251 if it does not exist.
2253 ``xx`` if set to True, set the value at key ``name`` to ``value`` only
2254 if it already exists.
2256 ``keepttl`` if True, retain the time to live associated with the key.
2257 (Available since Redis 6.0)
2259 ``get`` if True, set the value at key ``name`` to ``value`` and return
2260 the old value stored at key, or None if the key did not exist.
2261 (Available since Redis 6.2)
2263 ``exat`` sets an expire flag on key ``name`` for ``ex`` seconds,
2264 specified in unix time.
2266 ``pxat`` sets an expire flag on key ``name`` for ``ex`` milliseconds,
2267 specified in unix time.
2269 For more information see https://redis.io/commands/set
2270 """
2271 pieces: list[EncodableT] = [name, value]
2272 options = {}
2273 if ex is not None:
2274 pieces.append("EX")
2275 if isinstance(ex, datetime.timedelta):
2276 pieces.append(int(ex.total_seconds()))
2277 elif isinstance(ex, int):
2278 pieces.append(ex)
2279 elif isinstance(ex, str) and ex.isdigit():
2280 pieces.append(int(ex))
2281 else:
2282 raise DataError("ex must be datetime.timedelta or int")
2283 if px is not None:
2284 pieces.append("PX")
2285 if isinstance(px, datetime.timedelta):
2286 pieces.append(int(px.total_seconds() * 1000))
2287 elif isinstance(px, int):
2288 pieces.append(px)
2289 else:
2290 raise DataError("px must be datetime.timedelta or int")
2291 if exat is not None:
2292 pieces.append("EXAT")
2293 if isinstance(exat, datetime.datetime):
2294 exat = int(exat.timestamp())
2295 pieces.append(exat)
2296 if pxat is not None:
2297 pieces.append("PXAT")
2298 if isinstance(pxat, datetime.datetime):
2299 pxat = int(pxat.timestamp() * 1000)
2300 pieces.append(pxat)
2301 if keepttl:
2302 pieces.append("KEEPTTL")
2304 if nx:
2305 pieces.append("NX")
2306 if xx:
2307 pieces.append("XX")
2309 if get:
2310 pieces.append("GET")
2311 options["get"] = True
2313 return self.execute_command("SET", *pieces, **options)
2315 def __setitem__(self, name: KeyT, value: EncodableT):
2316 self.set(name, value)
2318 def setbit(self, name: KeyT, offset: int, value: int) -> ResponseT:
2319 """
2320 Flag the ``offset`` in ``name`` as ``value``. Returns an integer
2321 indicating the previous value of ``offset``.
2323 For more information see https://redis.io/commands/setbit
2324 """
2325 value = value and 1 or 0
2326 return self.execute_command("SETBIT", name, offset, value)
2328 def setex(self, name: KeyT, time: ExpiryT, value: EncodableT) -> ResponseT:
2329 """
2330 Set the value of key ``name`` to ``value`` that expires in ``time``
2331 seconds. ``time`` can be represented by an integer or a Python
2332 timedelta object.
2334 For more information see https://redis.io/commands/setex
2335 """
2336 if isinstance(time, datetime.timedelta):
2337 time = int(time.total_seconds())
2338 return self.execute_command("SETEX", name, time, value)
2340 def setnx(self, name: KeyT, value: EncodableT) -> ResponseT:
2341 """
2342 Set the value of key ``name`` to ``value`` if key doesn't exist
2344 For more information see https://redis.io/commands/setnx
2345 """
2346 return self.execute_command("SETNX", name, value)
2348 def setrange(self, name: KeyT, offset: int, value: EncodableT) -> ResponseT:
2349 """
2350 Overwrite bytes in the value of ``name`` starting at ``offset`` with
2351 ``value``. If ``offset`` plus the length of ``value`` exceeds the
2352 length of the original value, the new value will be larger than before.
2353 If ``offset`` exceeds the length of the original value, null bytes
2354 will be used to pad between the end of the previous value and the start
2355 of what's being injected.
2357 Returns the length of the new string.
2359 For more information see https://redis.io/commands/setrange
2360 """
2361 return self.execute_command("SETRANGE", name, offset, value)
2363 def stralgo(
2364 self,
2365 algo: Literal["LCS"],
2366 value1: KeyT,
2367 value2: KeyT,
2368 specific_argument: Union[Literal["strings"], Literal["keys"]] = "strings",
2369 len: bool = False,
2370 idx: bool = False,
2371 minmatchlen: Union[int, None] = None,
2372 withmatchlen: bool = False,
2373 **kwargs,
2374 ) -> ResponseT:
2375 """
2376 Implements complex algorithms that operate on strings.
2377 Right now the only algorithm implemented is the LCS algorithm
2378 (longest common substring). However new algorithms could be
2379 implemented in the future.
2381 ``algo`` Right now must be LCS
2382 ``value1`` and ``value2`` Can be two strings or two keys
2383 ``specific_argument`` Specifying if the arguments to the algorithm
2384 will be keys or strings. strings is the default.
2385 ``len`` Returns just the len of the match.
2386 ``idx`` Returns the match positions in each string.
2387 ``minmatchlen`` Restrict the list of matches to the ones of a given
2388 minimal length. Can be provided only when ``idx`` set to True.
2389 ``withmatchlen`` Returns the matches with the len of the match.
2390 Can be provided only when ``idx`` set to True.
2392 For more information see https://redis.io/commands/stralgo
2393 """
2394 # check validity
2395 supported_algo = ["LCS"]
2396 if algo not in supported_algo:
2397 supported_algos_str = ", ".join(supported_algo)
2398 raise DataError(f"The supported algorithms are: {supported_algos_str}")
2399 if specific_argument not in ["keys", "strings"]:
2400 raise DataError("specific_argument can be only keys or strings")
2401 if len and idx:
2402 raise DataError("len and idx cannot be provided together.")
2404 pieces: list[EncodableT] = [algo, specific_argument.upper(), value1, value2]
2405 if len:
2406 pieces.append(b"LEN")
2407 if idx:
2408 pieces.append(b"IDX")
2409 try:
2410 int(minmatchlen)
2411 pieces.extend([b"MINMATCHLEN", minmatchlen])
2412 except TypeError:
2413 pass
2414 if withmatchlen:
2415 pieces.append(b"WITHMATCHLEN")
2417 return self.execute_command(
2418 "STRALGO",
2419 *pieces,
2420 len=len,
2421 idx=idx,
2422 minmatchlen=minmatchlen,
2423 withmatchlen=withmatchlen,
2424 **kwargs,
2425 )
2427 def strlen(self, name: KeyT) -> ResponseT:
2428 """
2429 Return the number of bytes stored in the value of ``name``
2431 For more information see https://redis.io/commands/strlen
2432 """
2433 return self.execute_command("STRLEN", name)
2435 def substr(self, name: KeyT, start: int, end: int = -1) -> ResponseT:
2436 """
2437 Return a substring of the string at key ``name``. ``start`` and ``end``
2438 are 0-based integers specifying the portion of the string to return.
2439 """
2440 return self.execute_command("SUBSTR", name, start, end)
2442 def touch(self, *args: KeyT) -> ResponseT:
2443 """
2444 Alters the last access time of a key(s) ``*args``. A key is ignored
2445 if it does not exist.
2447 For more information see https://redis.io/commands/touch
2448 """
2449 return self.execute_command("TOUCH", *args)
2451 def ttl(self, name: KeyT) -> ResponseT:
2452 """
2453 Returns the number of seconds until the key ``name`` will expire
2455 For more information see https://redis.io/commands/ttl
2456 """
2457 return self.execute_command("TTL", name)
2459 def type(self, name: KeyT) -> ResponseT:
2460 """
2461 Returns the type of key ``name``
2463 For more information see https://redis.io/commands/type
2464 """
2465 return self.execute_command("TYPE", name)
2467 def watch(self, *names: KeyT) -> None:
2468 """
2469 Watches the values at keys ``names``, or None if the key doesn't exist
2471 For more information see https://redis.io/commands/watch
2472 """
2473 warnings.warn(DeprecationWarning("Call WATCH from a Pipeline object"))
2475 def unwatch(self) -> None:
2476 """
2477 Unwatches the value at key ``name``, or None of the key doesn't exist
2479 For more information see https://redis.io/commands/unwatch
2480 """
2481 warnings.warn(DeprecationWarning("Call UNWATCH from a Pipeline object"))
2483 def unlink(self, *names: KeyT) -> ResponseT:
2484 """
2485 Unlink one or more keys specified by ``names``
2487 For more information see https://redis.io/commands/unlink
2488 """
2489 return self.execute_command("UNLINK", *names)
2491 def lcs(
2492 self,
2493 key1: str,
2494 key2: str,
2495 len: Optional[bool] = False,
2496 idx: Optional[bool] = False,
2497 minmatchlen: Optional[int] = 0,
2498 withmatchlen: Optional[bool] = False,
2499 ) -> Union[str, int, list]:
2500 """
2501 Find the longest common subsequence between ``key1`` and ``key2``.
2502 If ``len`` is true the length of the match will will be returned.
2503 If ``idx`` is true the match position in each strings will be returned.
2504 ``minmatchlen`` restrict the list of matches to the ones of
2505 the given ``minmatchlen``.
2506 If ``withmatchlen`` the length of the match also will be returned.
2507 For more information see https://redis.io/commands/lcs
2508 """
2509 pieces = [key1, key2]
2510 if len:
2511 pieces.append("LEN")
2512 if idx:
2513 pieces.append("IDX")
2514 if minmatchlen != 0:
2515 pieces.extend(["MINMATCHLEN", minmatchlen])
2516 if withmatchlen:
2517 pieces.append("WITHMATCHLEN")
2518 return self.execute_command("LCS", *pieces)
2521class AsyncBasicKeyCommands(BasicKeyCommands):
2522 def __delitem__(self, name: KeyT):
2523 raise TypeError("Async Redis client does not support class deletion")
2525 def __contains__(self, name: KeyT):
2526 raise TypeError("Async Redis client does not support class inclusion")
2528 def __getitem__(self, name: KeyT):
2529 raise TypeError("Async Redis client does not support class retrieval")
2531 def __setitem__(self, name: KeyT, value: EncodableT):
2532 raise TypeError("Async Redis client does not support class assignment")
2534 async def watch(self, *names: KeyT) -> None:
2535 return super().watch(*names)
2537 async def unwatch(self) -> None:
2538 return super().unwatch()
2541class ListCommands(CommandsProtocol):
2542 """
2543 Redis commands for List data type.
2544 see: https://redis.io/topics/data-types#lists
2545 """
2547 def blpop(
2548 self, keys: List, timeout: Optional[int] = 0
2549 ) -> Union[Awaitable[list], list]:
2550 """
2551 LPOP a value off of the first non-empty list
2552 named in the ``keys`` list.
2554 If none of the lists in ``keys`` has a value to LPOP, then block
2555 for ``timeout`` seconds, or until a value gets pushed on to one
2556 of the lists.
2558 If timeout is 0, then block indefinitely.
2560 For more information see https://redis.io/commands/blpop
2561 """
2562 if timeout is None:
2563 timeout = 0
2564 keys = list_or_args(keys, None)
2565 keys.append(timeout)
2566 return self.execute_command("BLPOP", *keys)
2568 def brpop(
2569 self, keys: List, timeout: Optional[int] = 0
2570 ) -> Union[Awaitable[list], list]:
2571 """
2572 RPOP a value off of the first non-empty list
2573 named in the ``keys`` list.
2575 If none of the lists in ``keys`` has a value to RPOP, then block
2576 for ``timeout`` seconds, or until a value gets pushed on to one
2577 of the lists.
2579 If timeout is 0, then block indefinitely.
2581 For more information see https://redis.io/commands/brpop
2582 """
2583 if timeout is None:
2584 timeout = 0
2585 keys = list_or_args(keys, None)
2586 keys.append(timeout)
2587 return self.execute_command("BRPOP", *keys)
2589 def brpoplpush(
2590 self, src: str, dst: str, timeout: Optional[int] = 0
2591 ) -> Union[Awaitable[Optional[str]], Optional[str]]:
2592 """
2593 Pop a value off the tail of ``src``, push it on the head of ``dst``
2594 and then return it.
2596 This command blocks until a value is in ``src`` or until ``timeout``
2597 seconds elapse, whichever is first. A ``timeout`` value of 0 blocks
2598 forever.
2600 For more information see https://redis.io/commands/brpoplpush
2601 """
2602 if timeout is None:
2603 timeout = 0
2604 return self.execute_command("BRPOPLPUSH", src, dst, timeout)
2606 def blmpop(
2607 self,
2608 timeout: float,
2609 numkeys: int,
2610 *args: List[str],
2611 direction: str,
2612 count: Optional[int] = 1,
2613 ) -> Optional[list]:
2614 """
2615 Pop ``count`` values (default 1) from first non-empty in the list
2616 of provided key names.
2618 When all lists are empty this command blocks the connection until another
2619 client pushes to it or until the timeout, timeout of 0 blocks indefinitely
2621 For more information see https://redis.io/commands/blmpop
2622 """
2623 args = [timeout, numkeys, *args, direction, "COUNT", count]
2625 return self.execute_command("BLMPOP", *args)
2627 def lmpop(
2628 self,
2629 num_keys: int,
2630 *args: List[str],
2631 direction: str,
2632 count: Optional[int] = 1,
2633 ) -> Union[Awaitable[list], list]:
2634 """
2635 Pop ``count`` values (default 1) first non-empty list key from the list
2636 of args provided key names.
2638 For more information see https://redis.io/commands/lmpop
2639 """
2640 args = [num_keys] + list(args) + [direction]
2641 if count != 1:
2642 args.extend(["COUNT", count])
2644 return self.execute_command("LMPOP", *args)
2646 def lindex(
2647 self, name: str, index: int
2648 ) -> Union[Awaitable[Optional[str]], Optional[str]]:
2649 """
2650 Return the item from list ``name`` at position ``index``
2652 Negative indexes are supported and will return an item at the
2653 end of the list
2655 For more information see https://redis.io/commands/lindex
2656 """
2657 return self.execute_command("LINDEX", name, index)
2659 def linsert(
2660 self, name: str, where: str, refvalue: str, value: str
2661 ) -> Union[Awaitable[int], int]:
2662 """
2663 Insert ``value`` in list ``name`` either immediately before or after
2664 [``where``] ``refvalue``
2666 Returns the new length of the list on success or -1 if ``refvalue``
2667 is not in the list.
2669 For more information see https://redis.io/commands/linsert
2670 """
2671 return self.execute_command("LINSERT", name, where, refvalue, value)
2673 def llen(self, name: str) -> Union[Awaitable[int], int]:
2674 """
2675 Return the length of the list ``name``
2677 For more information see https://redis.io/commands/llen
2678 """
2679 return self.execute_command("LLEN", name)
2681 def lpop(
2682 self,
2683 name: str,
2684 count: Optional[int] = None,
2685 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]:
2686 """
2687 Removes and returns the first elements of the list ``name``.
2689 By default, the command pops a single element from the beginning of
2690 the list. When provided with the optional ``count`` argument, the reply
2691 will consist of up to count elements, depending on the list's length.
2693 For more information see https://redis.io/commands/lpop
2694 """
2695 if count is not None:
2696 return self.execute_command("LPOP", name, count)
2697 else:
2698 return self.execute_command("LPOP", name)
2700 def lpush(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]:
2701 """
2702 Push ``values`` onto the head of the list ``name``
2704 For more information see https://redis.io/commands/lpush
2705 """
2706 return self.execute_command("LPUSH", name, *values)
2708 def lpushx(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]:
2709 """
2710 Push ``value`` onto the head of the list ``name`` if ``name`` exists
2712 For more information see https://redis.io/commands/lpushx
2713 """
2714 return self.execute_command("LPUSHX", name, *values)
2716 def lrange(self, name: str, start: int, end: int) -> Union[Awaitable[list], list]:
2717 """
2718 Return a slice of the list ``name`` between
2719 position ``start`` and ``end``
2721 ``start`` and ``end`` can be negative numbers just like
2722 Python slicing notation
2724 For more information see https://redis.io/commands/lrange
2725 """
2726 return self.execute_command("LRANGE", name, start, end)
2728 def lrem(self, name: str, count: int, value: str) -> Union[Awaitable[int], int]:
2729 """
2730 Remove the first ``count`` occurrences of elements equal to ``value``
2731 from the list stored at ``name``.
2733 The count argument influences the operation in the following ways:
2734 count > 0: Remove elements equal to value moving from head to tail.
2735 count < 0: Remove elements equal to value moving from tail to head.
2736 count = 0: Remove all elements equal to value.
2738 For more information see https://redis.io/commands/lrem
2739 """
2740 return self.execute_command("LREM", name, count, value)
2742 def lset(self, name: str, index: int, value: str) -> Union[Awaitable[str], str]:
2743 """
2744 Set element at ``index`` of list ``name`` to ``value``
2746 For more information see https://redis.io/commands/lset
2747 """
2748 return self.execute_command("LSET", name, index, value)
2750 def ltrim(self, name: str, start: int, end: int) -> Union[Awaitable[str], str]:
2751 """
2752 Trim the list ``name``, removing all values not within the slice
2753 between ``start`` and ``end``
2755 ``start`` and ``end`` can be negative numbers just like
2756 Python slicing notation
2758 For more information see https://redis.io/commands/ltrim
2759 """
2760 return self.execute_command("LTRIM", name, start, end)
2762 def rpop(
2763 self,
2764 name: str,
2765 count: Optional[int] = None,
2766 ) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]:
2767 """
2768 Removes and returns the last elements of the list ``name``.
2770 By default, the command pops a single element from the end of the list.
2771 When provided with the optional ``count`` argument, the reply will
2772 consist of up to count elements, depending on the list's length.
2774 For more information see https://redis.io/commands/rpop
2775 """
2776 if count is not None:
2777 return self.execute_command("RPOP", name, count)
2778 else:
2779 return self.execute_command("RPOP", name)
2781 def rpoplpush(self, src: str, dst: str) -> Union[Awaitable[str], str]:
2782 """
2783 RPOP a value off of the ``src`` list and atomically LPUSH it
2784 on to the ``dst`` list. Returns the value.
2786 For more information see https://redis.io/commands/rpoplpush
2787 """
2788 return self.execute_command("RPOPLPUSH", src, dst)
2790 def rpush(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]:
2791 """
2792 Push ``values`` onto the tail of the list ``name``
2794 For more information see https://redis.io/commands/rpush
2795 """
2796 return self.execute_command("RPUSH", name, *values)
2798 def rpushx(self, name: str, value: str) -> Union[Awaitable[int], int]:
2799 """
2800 Push ``value`` onto the tail of the list ``name`` if ``name`` exists
2802 For more information see https://redis.io/commands/rpushx
2803 """
2804 return self.execute_command("RPUSHX", name, value)
2806 def lpos(
2807 self,
2808 name: str,
2809 value: str,
2810 rank: Optional[int] = None,
2811 count: Optional[int] = None,
2812 maxlen: Optional[int] = None,
2813 ) -> Union[str, List, None]:
2814 """
2815 Get position of ``value`` within the list ``name``
2817 If specified, ``rank`` indicates the "rank" of the first element to
2818 return in case there are multiple copies of ``value`` in the list.
2819 By default, LPOS returns the position of the first occurrence of
2820 ``value`` in the list. When ``rank`` 2, LPOS returns the position of
2821 the second ``value`` in the list. If ``rank`` is negative, LPOS
2822 searches the list in reverse. For example, -1 would return the
2823 position of the last occurrence of ``value`` and -2 would return the
2824 position of the next to last occurrence of ``value``.
2826 If specified, ``count`` indicates that LPOS should return a list of
2827 up to ``count`` positions. A ``count`` of 2 would return a list of
2828 up to 2 positions. A ``count`` of 0 returns a list of all positions
2829 matching ``value``. When ``count`` is specified and but ``value``
2830 does not exist in the list, an empty list is returned.
2832 If specified, ``maxlen`` indicates the maximum number of list
2833 elements to scan. A ``maxlen`` of 1000 will only return the
2834 position(s) of items within the first 1000 entries in the list.
2835 A ``maxlen`` of 0 (the default) will scan the entire list.
2837 For more information see https://redis.io/commands/lpos
2838 """
2839 pieces: list[EncodableT] = [name, value]
2840 if rank is not None:
2841 pieces.extend(["RANK", rank])
2843 if count is not None:
2844 pieces.extend(["COUNT", count])
2846 if maxlen is not None:
2847 pieces.extend(["MAXLEN", maxlen])
2849 return self.execute_command("LPOS", *pieces)
2851 def sort(
2852 self,
2853 name: str,
2854 start: Optional[int] = None,
2855 num: Optional[int] = None,
2856 by: Optional[str] = None,
2857 get: Optional[List[str]] = None,
2858 desc: bool = False,
2859 alpha: bool = False,
2860 store: Optional[str] = None,
2861 groups: Optional[bool] = False,
2862 ) -> Union[List, int]:
2863 """
2864 Sort and return the list, set or sorted set at ``name``.
2866 ``start`` and ``num`` allow for paging through the sorted data
2868 ``by`` allows using an external key to weight and sort the items.
2869 Use an "*" to indicate where in the key the item value is located
2871 ``get`` allows for returning items from external keys rather than the
2872 sorted data itself. Use an "*" to indicate where in the key
2873 the item value is located
2875 ``desc`` allows for reversing the sort
2877 ``alpha`` allows for sorting lexicographically rather than numerically
2879 ``store`` allows for storing the result of the sort into
2880 the key ``store``
2882 ``groups`` if set to True and if ``get`` contains at least two
2883 elements, sort will return a list of tuples, each containing the
2884 values fetched from the arguments to ``get``.
2886 For more information see https://redis.io/commands/sort
2887 """
2888 if (start is not None and num is None) or (num is not None and start is None):
2889 raise DataError("``start`` and ``num`` must both be specified")
2891 pieces: list[EncodableT] = [name]
2892 if by is not None:
2893 pieces.extend([b"BY", by])
2894 if start is not None and num is not None:
2895 pieces.extend([b"LIMIT", start, num])
2896 if get is not None:
2897 # If get is a string assume we want to get a single value.
2898 # Otherwise assume it's an interable and we want to get multiple
2899 # values. We can't just iterate blindly because strings are
2900 # iterable.
2901 if isinstance(get, (bytes, str)):
2902 pieces.extend([b"GET", get])
2903 else:
2904 for g in get:
2905 pieces.extend([b"GET", g])
2906 if desc:
2907 pieces.append(b"DESC")
2908 if alpha:
2909 pieces.append(b"ALPHA")
2910 if store is not None:
2911 pieces.extend([b"STORE", store])
2912 if groups:
2913 if not get or isinstance(get, (bytes, str)) or len(get) < 2:
2914 raise DataError(
2915 'when using "groups" the "get" argument '
2916 "must be specified and contain at least "
2917 "two keys"
2918 )
2920 options = {"groups": len(get) if groups else None}
2921 return self.execute_command("SORT", *pieces, **options)
2923 def sort_ro(
2924 self,
2925 key: str,
2926 start: Optional[int] = None,
2927 num: Optional[int] = None,
2928 by: Optional[str] = None,
2929 get: Optional[List[str]] = None,
2930 desc: bool = False,
2931 alpha: bool = False,
2932 ) -> list:
2933 """
2934 Returns the elements contained in the list, set or sorted set at key.
2935 (read-only variant of the SORT command)
2937 ``start`` and ``num`` allow for paging through the sorted data
2939 ``by`` allows using an external key to weight and sort the items.
2940 Use an "*" to indicate where in the key the item value is located
2942 ``get`` allows for returning items from external keys rather than the
2943 sorted data itself. Use an "*" to indicate where in the key
2944 the item value is located
2946 ``desc`` allows for reversing the sort
2948 ``alpha`` allows for sorting lexicographically rather than numerically
2950 For more information see https://redis.io/commands/sort_ro
2951 """
2952 return self.sort(
2953 key, start=start, num=num, by=by, get=get, desc=desc, alpha=alpha
2954 )
2957AsyncListCommands = ListCommands
2960class ScanCommands(CommandsProtocol):
2961 """
2962 Redis SCAN commands.
2963 see: https://redis.io/commands/scan
2964 """
2966 def scan(
2967 self,
2968 cursor: int = 0,
2969 match: Union[PatternT, None] = None,
2970 count: Union[int, None] = None,
2971 _type: Union[str, None] = None,
2972 **kwargs,
2973 ) -> ResponseT:
2974 """
2975 Incrementally return lists of key names. Also return a cursor
2976 indicating the scan position.
2978 ``match`` allows for filtering the keys by pattern
2980 ``count`` provides a hint to Redis about the number of keys to
2981 return per batch.
2983 ``_type`` filters the returned values by a particular Redis type.
2984 Stock Redis instances allow for the following types:
2985 HASH, LIST, SET, STREAM, STRING, ZSET
2986 Additionally, Redis modules can expose other types as well.
2988 For more information see https://redis.io/commands/scan
2989 """
2990 pieces: list[EncodableT] = [cursor]
2991 if match is not None:
2992 pieces.extend([b"MATCH", match])
2993 if count is not None:
2994 pieces.extend([b"COUNT", count])
2995 if _type is not None:
2996 pieces.extend([b"TYPE", _type])
2997 return self.execute_command("SCAN", *pieces, **kwargs)
2999 def scan_iter(
3000 self,
3001 match: Union[PatternT, None] = None,
3002 count: Union[int, None] = None,
3003 _type: Union[str, None] = None,
3004 **kwargs,
3005 ) -> Iterator:
3006 """
3007 Make an iterator using the SCAN command so that the client doesn't
3008 need to remember the cursor position.
3010 ``match`` allows for filtering the keys by pattern
3012 ``count`` provides a hint to Redis about the number of keys to
3013 return per batch.
3015 ``_type`` filters the returned values by a particular Redis type.
3016 Stock Redis instances allow for the following types:
3017 HASH, LIST, SET, STREAM, STRING, ZSET
3018 Additionally, Redis modules can expose other types as well.
3019 """
3020 cursor = "0"
3021 while cursor != 0:
3022 cursor, data = self.scan(
3023 cursor=cursor, match=match, count=count, _type=_type, **kwargs
3024 )
3025 yield from data
3027 def sscan(
3028 self,
3029 name: KeyT,
3030 cursor: int = 0,
3031 match: Union[PatternT, None] = None,
3032 count: Union[int, None] = None,
3033 ) -> ResponseT:
3034 """
3035 Incrementally return lists of elements in a set. Also return a cursor
3036 indicating the scan position.
3038 ``match`` allows for filtering the keys by pattern
3040 ``count`` allows for hint the minimum number of returns
3042 For more information see https://redis.io/commands/sscan
3043 """
3044 pieces: list[EncodableT] = [name, cursor]
3045 if match is not None:
3046 pieces.extend([b"MATCH", match])
3047 if count is not None:
3048 pieces.extend([b"COUNT", count])
3049 return self.execute_command("SSCAN", *pieces)
3051 def sscan_iter(
3052 self,
3053 name: KeyT,
3054 match: Union[PatternT, None] = None,
3055 count: Union[int, None] = None,
3056 ) -> Iterator:
3057 """
3058 Make an iterator using the SSCAN command so that the client doesn't
3059 need to remember the cursor position.
3061 ``match`` allows for filtering the keys by pattern
3063 ``count`` allows for hint the minimum number of returns
3064 """
3065 cursor = "0"
3066 while cursor != 0:
3067 cursor, data = self.sscan(name, cursor=cursor, match=match, count=count)
3068 yield from data
3070 def hscan(
3071 self,
3072 name: KeyT,
3073 cursor: int = 0,
3074 match: Union[PatternT, None] = None,
3075 count: Union[int, None] = None,
3076 ) -> ResponseT:
3077 """
3078 Incrementally return key/value slices in a hash. Also return a cursor
3079 indicating the scan position.
3081 ``match`` allows for filtering the keys by pattern
3083 ``count`` allows for hint the minimum number of returns
3085 For more information see https://redis.io/commands/hscan
3086 """
3087 pieces: list[EncodableT] = [name, cursor]
3088 if match is not None:
3089 pieces.extend([b"MATCH", match])
3090 if count is not None:
3091 pieces.extend([b"COUNT", count])
3092 return self.execute_command("HSCAN", *pieces)
3094 def hscan_iter(
3095 self,
3096 name: str,
3097 match: Union[PatternT, None] = None,
3098 count: Union[int, None] = None,
3099 ) -> Iterator:
3100 """
3101 Make an iterator using the HSCAN command so that the client doesn't
3102 need to remember the cursor position.
3104 ``match`` allows for filtering the keys by pattern
3106 ``count`` allows for hint the minimum number of returns
3107 """
3108 cursor = "0"
3109 while cursor != 0:
3110 cursor, data = self.hscan(name, cursor=cursor, match=match, count=count)
3111 yield from data.items()
3113 def zscan(
3114 self,
3115 name: KeyT,
3116 cursor: int = 0,
3117 match: Union[PatternT, None] = None,
3118 count: Union[int, None] = None,
3119 score_cast_func: Union[type, Callable] = float,
3120 ) -> ResponseT:
3121 """
3122 Incrementally return lists of elements in a sorted set. Also return a
3123 cursor indicating the scan position.
3125 ``match`` allows for filtering the keys by pattern
3127 ``count`` allows for hint the minimum number of returns
3129 ``score_cast_func`` a callable used to cast the score return value
3131 For more information see https://redis.io/commands/zscan
3132 """
3133 pieces = [name, cursor]
3134 if match is not None:
3135 pieces.extend([b"MATCH", match])
3136 if count is not None:
3137 pieces.extend([b"COUNT", count])
3138 options = {"score_cast_func": score_cast_func}
3139 return self.execute_command("ZSCAN", *pieces, **options)
3141 def zscan_iter(
3142 self,
3143 name: KeyT,
3144 match: Union[PatternT, None] = None,
3145 count: Union[int, None] = None,
3146 score_cast_func: Union[type, Callable] = float,
3147 ) -> Iterator:
3148 """
3149 Make an iterator using the ZSCAN command so that the client doesn't
3150 need to remember the cursor position.
3152 ``match`` allows for filtering the keys by pattern
3154 ``count`` allows for hint the minimum number of returns
3156 ``score_cast_func`` a callable used to cast the score return value
3157 """
3158 cursor = "0"
3159 while cursor != 0:
3160 cursor, data = self.zscan(
3161 name,
3162 cursor=cursor,
3163 match=match,
3164 count=count,
3165 score_cast_func=score_cast_func,
3166 )
3167 yield from data
3170class AsyncScanCommands(ScanCommands):
3171 async def scan_iter(
3172 self,
3173 match: Union[PatternT, None] = None,
3174 count: Union[int, None] = None,
3175 _type: Union[str, None] = None,
3176 **kwargs,
3177 ) -> AsyncIterator:
3178 """
3179 Make an iterator using the SCAN command so that the client doesn't
3180 need to remember the cursor position.
3182 ``match`` allows for filtering the keys by pattern
3184 ``count`` provides a hint to Redis about the number of keys to
3185 return per batch.
3187 ``_type`` filters the returned values by a particular Redis type.
3188 Stock Redis instances allow for the following types:
3189 HASH, LIST, SET, STREAM, STRING, ZSET
3190 Additionally, Redis modules can expose other types as well.
3191 """
3192 cursor = "0"
3193 while cursor != 0:
3194 cursor, data = await self.scan(
3195 cursor=cursor, match=match, count=count, _type=_type, **kwargs
3196 )
3197 for d in data:
3198 yield d
3200 async def sscan_iter(
3201 self,
3202 name: KeyT,
3203 match: Union[PatternT, None] = None,
3204 count: Union[int, None] = None,
3205 ) -> AsyncIterator:
3206 """
3207 Make an iterator using the SSCAN command so that the client doesn't
3208 need to remember the cursor position.
3210 ``match`` allows for filtering the keys by pattern
3212 ``count`` allows for hint the minimum number of returns
3213 """
3214 cursor = "0"
3215 while cursor != 0:
3216 cursor, data = await self.sscan(
3217 name, cursor=cursor, match=match, count=count
3218 )
3219 for d in data:
3220 yield d
3222 async def hscan_iter(
3223 self,
3224 name: str,
3225 match: Union[PatternT, None] = None,
3226 count: Union[int, None] = None,
3227 ) -> AsyncIterator:
3228 """
3229 Make an iterator using the HSCAN command so that the client doesn't
3230 need to remember the cursor position.
3232 ``match`` allows for filtering the keys by pattern
3234 ``count`` allows for hint the minimum number of returns
3235 """
3236 cursor = "0"
3237 while cursor != 0:
3238 cursor, data = await self.hscan(
3239 name, cursor=cursor, match=match, count=count
3240 )
3241 for it in data.items():
3242 yield it
3244 async def zscan_iter(
3245 self,
3246 name: KeyT,
3247 match: Union[PatternT, None] = None,
3248 count: Union[int, None] = None,
3249 score_cast_func: Union[type, Callable] = float,
3250 ) -> AsyncIterator:
3251 """
3252 Make an iterator using the ZSCAN command so that the client doesn't
3253 need to remember the cursor position.
3255 ``match`` allows for filtering the keys by pattern
3257 ``count`` allows for hint the minimum number of returns
3259 ``score_cast_func`` a callable used to cast the score return value
3260 """
3261 cursor = "0"
3262 while cursor != 0:
3263 cursor, data = await self.zscan(
3264 name,
3265 cursor=cursor,
3266 match=match,
3267 count=count,
3268 score_cast_func=score_cast_func,
3269 )
3270 for d in data:
3271 yield d
3274class SetCommands(CommandsProtocol):
3275 """
3276 Redis commands for Set data type.
3277 see: https://redis.io/topics/data-types#sets
3278 """
3280 def sadd(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]:
3281 """
3282 Add ``value(s)`` to set ``name``
3284 For more information see https://redis.io/commands/sadd
3285 """
3286 return self.execute_command("SADD", name, *values)
3288 def scard(self, name: str) -> Union[Awaitable[int], int]:
3289 """
3290 Return the number of elements in set ``name``
3292 For more information see https://redis.io/commands/scard
3293 """
3294 return self.execute_command("SCARD", name)
3296 def sdiff(self, keys: List, *args: List) -> Union[Awaitable[list], list]:
3297 """
3298 Return the difference of sets specified by ``keys``
3300 For more information see https://redis.io/commands/sdiff
3301 """
3302 args = list_or_args(keys, args)
3303 return self.execute_command("SDIFF", *args)
3305 def sdiffstore(
3306 self, dest: str, keys: List, *args: List
3307 ) -> Union[Awaitable[int], int]:
3308 """
3309 Store the difference of sets specified by ``keys`` into a new
3310 set named ``dest``. Returns the number of keys in the new set.
3312 For more information see https://redis.io/commands/sdiffstore
3313 """
3314 args = list_or_args(keys, args)
3315 return self.execute_command("SDIFFSTORE", dest, *args)
3317 def sinter(self, keys: List, *args: List) -> Union[Awaitable[list], list]:
3318 """
3319 Return the intersection of sets specified by ``keys``
3321 For more information see https://redis.io/commands/sinter
3322 """
3323 args = list_or_args(keys, args)
3324 return self.execute_command("SINTER", *args)
3326 def sintercard(
3327 self, numkeys: int, keys: List[str], limit: int = 0
3328 ) -> Union[Awaitable[int], int]:
3329 """
3330 Return the cardinality of the intersect of multiple sets specified by ``keys`.
3332 When LIMIT provided (defaults to 0 and means unlimited), if the intersection
3333 cardinality reaches limit partway through the computation, the algorithm will
3334 exit and yield limit as the cardinality
3336 For more information see https://redis.io/commands/sintercard
3337 """
3338 args = [numkeys, *keys, "LIMIT", limit]
3339 return self.execute_command("SINTERCARD", *args)
3341 def sinterstore(
3342 self, dest: str, keys: List, *args: List
3343 ) -> Union[Awaitable[int], int]:
3344 """
3345 Store the intersection of sets specified by ``keys`` into a new
3346 set named ``dest``. Returns the number of keys in the new set.
3348 For more information see https://redis.io/commands/sinterstore
3349 """
3350 args = list_or_args(keys, args)
3351 return self.execute_command("SINTERSTORE", dest, *args)
3353 def sismember(self, name: str, value: str) -> Union[Awaitable[bool], bool]:
3354 """
3355 Return a boolean indicating if ``value`` is a member of set ``name``
3357 For more information see https://redis.io/commands/sismember
3358 """
3359 return self.execute_command("SISMEMBER", name, value)
3361 def smembers(self, name: str) -> Union[Awaitable[Set], Set]:
3362 """
3363 Return all members of the set ``name``
3365 For more information see https://redis.io/commands/smembers
3366 """
3367 return self.execute_command("SMEMBERS", name)
3369 def smismember(
3370 self, name: str, values: List, *args: List
3371 ) -> Union[
3372 Awaitable[List[Union[Literal[0], Literal[1]]]],
3373 List[Union[Literal[0], Literal[1]]],
3374 ]:
3375 """
3376 Return whether each value in ``values`` is a member of the set ``name``
3377 as a list of ``int`` in the order of ``values``:
3378 - 1 if the value is a member of the set.
3379 - 0 if the value is not a member of the set or if key does not exist.
3381 For more information see https://redis.io/commands/smismember
3382 """
3383 args = list_or_args(values, args)
3384 return self.execute_command("SMISMEMBER", name, *args)
3386 def smove(self, src: str, dst: str, value: str) -> Union[Awaitable[bool], bool]:
3387 """
3388 Move ``value`` from set ``src`` to set ``dst`` atomically
3390 For more information see https://redis.io/commands/smove
3391 """
3392 return self.execute_command("SMOVE", src, dst, value)
3394 def spop(self, name: str, count: Optional[int] = None) -> Union[str, List, None]:
3395 """
3396 Remove and return a random member of set ``name``
3398 For more information see https://redis.io/commands/spop
3399 """
3400 args = (count is not None) and [count] or []
3401 return self.execute_command("SPOP", name, *args)
3403 def srandmember(
3404 self, name: str, number: Optional[int] = None
3405 ) -> Union[str, List, None]:
3406 """
3407 If ``number`` is None, returns a random member of set ``name``.
3409 If ``number`` is supplied, returns a list of ``number`` random
3410 members of set ``name``. Note this is only available when running
3411 Redis 2.6+.
3413 For more information see https://redis.io/commands/srandmember
3414 """
3415 args = (number is not None) and [number] or []
3416 return self.execute_command("SRANDMEMBER", name, *args)
3418 def srem(self, name: str, *values: FieldT) -> Union[Awaitable[int], int]:
3419 """
3420 Remove ``values`` from set ``name``
3422 For more information see https://redis.io/commands/srem
3423 """
3424 return self.execute_command("SREM", name, *values)
3426 def sunion(self, keys: List, *args: List) -> Union[Awaitable[List], List]:
3427 """
3428 Return the union of sets specified by ``keys``
3430 For more information see https://redis.io/commands/sunion
3431 """
3432 args = list_or_args(keys, args)
3433 return self.execute_command("SUNION", *args)
3435 def sunionstore(
3436 self, dest: str, keys: List, *args: List
3437 ) -> Union[Awaitable[int], int]:
3438 """
3439 Store the union of sets specified by ``keys`` into a new
3440 set named ``dest``. Returns the number of keys in the new set.
3442 For more information see https://redis.io/commands/sunionstore
3443 """
3444 args = list_or_args(keys, args)
3445 return self.execute_command("SUNIONSTORE", dest, *args)
3448AsyncSetCommands = SetCommands
3451class StreamCommands(CommandsProtocol):
3452 """
3453 Redis commands for Stream data type.
3454 see: https://redis.io/topics/streams-intro
3455 """
3457 def xack(self, name: KeyT, groupname: GroupT, *ids: StreamIdT) -> ResponseT:
3458 """
3459 Acknowledges the successful processing of one or more messages.
3460 name: name of the stream.
3461 groupname: name of the consumer group.
3462 *ids: message ids to acknowledge.
3464 For more information see https://redis.io/commands/xack
3465 """
3466 return self.execute_command("XACK", name, groupname, *ids)
3468 def xadd(
3469 self,
3470 name: KeyT,
3471 fields: Dict[FieldT, EncodableT],
3472 id: StreamIdT = "*",
3473 maxlen: Union[int, None] = None,
3474 approximate: bool = True,
3475 nomkstream: bool = False,
3476 minid: Union[StreamIdT, None] = None,
3477 limit: Union[int, None] = None,
3478 ) -> ResponseT:
3479 """
3480 Add to a stream.
3481 name: name of the stream
3482 fields: dict of field/value pairs to insert into the stream
3483 id: Location to insert this record. By default it is appended.
3484 maxlen: truncate old stream members beyond this size.
3485 Can't be specified with minid.
3486 approximate: actual stream length may be slightly more than maxlen
3487 nomkstream: When set to true, do not make a stream
3488 minid: the minimum id in the stream to query.
3489 Can't be specified with maxlen.
3490 limit: specifies the maximum number of entries to retrieve
3492 For more information see https://redis.io/commands/xadd
3493 """
3494 pieces: list[EncodableT] = []
3495 if maxlen is not None and minid is not None:
3496 raise DataError("Only one of ```maxlen``` or ```minid``` may be specified")
3498 if maxlen is not None:
3499 if not isinstance(maxlen, int) or maxlen < 0:
3500 raise DataError("XADD maxlen must be non-negative integer")
3501 pieces.append(b"MAXLEN")
3502 if approximate:
3503 pieces.append(b"~")
3504 pieces.append(str(maxlen))
3505 if minid is not None:
3506 pieces.append(b"MINID")
3507 if approximate:
3508 pieces.append(b"~")
3509 pieces.append(minid)
3510 if limit is not None:
3511 pieces.extend([b"LIMIT", limit])
3512 if nomkstream:
3513 pieces.append(b"NOMKSTREAM")
3514 pieces.append(id)
3515 if not isinstance(fields, dict) or len(fields) == 0:
3516 raise DataError("XADD fields must be a non-empty dict")
3517 for pair in fields.items():
3518 pieces.extend(pair)
3519 return self.execute_command("XADD", name, *pieces)
3521 def xautoclaim(
3522 self,
3523 name: KeyT,
3524 groupname: GroupT,
3525 consumername: ConsumerT,
3526 min_idle_time: int,
3527 start_id: StreamIdT = "0-0",
3528 count: Union[int, None] = None,
3529 justid: bool = False,
3530 ) -> ResponseT:
3531 """
3532 Transfers ownership of pending stream entries that match the specified
3533 criteria. Conceptually, equivalent to calling XPENDING and then XCLAIM,
3534 but provides a more straightforward way to deal with message delivery
3535 failures via SCAN-like semantics.
3536 name: name of the stream.
3537 groupname: name of the consumer group.
3538 consumername: name of a consumer that claims the message.
3539 min_idle_time: filter messages that were idle less than this amount of
3540 milliseconds.
3541 start_id: filter messages with equal or greater ID.
3542 count: optional integer, upper limit of the number of entries that the
3543 command attempts to claim. Set to 100 by default.
3544 justid: optional boolean, false by default. Return just an array of IDs
3545 of messages successfully claimed, without returning the actual message
3547 For more information see https://redis.io/commands/xautoclaim
3548 """
3549 try:
3550 if int(min_idle_time) < 0:
3551 raise DataError(
3552 "XAUTOCLAIM min_idle_time must be a nonnegative integer"
3553 )
3554 except TypeError:
3555 pass
3557 kwargs = {}
3558 pieces = [name, groupname, consumername, min_idle_time, start_id]
3560 try:
3561 if int(count) < 0:
3562 raise DataError("XPENDING count must be a integer >= 0")
3563 pieces.extend([b"COUNT", count])
3564 except TypeError:
3565 pass
3566 if justid:
3567 pieces.append(b"JUSTID")
3568 kwargs["parse_justid"] = True
3570 return self.execute_command("XAUTOCLAIM", *pieces, **kwargs)
3572 def xclaim(
3573 self,
3574 name: KeyT,
3575 groupname: GroupT,
3576 consumername: ConsumerT,
3577 min_idle_time: int,
3578 message_ids: Union[List[StreamIdT], Tuple[StreamIdT]],
3579 idle: Union[int, None] = None,
3580 time: Union[int, None] = None,
3581 retrycount: Union[int, None] = None,
3582 force: bool = False,
3583 justid: bool = False,
3584 ) -> ResponseT:
3585 """
3586 Changes the ownership of a pending message.
3587 name: name of the stream.
3588 groupname: name of the consumer group.
3589 consumername: name of a consumer that claims the message.
3590 min_idle_time: filter messages that were idle less than this amount of
3591 milliseconds
3592 message_ids: non-empty list or tuple of message IDs to claim
3593 idle: optional. Set the idle time (last time it was delivered) of the
3594 message in ms
3595 time: optional integer. This is the same as idle but instead of a
3596 relative amount of milliseconds, it sets the idle time to a specific
3597 Unix time (in milliseconds).
3598 retrycount: optional integer. set the retry counter to the specified
3599 value. This counter is incremented every time a message is delivered
3600 again.
3601 force: optional boolean, false by default. Creates the pending message
3602 entry in the PEL even if certain specified IDs are not already in the
3603 PEL assigned to a different client.
3604 justid: optional boolean, false by default. Return just an array of IDs
3605 of messages successfully claimed, without returning the actual message
3607 For more information see https://redis.io/commands/xclaim
3608 """
3609 if not isinstance(min_idle_time, int) or min_idle_time < 0:
3610 raise DataError("XCLAIM min_idle_time must be a non negative integer")
3611 if not isinstance(message_ids, (list, tuple)) or not message_ids:
3612 raise DataError(
3613 "XCLAIM message_ids must be a non empty list or "
3614 "tuple of message IDs to claim"
3615 )
3617 kwargs = {}
3618 pieces: list[EncodableT] = [name, groupname, consumername, str(min_idle_time)]
3619 pieces.extend(list(message_ids))
3621 if idle is not None:
3622 if not isinstance(idle, int):
3623 raise DataError("XCLAIM idle must be an integer")
3624 pieces.extend((b"IDLE", str(idle)))
3625 if time is not None:
3626 if not isinstance(time, int):
3627 raise DataError("XCLAIM time must be an integer")
3628 pieces.extend((b"TIME", str(time)))
3629 if retrycount is not None:
3630 if not isinstance(retrycount, int):
3631 raise DataError("XCLAIM retrycount must be an integer")
3632 pieces.extend((b"RETRYCOUNT", str(retrycount)))
3634 if force:
3635 if not isinstance(force, bool):
3636 raise DataError("XCLAIM force must be a boolean")
3637 pieces.append(b"FORCE")
3638 if justid:
3639 if not isinstance(justid, bool):
3640 raise DataError("XCLAIM justid must be a boolean")
3641 pieces.append(b"JUSTID")
3642 kwargs["parse_justid"] = True
3643 return self.execute_command("XCLAIM", *pieces, **kwargs)
3645 def xdel(self, name: KeyT, *ids: StreamIdT) -> ResponseT:
3646 """
3647 Deletes one or more messages from a stream.
3648 name: name of the stream.
3649 *ids: message ids to delete.
3651 For more information see https://redis.io/commands/xdel
3652 """
3653 return self.execute_command("XDEL", name, *ids)
3655 def xgroup_create(
3656 self,
3657 name: KeyT,
3658 groupname: GroupT,
3659 id: StreamIdT = "$",
3660 mkstream: bool = False,
3661 entries_read: Optional[int] = None,
3662 ) -> ResponseT:
3663 """
3664 Create a new consumer group associated with a stream.
3665 name: name of the stream.
3666 groupname: name of the consumer group.
3667 id: ID of the last item in the stream to consider already delivered.
3669 For more information see https://redis.io/commands/xgroup-create
3670 """
3671 pieces: list[EncodableT] = ["XGROUP CREATE", name, groupname, id]
3672 if mkstream:
3673 pieces.append(b"MKSTREAM")
3674 if entries_read is not None:
3675 pieces.extend(["ENTRIESREAD", entries_read])
3677 return self.execute_command(*pieces)
3679 def xgroup_delconsumer(
3680 self, name: KeyT, groupname: GroupT, consumername: ConsumerT
3681 ) -> ResponseT:
3682 """
3683 Remove a specific consumer from a consumer group.
3684 Returns the number of pending messages that the consumer had before it
3685 was deleted.
3686 name: name of the stream.
3687 groupname: name of the consumer group.
3688 consumername: name of consumer to delete
3690 For more information see https://redis.io/commands/xgroup-delconsumer
3691 """
3692 return self.execute_command("XGROUP DELCONSUMER", name, groupname, consumername)
3694 def xgroup_destroy(self, name: KeyT, groupname: GroupT) -> ResponseT:
3695 """
3696 Destroy a consumer group.
3697 name: name of the stream.
3698 groupname: name of the consumer group.
3700 For more information see https://redis.io/commands/xgroup-destroy
3701 """
3702 return self.execute_command("XGROUP DESTROY", name, groupname)
3704 def xgroup_createconsumer(
3705 self, name: KeyT, groupname: GroupT, consumername: ConsumerT
3706 ) -> ResponseT:
3707 """
3708 Consumers in a consumer group are auto-created every time a new
3709 consumer name is mentioned by some command.
3710 They can be explicitly created by using this command.
3711 name: name of the stream.
3712 groupname: name of the consumer group.
3713 consumername: name of consumer to create.
3715 See: https://redis.io/commands/xgroup-createconsumer
3716 """
3717 return self.execute_command(
3718 "XGROUP CREATECONSUMER", name, groupname, consumername
3719 )
3721 def xgroup_setid(
3722 self,
3723 name: KeyT,
3724 groupname: GroupT,
3725 id: StreamIdT,
3726 entries_read: Optional[int] = None,
3727 ) -> ResponseT:
3728 """
3729 Set the consumer group last delivered ID to something else.
3730 name: name of the stream.
3731 groupname: name of the consumer group.
3732 id: ID of the last item in the stream to consider already delivered.
3734 For more information see https://redis.io/commands/xgroup-setid
3735 """
3736 pieces = [name, groupname, id]
3737 if entries_read is not None:
3738 pieces.extend(["ENTRIESREAD", entries_read])
3739 return self.execute_command("XGROUP SETID", *pieces)
3741 def xinfo_consumers(self, name: KeyT, groupname: GroupT) -> ResponseT:
3742 """
3743 Returns general information about the consumers in the group.
3744 name: name of the stream.
3745 groupname: name of the consumer group.
3747 For more information see https://redis.io/commands/xinfo-consumers
3748 """
3749 return self.execute_command("XINFO CONSUMERS", name, groupname)
3751 def xinfo_groups(self, name: KeyT) -> ResponseT:
3752 """
3753 Returns general information about the consumer groups of the stream.
3754 name: name of the stream.
3756 For more information see https://redis.io/commands/xinfo-groups
3757 """
3758 return self.execute_command("XINFO GROUPS", name)
3760 def xinfo_stream(self, name: KeyT, full: bool = False) -> ResponseT:
3761 """
3762 Returns general information about the stream.
3763 name: name of the stream.
3764 full: optional boolean, false by default. Return full summary
3766 For more information see https://redis.io/commands/xinfo-stream
3767 """
3768 pieces = [name]
3769 options = {}
3770 if full:
3771 pieces.append(b"FULL")
3772 options = {"full": full}
3773 return self.execute_command("XINFO STREAM", *pieces, **options)
3775 def xlen(self, name: KeyT) -> ResponseT:
3776 """
3777 Returns the number of elements in a given stream.
3779 For more information see https://redis.io/commands/xlen
3780 """
3781 return self.execute_command("XLEN", name)
3783 def xpending(self, name: KeyT, groupname: GroupT) -> ResponseT:
3784 """
3785 Returns information about pending messages of a group.
3786 name: name of the stream.
3787 groupname: name of the consumer group.
3789 For more information see https://redis.io/commands/xpending
3790 """
3791 return self.execute_command("XPENDING", name, groupname)
3793 def xpending_range(
3794 self,
3795 name: KeyT,
3796 groupname: GroupT,
3797 min: StreamIdT,
3798 max: StreamIdT,
3799 count: int,
3800 consumername: Union[ConsumerT, None] = None,
3801 idle: Union[int, None] = None,
3802 ) -> ResponseT:
3803 """
3804 Returns information about pending messages, in a range.
3806 name: name of the stream.
3807 groupname: name of the consumer group.
3808 idle: available from version 6.2. filter entries by their
3809 idle-time, given in milliseconds (optional).
3810 min: minimum stream ID.
3811 max: maximum stream ID.
3812 count: number of messages to return
3813 consumername: name of a consumer to filter by (optional).
3814 """
3815 if {min, max, count} == {None}:
3816 if idle is not None or consumername is not None:
3817 raise DataError(
3818 "if XPENDING is provided with idle time"
3819 " or consumername, it must be provided"
3820 " with min, max and count parameters"
3821 )
3822 return self.xpending(name, groupname)
3824 pieces = [name, groupname]
3825 if min is None or max is None or count is None:
3826 raise DataError(
3827 "XPENDING must be provided with min, max "
3828 "and count parameters, or none of them."
3829 )
3830 # idle
3831 try:
3832 if int(idle) < 0:
3833 raise DataError("XPENDING idle must be a integer >= 0")
3834 pieces.extend(["IDLE", idle])
3835 except TypeError:
3836 pass
3837 # count
3838 try:
3839 if int(count) < 0:
3840 raise DataError("XPENDING count must be a integer >= 0")
3841 pieces.extend([min, max, count])
3842 except TypeError:
3843 pass
3844 # consumername
3845 if consumername:
3846 pieces.append(consumername)
3848 return self.execute_command("XPENDING", *pieces, parse_detail=True)
3850 def xrange(
3851 self,
3852 name: KeyT,
3853 min: StreamIdT = "-",
3854 max: StreamIdT = "+",
3855 count: Union[int, None] = None,
3856 ) -> ResponseT:
3857 """
3858 Read stream values within an interval.
3859 name: name of the stream.
3860 start: first stream ID. defaults to '-',
3861 meaning the earliest available.
3862 finish: last stream ID. defaults to '+',
3863 meaning the latest available.
3864 count: if set, only return this many items, beginning with the
3865 earliest available.
3867 For more information see https://redis.io/commands/xrange
3868 """
3869 pieces = [min, max]
3870 if count is not None:
3871 if not isinstance(count, int) or count < 1:
3872 raise DataError("XRANGE count must be a positive integer")
3873 pieces.append(b"COUNT")
3874 pieces.append(str(count))
3876 return self.execute_command("XRANGE", name, *pieces)
3878 def xread(
3879 self,
3880 streams: Dict[KeyT, StreamIdT],
3881 count: Union[int, None] = None,
3882 block: Union[int, None] = None,
3883 ) -> ResponseT:
3884 """
3885 Block and monitor multiple streams for new data.
3886 streams: a dict of stream names to stream IDs, where
3887 IDs indicate the last ID already seen.
3888 count: if set, only return this many items, beginning with the
3889 earliest available.
3890 block: number of milliseconds to wait, if nothing already present.
3892 For more information see https://redis.io/commands/xread
3893 """
3894 pieces = []
3895 if block is not None:
3896 if not isinstance(block, int) or block < 0:
3897 raise DataError("XREAD block must be a non-negative integer")
3898 pieces.append(b"BLOCK")
3899 pieces.append(str(block))
3900 if count is not None:
3901 if not isinstance(count, int) or count < 1:
3902 raise DataError("XREAD count must be a positive integer")
3903 pieces.append(b"COUNT")
3904 pieces.append(str(count))
3905 if not isinstance(streams, dict) or len(streams) == 0:
3906 raise DataError("XREAD streams must be a non empty dict")
3907 pieces.append(b"STREAMS")
3908 keys, values = zip(*streams.items())
3909 pieces.extend(keys)
3910 pieces.extend(values)
3911 return self.execute_command("XREAD", *pieces)
3913 def xreadgroup(
3914 self,
3915 groupname: str,
3916 consumername: str,
3917 streams: Dict[KeyT, StreamIdT],
3918 count: Union[int, None] = None,
3919 block: Union[int, None] = None,
3920 noack: bool = False,
3921 ) -> ResponseT:
3922 """
3923 Read from a stream via a consumer group.
3924 groupname: name of the consumer group.
3925 consumername: name of the requesting consumer.
3926 streams: a dict of stream names to stream IDs, where
3927 IDs indicate the last ID already seen.
3928 count: if set, only return this many items, beginning with the
3929 earliest available.
3930 block: number of milliseconds to wait, if nothing already present.
3931 noack: do not add messages to the PEL
3933 For more information see https://redis.io/commands/xreadgroup
3934 """
3935 pieces: list[EncodableT] = [b"GROUP", groupname, consumername]
3936 if count is not None:
3937 if not isinstance(count, int) or count < 1:
3938 raise DataError("XREADGROUP count must be a positive integer")
3939 pieces.append(b"COUNT")
3940 pieces.append(str(count))
3941 if block is not None:
3942 if not isinstance(block, int) or block < 0:
3943 raise DataError("XREADGROUP block must be a non-negative integer")
3944 pieces.append(b"BLOCK")
3945 pieces.append(str(block))
3946 if noack:
3947 pieces.append(b"NOACK")
3948 if not isinstance(streams, dict) or len(streams) == 0:
3949 raise DataError("XREADGROUP streams must be a non empty dict")
3950 pieces.append(b"STREAMS")
3951 pieces.extend(streams.keys())
3952 pieces.extend(streams.values())
3953 return self.execute_command("XREADGROUP", *pieces)
3955 def xrevrange(
3956 self,
3957 name: KeyT,
3958 max: StreamIdT = "+",
3959 min: StreamIdT = "-",
3960 count: Union[int, None] = None,
3961 ) -> ResponseT:
3962 """
3963 Read stream values within an interval, in reverse order.
3964 name: name of the stream
3965 start: first stream ID. defaults to '+',
3966 meaning the latest available.
3967 finish: last stream ID. defaults to '-',
3968 meaning the earliest available.
3969 count: if set, only return this many items, beginning with the
3970 latest available.
3972 For more information see https://redis.io/commands/xrevrange
3973 """
3974 pieces: list[EncodableT] = [max, min]
3975 if count is not None:
3976 if not isinstance(count, int) or count < 1:
3977 raise DataError("XREVRANGE count must be a positive integer")
3978 pieces.append(b"COUNT")
3979 pieces.append(str(count))
3981 return self.execute_command("XREVRANGE", name, *pieces)
3983 def xtrim(
3984 self,
3985 name: KeyT,
3986 maxlen: Union[int, None] = None,
3987 approximate: bool = True,
3988 minid: Union[StreamIdT, None] = None,
3989 limit: Union[int, None] = None,
3990 ) -> ResponseT:
3991 """
3992 Trims old messages from a stream.
3993 name: name of the stream.
3994 maxlen: truncate old stream messages beyond this size
3995 Can't be specified with minid.
3996 approximate: actual stream length may be slightly more than maxlen
3997 minid: the minimum id in the stream to query
3998 Can't be specified with maxlen.
3999 limit: specifies the maximum number of entries to retrieve
4001 For more information see https://redis.io/commands/xtrim
4002 """
4003 pieces: list[EncodableT] = []
4004 if maxlen is not None and minid is not None:
4005 raise DataError("Only one of ``maxlen`` or ``minid`` may be specified")
4007 if maxlen is None and minid is None:
4008 raise DataError("One of ``maxlen`` or ``minid`` must be specified")
4010 if maxlen is not None:
4011 pieces.append(b"MAXLEN")
4012 if minid is not None:
4013 pieces.append(b"MINID")
4014 if approximate:
4015 pieces.append(b"~")
4016 if maxlen is not None:
4017 pieces.append(maxlen)
4018 if minid is not None:
4019 pieces.append(minid)
4020 if limit is not None:
4021 pieces.append(b"LIMIT")
4022 pieces.append(limit)
4024 return self.execute_command("XTRIM", name, *pieces)
4027AsyncStreamCommands = StreamCommands
4030class SortedSetCommands(CommandsProtocol):
4031 """
4032 Redis commands for Sorted Sets data type.
4033 see: https://redis.io/topics/data-types-intro#redis-sorted-sets
4034 """
4036 def zadd(
4037 self,
4038 name: KeyT,
4039 mapping: Mapping[AnyKeyT, EncodableT],
4040 nx: bool = False,
4041 xx: bool = False,
4042 ch: bool = False,
4043 incr: bool = False,
4044 gt: bool = False,
4045 lt: bool = False,
4046 ) -> ResponseT:
4047 """
4048 Set any number of element-name, score pairs to the key ``name``. Pairs
4049 are specified as a dict of element-names keys to score values.
4051 ``nx`` forces ZADD to only create new elements and not to update
4052 scores for elements that already exist.
4054 ``xx`` forces ZADD to only update scores of elements that already
4055 exist. New elements will not be added.
4057 ``ch`` modifies the return value to be the numbers of elements changed.
4058 Changed elements include new elements that were added and elements
4059 whose scores changed.
4061 ``incr`` modifies ZADD to behave like ZINCRBY. In this mode only a
4062 single element/score pair can be specified and the score is the amount
4063 the existing score will be incremented by. When using this mode the
4064 return value of ZADD will be the new score of the element.
4066 ``LT`` Only update existing elements if the new score is less than
4067 the current score. This flag doesn't prevent adding new elements.
4069 ``GT`` Only update existing elements if the new score is greater than
4070 the current score. This flag doesn't prevent adding new elements.
4072 The return value of ZADD varies based on the mode specified. With no
4073 options, ZADD returns the number of new elements added to the sorted
4074 set.
4076 ``NX``, ``LT``, and ``GT`` are mutually exclusive options.
4078 See: https://redis.io/commands/ZADD
4079 """
4080 if not mapping:
4081 raise DataError("ZADD requires at least one element/score pair")
4082 if nx and xx:
4083 raise DataError("ZADD allows either 'nx' or 'xx', not both")
4084 if gt and lt:
4085 raise DataError("ZADD allows either 'gt' or 'lt', not both")
4086 if incr and len(mapping) != 1:
4087 raise DataError(
4088 "ZADD option 'incr' only works when passing a "
4089 "single element/score pair"
4090 )
4091 if nx and (gt or lt):
4092 raise DataError("Only one of 'nx', 'lt', or 'gr' may be defined.")
4094 pieces: list[EncodableT] = []
4095 options = {}
4096 if nx:
4097 pieces.append(b"NX")
4098 if xx:
4099 pieces.append(b"XX")
4100 if ch:
4101 pieces.append(b"CH")
4102 if incr:
4103 pieces.append(b"INCR")
4104 options["as_score"] = True
4105 if gt:
4106 pieces.append(b"GT")
4107 if lt:
4108 pieces.append(b"LT")
4109 for pair in mapping.items():
4110 pieces.append(pair[1])
4111 pieces.append(pair[0])
4112 return self.execute_command("ZADD", name, *pieces, **options)
4114 def zcard(self, name: KeyT) -> ResponseT:
4115 """
4116 Return the number of elements in the sorted set ``name``
4118 For more information see https://redis.io/commands/zcard
4119 """
4120 return self.execute_command("ZCARD", name)
4122 def zcount(self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT) -> ResponseT:
4123 """
4124 Returns the number of elements in the sorted set at key ``name`` with
4125 a score between ``min`` and ``max``.
4127 For more information see https://redis.io/commands/zcount
4128 """
4129 return self.execute_command("ZCOUNT", name, min, max)
4131 def zdiff(self, keys: KeysT, withscores: bool = False) -> ResponseT:
4132 """
4133 Returns the difference between the first and all successive input
4134 sorted sets provided in ``keys``.
4136 For more information see https://redis.io/commands/zdiff
4137 """
4138 pieces = [len(keys), *keys]
4139 if withscores:
4140 pieces.append("WITHSCORES")
4141 return self.execute_command("ZDIFF", *pieces)
4143 def zdiffstore(self, dest: KeyT, keys: KeysT) -> ResponseT:
4144 """
4145 Computes the difference between the first and all successive input
4146 sorted sets provided in ``keys`` and stores the result in ``dest``.
4148 For more information see https://redis.io/commands/zdiffstore
4149 """
4150 pieces = [len(keys), *keys]
4151 return self.execute_command("ZDIFFSTORE", dest, *pieces)
4153 def zincrby(self, name: KeyT, amount: float, value: EncodableT) -> ResponseT:
4154 """
4155 Increment the score of ``value`` in sorted set ``name`` by ``amount``
4157 For more information see https://redis.io/commands/zincrby
4158 """
4159 return self.execute_command("ZINCRBY", name, amount, value)
4161 def zinter(
4162 self, keys: KeysT, aggregate: Union[str, None] = None, withscores: bool = False
4163 ) -> ResponseT:
4164 """
4165 Return the intersect of multiple sorted sets specified by ``keys``.
4166 With the ``aggregate`` option, it is possible to specify how the
4167 results of the union are aggregated. This option defaults to SUM,
4168 where the score of an element is summed across the inputs where it
4169 exists. When this option is set to either MIN or MAX, the resulting
4170 set will contain the minimum or maximum score of an element across
4171 the inputs where it exists.
4173 For more information see https://redis.io/commands/zinter
4174 """
4175 return self._zaggregate("ZINTER", None, keys, aggregate, withscores=withscores)
4177 def zinterstore(
4178 self,
4179 dest: KeyT,
4180 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
4181 aggregate: Union[str, None] = None,
4182 ) -> ResponseT:
4183 """
4184 Intersect multiple sorted sets specified by ``keys`` into a new
4185 sorted set, ``dest``. Scores in the destination will be aggregated
4186 based on the ``aggregate``. This option defaults to SUM, where the
4187 score of an element is summed across the inputs where it exists.
4188 When this option is set to either MIN or MAX, the resulting set will
4189 contain the minimum or maximum score of an element across the inputs
4190 where it exists.
4192 For more information see https://redis.io/commands/zinterstore
4193 """
4194 return self._zaggregate("ZINTERSTORE", dest, keys, aggregate)
4196 def zintercard(
4197 self, numkeys: int, keys: List[str], limit: int = 0
4198 ) -> Union[Awaitable[int], int]:
4199 """
4200 Return the cardinality of the intersect of multiple sorted sets
4201 specified by ``keys`.
4202 When LIMIT provided (defaults to 0 and means unlimited), if the intersection
4203 cardinality reaches limit partway through the computation, the algorithm will
4204 exit and yield limit as the cardinality
4206 For more information see https://redis.io/commands/zintercard
4207 """
4208 args = [numkeys, *keys, "LIMIT", limit]
4209 return self.execute_command("ZINTERCARD", *args)
4211 def zlexcount(self, name, min, max):
4212 """
4213 Return the number of items in the sorted set ``name`` between the
4214 lexicographical range ``min`` and ``max``.
4216 For more information see https://redis.io/commands/zlexcount
4217 """
4218 return self.execute_command("ZLEXCOUNT", name, min, max)
4220 def zpopmax(self, name: KeyT, count: Union[int, None] = None) -> ResponseT:
4221 """
4222 Remove and return up to ``count`` members with the highest scores
4223 from the sorted set ``name``.
4225 For more information see https://redis.io/commands/zpopmax
4226 """
4227 args = (count is not None) and [count] or []
4228 options = {"withscores": True}
4229 return self.execute_command("ZPOPMAX", name, *args, **options)
4231 def zpopmin(self, name: KeyT, count: Union[int, None] = None) -> ResponseT:
4232 """
4233 Remove and return up to ``count`` members with the lowest scores
4234 from the sorted set ``name``.
4236 For more information see https://redis.io/commands/zpopmin
4237 """
4238 args = (count is not None) and [count] or []
4239 options = {"withscores": True}
4240 return self.execute_command("ZPOPMIN", name, *args, **options)
4242 def zrandmember(
4243 self, key: KeyT, count: int = None, withscores: bool = False
4244 ) -> ResponseT:
4245 """
4246 Return a random element from the sorted set value stored at key.
4248 ``count`` if the argument is positive, return an array of distinct
4249 fields. If called with a negative count, the behavior changes and
4250 the command is allowed to return the same field multiple times.
4251 In this case, the number of returned fields is the absolute value
4252 of the specified count.
4254 ``withscores`` The optional WITHSCORES modifier changes the reply so it
4255 includes the respective scores of the randomly selected elements from
4256 the sorted set.
4258 For more information see https://redis.io/commands/zrandmember
4259 """
4260 params = []
4261 if count is not None:
4262 params.append(count)
4263 if withscores:
4264 params.append("WITHSCORES")
4266 return self.execute_command("ZRANDMEMBER", key, *params)
4268 def bzpopmax(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT:
4269 """
4270 ZPOPMAX a value off of the first non-empty sorted set
4271 named in the ``keys`` list.
4273 If none of the sorted sets in ``keys`` has a value to ZPOPMAX,
4274 then block for ``timeout`` seconds, or until a member gets added
4275 to one of the sorted sets.
4277 If timeout is 0, then block indefinitely.
4279 For more information see https://redis.io/commands/bzpopmax
4280 """
4281 if timeout is None:
4282 timeout = 0
4283 keys = list_or_args(keys, None)
4284 keys.append(timeout)
4285 return self.execute_command("BZPOPMAX", *keys)
4287 def bzpopmin(self, keys: KeysT, timeout: TimeoutSecT = 0) -> ResponseT:
4288 """
4289 ZPOPMIN a value off of the first non-empty sorted set
4290 named in the ``keys`` list.
4292 If none of the sorted sets in ``keys`` has a value to ZPOPMIN,
4293 then block for ``timeout`` seconds, or until a member gets added
4294 to one of the sorted sets.
4296 If timeout is 0, then block indefinitely.
4298 For more information see https://redis.io/commands/bzpopmin
4299 """
4300 if timeout is None:
4301 timeout = 0
4302 keys: list[EncodableT] = list_or_args(keys, None)
4303 keys.append(timeout)
4304 return self.execute_command("BZPOPMIN", *keys)
4306 def zmpop(
4307 self,
4308 num_keys: int,
4309 keys: List[str],
4310 min: Optional[bool] = False,
4311 max: Optional[bool] = False,
4312 count: Optional[int] = 1,
4313 ) -> Union[Awaitable[list], list]:
4314 """
4315 Pop ``count`` values (default 1) off of the first non-empty sorted set
4316 named in the ``keys`` list.
4317 For more information see https://redis.io/commands/zmpop
4318 """
4319 args = [num_keys] + keys
4320 if (min and max) or (not min and not max):
4321 raise DataError
4322 elif min:
4323 args.append("MIN")
4324 else:
4325 args.append("MAX")
4326 if count != 1:
4327 args.extend(["COUNT", count])
4329 return self.execute_command("ZMPOP", *args)
4331 def bzmpop(
4332 self,
4333 timeout: float,
4334 numkeys: int,
4335 keys: List[str],
4336 min: Optional[bool] = False,
4337 max: Optional[bool] = False,
4338 count: Optional[int] = 1,
4339 ) -> Optional[list]:
4340 """
4341 Pop ``count`` values (default 1) off of the first non-empty sorted set
4342 named in the ``keys`` list.
4344 If none of the sorted sets in ``keys`` has a value to pop,
4345 then block for ``timeout`` seconds, or until a member gets added
4346 to one of the sorted sets.
4348 If timeout is 0, then block indefinitely.
4350 For more information see https://redis.io/commands/bzmpop
4351 """
4352 args = [timeout, numkeys, *keys]
4353 if (min and max) or (not min and not max):
4354 raise DataError("Either min or max, but not both must be set")
4355 elif min:
4356 args.append("MIN")
4357 else:
4358 args.append("MAX")
4359 args.extend(["COUNT", count])
4361 return self.execute_command("BZMPOP", *args)
4363 def _zrange(
4364 self,
4365 command,
4366 dest: Union[KeyT, None],
4367 name: KeyT,
4368 start: int,
4369 end: int,
4370 desc: bool = False,
4371 byscore: bool = False,
4372 bylex: bool = False,
4373 withscores: bool = False,
4374 score_cast_func: Union[type, Callable, None] = float,
4375 offset: Union[int, None] = None,
4376 num: Union[int, None] = None,
4377 ) -> ResponseT:
4378 if byscore and bylex:
4379 raise DataError("``byscore`` and ``bylex`` can not be specified together.")
4380 if (offset is not None and num is None) or (num is not None and offset is None):
4381 raise DataError("``offset`` and ``num`` must both be specified.")
4382 if bylex and withscores:
4383 raise DataError(
4384 "``withscores`` not supported in combination with ``bylex``."
4385 )
4386 pieces = [command]
4387 if dest:
4388 pieces.append(dest)
4389 pieces.extend([name, start, end])
4390 if byscore:
4391 pieces.append("BYSCORE")
4392 if bylex:
4393 pieces.append("BYLEX")
4394 if desc:
4395 pieces.append("REV")
4396 if offset is not None and num is not None:
4397 pieces.extend(["LIMIT", offset, num])
4398 if withscores:
4399 pieces.append("WITHSCORES")
4400 options = {"withscores": withscores, "score_cast_func": score_cast_func}
4401 return self.execute_command(*pieces, **options)
4403 def zrange(
4404 self,
4405 name: KeyT,
4406 start: int,
4407 end: int,
4408 desc: bool = False,
4409 withscores: bool = False,
4410 score_cast_func: Union[type, Callable] = float,
4411 byscore: bool = False,
4412 bylex: bool = False,
4413 offset: int = None,
4414 num: int = None,
4415 ) -> ResponseT:
4416 """
4417 Return a range of values from sorted set ``name`` between
4418 ``start`` and ``end`` sorted in ascending order.
4420 ``start`` and ``end`` can be negative, indicating the end of the range.
4422 ``desc`` a boolean indicating whether to sort the results in reversed
4423 order.
4425 ``withscores`` indicates to return the scores along with the values.
4426 The return type is a list of (value, score) pairs.
4428 ``score_cast_func`` a callable used to cast the score return value.
4430 ``byscore`` when set to True, returns the range of elements from the
4431 sorted set having scores equal or between ``start`` and ``end``.
4433 ``bylex`` when set to True, returns the range of elements from the
4434 sorted set between the ``start`` and ``end`` lexicographical closed
4435 range intervals.
4436 Valid ``start`` and ``end`` must start with ( or [, in order to specify
4437 whether the range interval is exclusive or inclusive, respectively.
4439 ``offset`` and ``num`` are specified, then return a slice of the range.
4440 Can't be provided when using ``bylex``.
4442 For more information see https://redis.io/commands/zrange
4443 """
4444 # Need to support ``desc`` also when using old redis version
4445 # because it was supported in 3.5.3 (of redis-py)
4446 if not byscore and not bylex and (offset is None and num is None) and desc:
4447 return self.zrevrange(name, start, end, withscores, score_cast_func)
4449 return self._zrange(
4450 "ZRANGE",
4451 None,
4452 name,
4453 start,
4454 end,
4455 desc,
4456 byscore,
4457 bylex,
4458 withscores,
4459 score_cast_func,
4460 offset,
4461 num,
4462 )
4464 def zrevrange(
4465 self,
4466 name: KeyT,
4467 start: int,
4468 end: int,
4469 withscores: bool = False,
4470 score_cast_func: Union[type, Callable] = float,
4471 ) -> ResponseT:
4472 """
4473 Return a range of values from sorted set ``name`` between
4474 ``start`` and ``end`` sorted in descending order.
4476 ``start`` and ``end`` can be negative, indicating the end of the range.
4478 ``withscores`` indicates to return the scores along with the values
4479 The return type is a list of (value, score) pairs
4481 ``score_cast_func`` a callable used to cast the score return value
4483 For more information see https://redis.io/commands/zrevrange
4484 """
4485 pieces = ["ZREVRANGE", name, start, end]
4486 if withscores:
4487 pieces.append(b"WITHSCORES")
4488 options = {"withscores": withscores, "score_cast_func": score_cast_func}
4489 return self.execute_command(*pieces, **options)
4491 def zrangestore(
4492 self,
4493 dest: KeyT,
4494 name: KeyT,
4495 start: int,
4496 end: int,
4497 byscore: bool = False,
4498 bylex: bool = False,
4499 desc: bool = False,
4500 offset: Union[int, None] = None,
4501 num: Union[int, None] = None,
4502 ) -> ResponseT:
4503 """
4504 Stores in ``dest`` the result of a range of values from sorted set
4505 ``name`` between ``start`` and ``end`` sorted in ascending order.
4507 ``start`` and ``end`` can be negative, indicating the end of the range.
4509 ``byscore`` when set to True, returns the range of elements from the
4510 sorted set having scores equal or between ``start`` and ``end``.
4512 ``bylex`` when set to True, returns the range of elements from the
4513 sorted set between the ``start`` and ``end`` lexicographical closed
4514 range intervals.
4515 Valid ``start`` and ``end`` must start with ( or [, in order to specify
4516 whether the range interval is exclusive or inclusive, respectively.
4518 ``desc`` a boolean indicating whether to sort the results in reversed
4519 order.
4521 ``offset`` and ``num`` are specified, then return a slice of the range.
4522 Can't be provided when using ``bylex``.
4524 For more information see https://redis.io/commands/zrangestore
4525 """
4526 return self._zrange(
4527 "ZRANGESTORE",
4528 dest,
4529 name,
4530 start,
4531 end,
4532 desc,
4533 byscore,
4534 bylex,
4535 False,
4536 None,
4537 offset,
4538 num,
4539 )
4541 def zrangebylex(
4542 self,
4543 name: KeyT,
4544 min: EncodableT,
4545 max: EncodableT,
4546 start: Union[int, None] = None,
4547 num: Union[int, None] = None,
4548 ) -> ResponseT:
4549 """
4550 Return the lexicographical range of values from sorted set ``name``
4551 between ``min`` and ``max``.
4553 If ``start`` and ``num`` are specified, then return a slice of the
4554 range.
4556 For more information see https://redis.io/commands/zrangebylex
4557 """
4558 if (start is not None and num is None) or (num is not None and start is None):
4559 raise DataError("``start`` and ``num`` must both be specified")
4560 pieces = ["ZRANGEBYLEX", name, min, max]
4561 if start is not None and num is not None:
4562 pieces.extend([b"LIMIT", start, num])
4563 return self.execute_command(*pieces)
4565 def zrevrangebylex(
4566 self,
4567 name: KeyT,
4568 max: EncodableT,
4569 min: EncodableT,
4570 start: Union[int, None] = None,
4571 num: Union[int, None] = None,
4572 ) -> ResponseT:
4573 """
4574 Return the reversed lexicographical range of values from sorted set
4575 ``name`` between ``max`` and ``min``.
4577 If ``start`` and ``num`` are specified, then return a slice of the
4578 range.
4580 For more information see https://redis.io/commands/zrevrangebylex
4581 """
4582 if (start is not None and num is None) or (num is not None and start is None):
4583 raise DataError("``start`` and ``num`` must both be specified")
4584 pieces = ["ZREVRANGEBYLEX", name, max, min]
4585 if start is not None and num is not None:
4586 pieces.extend(["LIMIT", start, num])
4587 return self.execute_command(*pieces)
4589 def zrangebyscore(
4590 self,
4591 name: KeyT,
4592 min: ZScoreBoundT,
4593 max: ZScoreBoundT,
4594 start: Union[int, None] = None,
4595 num: Union[int, None] = None,
4596 withscores: bool = False,
4597 score_cast_func: Union[type, Callable] = float,
4598 ) -> ResponseT:
4599 """
4600 Return a range of values from the sorted set ``name`` with scores
4601 between ``min`` and ``max``.
4603 If ``start`` and ``num`` are specified, then return a slice
4604 of the range.
4606 ``withscores`` indicates to return the scores along with the values.
4607 The return type is a list of (value, score) pairs
4609 `score_cast_func`` a callable used to cast the score return value
4611 For more information see https://redis.io/commands/zrangebyscore
4612 """
4613 if (start is not None and num is None) or (num is not None and start is None):
4614 raise DataError("``start`` and ``num`` must both be specified")
4615 pieces = ["ZRANGEBYSCORE", name, min, max]
4616 if start is not None and num is not None:
4617 pieces.extend(["LIMIT", start, num])
4618 if withscores:
4619 pieces.append("WITHSCORES")
4620 options = {"withscores": withscores, "score_cast_func": score_cast_func}
4621 return self.execute_command(*pieces, **options)
4623 def zrevrangebyscore(
4624 self,
4625 name: KeyT,
4626 max: ZScoreBoundT,
4627 min: ZScoreBoundT,
4628 start: Union[int, None] = None,
4629 num: Union[int, None] = None,
4630 withscores: bool = False,
4631 score_cast_func: Union[type, Callable] = float,
4632 ):
4633 """
4634 Return a range of values from the sorted set ``name`` with scores
4635 between ``min`` and ``max`` in descending order.
4637 If ``start`` and ``num`` are specified, then return a slice
4638 of the range.
4640 ``withscores`` indicates to return the scores along with the values.
4641 The return type is a list of (value, score) pairs
4643 ``score_cast_func`` a callable used to cast the score return value
4645 For more information see https://redis.io/commands/zrevrangebyscore
4646 """
4647 if (start is not None and num is None) or (num is not None and start is None):
4648 raise DataError("``start`` and ``num`` must both be specified")
4649 pieces = ["ZREVRANGEBYSCORE", name, max, min]
4650 if start is not None and num is not None:
4651 pieces.extend(["LIMIT", start, num])
4652 if withscores:
4653 pieces.append("WITHSCORES")
4654 options = {"withscores": withscores, "score_cast_func": score_cast_func}
4655 return self.execute_command(*pieces, **options)
4657 def zrank(
4658 self,
4659 name: KeyT,
4660 value: EncodableT,
4661 withscore: bool = False,
4662 ) -> ResponseT:
4663 """
4664 Returns a 0-based value indicating the rank of ``value`` in sorted set
4665 ``name``.
4666 The optional WITHSCORE argument supplements the command's
4667 reply with the score of the element returned.
4669 For more information see https://redis.io/commands/zrank
4670 """
4671 if withscore:
4672 return self.execute_command("ZRANK", name, value, "WITHSCORE")
4673 return self.execute_command("ZRANK", name, value)
4675 def zrem(self, name: KeyT, *values: FieldT) -> ResponseT:
4676 """
4677 Remove member ``values`` from sorted set ``name``
4679 For more information see https://redis.io/commands/zrem
4680 """
4681 return self.execute_command("ZREM", name, *values)
4683 def zremrangebylex(self, name: KeyT, min: EncodableT, max: EncodableT) -> ResponseT:
4684 """
4685 Remove all elements in the sorted set ``name`` between the
4686 lexicographical range specified by ``min`` and ``max``.
4688 Returns the number of elements removed.
4690 For more information see https://redis.io/commands/zremrangebylex
4691 """
4692 return self.execute_command("ZREMRANGEBYLEX", name, min, max)
4694 def zremrangebyrank(self, name: KeyT, min: int, max: int) -> ResponseT:
4695 """
4696 Remove all elements in the sorted set ``name`` with ranks between
4697 ``min`` and ``max``. Values are 0-based, ordered from smallest score
4698 to largest. Values can be negative indicating the highest scores.
4699 Returns the number of elements removed
4701 For more information see https://redis.io/commands/zremrangebyrank
4702 """
4703 return self.execute_command("ZREMRANGEBYRANK", name, min, max)
4705 def zremrangebyscore(
4706 self, name: KeyT, min: ZScoreBoundT, max: ZScoreBoundT
4707 ) -> ResponseT:
4708 """
4709 Remove all elements in the sorted set ``name`` with scores
4710 between ``min`` and ``max``. Returns the number of elements removed.
4712 For more information see https://redis.io/commands/zremrangebyscore
4713 """
4714 return self.execute_command("ZREMRANGEBYSCORE", name, min, max)
4716 def zrevrank(
4717 self,
4718 name: KeyT,
4719 value: EncodableT,
4720 withscore: bool = False,
4721 ) -> ResponseT:
4722 """
4723 Returns a 0-based value indicating the descending rank of
4724 ``value`` in sorted set ``name``.
4725 The optional ``withscore`` argument supplements the command's
4726 reply with the score of the element returned.
4728 For more information see https://redis.io/commands/zrevrank
4729 """
4730 if withscore:
4731 return self.execute_command("ZREVRANK", name, value, "WITHSCORE")
4732 return self.execute_command("ZREVRANK", name, value)
4734 def zscore(self, name: KeyT, value: EncodableT) -> ResponseT:
4735 """
4736 Return the score of element ``value`` in sorted set ``name``
4738 For more information see https://redis.io/commands/zscore
4739 """
4740 return self.execute_command("ZSCORE", name, value)
4742 def zunion(
4743 self,
4744 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
4745 aggregate: Union[str, None] = None,
4746 withscores: bool = False,
4747 ) -> ResponseT:
4748 """
4749 Return the union of multiple sorted sets specified by ``keys``.
4750 ``keys`` can be provided as dictionary of keys and their weights.
4751 Scores will be aggregated based on the ``aggregate``, or SUM if
4752 none is provided.
4754 For more information see https://redis.io/commands/zunion
4755 """
4756 return self._zaggregate("ZUNION", None, keys, aggregate, withscores=withscores)
4758 def zunionstore(
4759 self,
4760 dest: KeyT,
4761 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
4762 aggregate: Union[str, None] = None,
4763 ) -> ResponseT:
4764 """
4765 Union multiple sorted sets specified by ``keys`` into
4766 a new sorted set, ``dest``. Scores in the destination will be
4767 aggregated based on the ``aggregate``, or SUM if none is provided.
4769 For more information see https://redis.io/commands/zunionstore
4770 """
4771 return self._zaggregate("ZUNIONSTORE", dest, keys, aggregate)
4773 def zmscore(self, key: KeyT, members: List[str]) -> ResponseT:
4774 """
4775 Returns the scores associated with the specified members
4776 in the sorted set stored at key.
4777 ``members`` should be a list of the member name.
4778 Return type is a list of score.
4779 If the member does not exist, a None will be returned
4780 in corresponding position.
4782 For more information see https://redis.io/commands/zmscore
4783 """
4784 if not members:
4785 raise DataError("ZMSCORE members must be a non-empty list")
4786 pieces = [key] + members
4787 return self.execute_command("ZMSCORE", *pieces)
4789 def _zaggregate(
4790 self,
4791 command: str,
4792 dest: Union[KeyT, None],
4793 keys: Union[Sequence[KeyT], Mapping[AnyKeyT, float]],
4794 aggregate: Union[str, None] = None,
4795 **options,
4796 ) -> ResponseT:
4797 pieces: list[EncodableT] = [command]
4798 if dest is not None:
4799 pieces.append(dest)
4800 pieces.append(len(keys))
4801 if isinstance(keys, dict):
4802 keys, weights = keys.keys(), keys.values()
4803 else:
4804 weights = None
4805 pieces.extend(keys)
4806 if weights:
4807 pieces.append(b"WEIGHTS")
4808 pieces.extend(weights)
4809 if aggregate:
4810 if aggregate.upper() in ["SUM", "MIN", "MAX"]:
4811 pieces.append(b"AGGREGATE")
4812 pieces.append(aggregate)
4813 else:
4814 raise DataError("aggregate can be sum, min or max.")
4815 if options.get("withscores", False):
4816 pieces.append(b"WITHSCORES")
4817 return self.execute_command(*pieces, **options)
4820AsyncSortedSetCommands = SortedSetCommands
4823class HyperlogCommands(CommandsProtocol):
4824 """
4825 Redis commands of HyperLogLogs data type.
4826 see: https://redis.io/topics/data-types-intro#hyperloglogs
4827 """
4829 def pfadd(self, name: KeyT, *values: FieldT) -> ResponseT:
4830 """
4831 Adds the specified elements to the specified HyperLogLog.
4833 For more information see https://redis.io/commands/pfadd
4834 """
4835 return self.execute_command("PFADD", name, *values)
4837 def pfcount(self, *sources: KeyT) -> ResponseT:
4838 """
4839 Return the approximated cardinality of
4840 the set observed by the HyperLogLog at key(s).
4842 For more information see https://redis.io/commands/pfcount
4843 """
4844 return self.execute_command("PFCOUNT", *sources)
4846 def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT:
4847 """
4848 Merge N different HyperLogLogs into a single one.
4850 For more information see https://redis.io/commands/pfmerge
4851 """
4852 return self.execute_command("PFMERGE", dest, *sources)
4855AsyncHyperlogCommands = HyperlogCommands
4858class HashCommands(CommandsProtocol):
4859 """
4860 Redis commands for Hash data type.
4861 see: https://redis.io/topics/data-types-intro#redis-hashes
4862 """
4864 def hdel(self, name: str, *keys: List) -> Union[Awaitable[int], int]:
4865 """
4866 Delete ``keys`` from hash ``name``
4868 For more information see https://redis.io/commands/hdel
4869 """
4870 return self.execute_command("HDEL", name, *keys)
4872 def hexists(self, name: str, key: str) -> Union[Awaitable[bool], bool]:
4873 """
4874 Returns a boolean indicating if ``key`` exists within hash ``name``
4876 For more information see https://redis.io/commands/hexists
4877 """
4878 return self.execute_command("HEXISTS", name, key)
4880 def hget(
4881 self, name: str, key: str
4882 ) -> Union[Awaitable[Optional[str]], Optional[str]]:
4883 """
4884 Return the value of ``key`` within the hash ``name``
4886 For more information see https://redis.io/commands/hget
4887 """
4888 return self.execute_command("HGET", name, key)
4890 def hgetall(self, name: str) -> Union[Awaitable[dict], dict]:
4891 """
4892 Return a Python dict of the hash's name/value pairs
4894 For more information see https://redis.io/commands/hgetall
4895 """
4896 return self.execute_command("HGETALL", name)
4898 def hincrby(
4899 self, name: str, key: str, amount: int = 1
4900 ) -> Union[Awaitable[int], int]:
4901 """
4902 Increment the value of ``key`` in hash ``name`` by ``amount``
4904 For more information see https://redis.io/commands/hincrby
4905 """
4906 return self.execute_command("HINCRBY", name, key, amount)
4908 def hincrbyfloat(
4909 self, name: str, key: str, amount: float = 1.0
4910 ) -> Union[Awaitable[float], float]:
4911 """
4912 Increment the value of ``key`` in hash ``name`` by floating ``amount``
4914 For more information see https://redis.io/commands/hincrbyfloat
4915 """
4916 return self.execute_command("HINCRBYFLOAT", name, key, amount)
4918 def hkeys(self, name: str) -> Union[Awaitable[List], List]:
4919 """
4920 Return the list of keys within hash ``name``
4922 For more information see https://redis.io/commands/hkeys
4923 """
4924 return self.execute_command("HKEYS", name)
4926 def hlen(self, name: str) -> Union[Awaitable[int], int]:
4927 """
4928 Return the number of elements in hash ``name``
4930 For more information see https://redis.io/commands/hlen
4931 """
4932 return self.execute_command("HLEN", name)
4934 def hset(
4935 self,
4936 name: str,
4937 key: Optional[str] = None,
4938 value: Optional[str] = None,
4939 mapping: Optional[dict] = None,
4940 items: Optional[list] = None,
4941 ) -> Union[Awaitable[int], int]:
4942 """
4943 Set ``key`` to ``value`` within hash ``name``,
4944 ``mapping`` accepts a dict of key/value pairs that will be
4945 added to hash ``name``.
4946 ``items`` accepts a list of key/value pairs that will be
4947 added to hash ``name``.
4948 Returns the number of fields that were added.
4950 For more information see https://redis.io/commands/hset
4951 """
4952 if key is None and not mapping and not items:
4953 raise DataError("'hset' with no key value pairs")
4954 items = items or []
4955 if key is not None:
4956 items.extend((key, value))
4957 if mapping:
4958 for pair in mapping.items():
4959 items.extend(pair)
4961 return self.execute_command("HSET", name, *items)
4963 def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool]:
4964 """
4965 Set ``key`` to ``value`` within hash ``name`` if ``key`` does not
4966 exist. Returns 1 if HSETNX created a field, otherwise 0.
4968 For more information see https://redis.io/commands/hsetnx
4969 """
4970 return self.execute_command("HSETNX", name, key, value)
4972 def hmset(self, name: str, mapping: dict) -> Union[Awaitable[str], str]:
4973 """
4974 Set key to value within hash ``name`` for each corresponding
4975 key and value from the ``mapping`` dict.
4977 For more information see https://redis.io/commands/hmset
4978 """
4979 warnings.warn(
4980 f"{self.__class__.__name__}.hmset() is deprecated. "
4981 f"Use {self.__class__.__name__}.hset() instead.",
4982 DeprecationWarning,
4983 stacklevel=2,
4984 )
4985 if not mapping:
4986 raise DataError("'hmset' with 'mapping' of length 0")
4987 items = []
4988 for pair in mapping.items():
4989 items.extend(pair)
4990 return self.execute_command("HMSET", name, *items)
4992 def hmget(self, name: str, keys: List, *args: List) -> Union[Awaitable[List], List]:
4993 """
4994 Returns a list of values ordered identically to ``keys``
4996 For more information see https://redis.io/commands/hmget
4997 """
4998 args = list_or_args(keys, args)
4999 return self.execute_command("HMGET", name, *args)
5001 def hvals(self, name: str) -> Union[Awaitable[List], List]:
5002 """
5003 Return the list of values within hash ``name``
5005 For more information see https://redis.io/commands/hvals
5006 """
5007 return self.execute_command("HVALS", name)
5009 def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]:
5010 """
5011 Return the number of bytes stored in the value of ``key``
5012 within hash ``name``
5014 For more information see https://redis.io/commands/hstrlen
5015 """
5016 return self.execute_command("HSTRLEN", name, key)
5019AsyncHashCommands = HashCommands
5022class Script:
5023 """
5024 An executable Lua script object returned by ``register_script``
5025 """
5027 def __init__(self, registered_client: "Redis", script: ScriptTextT):
5028 self.registered_client = registered_client
5029 self.script = script
5030 # Precalculate and store the SHA1 hex digest of the script.
5032 if isinstance(script, str):
5033 # We need the encoding from the client in order to generate an
5034 # accurate byte representation of the script
5035 try:
5036 encoder = registered_client.connection_pool.get_encoder()
5037 except AttributeError:
5038 # Cluster
5039 encoder = registered_client.get_encoder()
5040 script = encoder.encode(script)
5041 self.sha = hashlib.sha1(script).hexdigest()
5043 def __call__(
5044 self,
5045 keys: Union[Sequence[KeyT], None] = None,
5046 args: Union[Iterable[EncodableT], None] = None,
5047 client: Union["Redis", None] = None,
5048 ):
5049 """Execute the script, passing any required ``args``"""
5050 keys = keys or []
5051 args = args or []
5052 if client is None:
5053 client = self.registered_client
5054 args = tuple(keys) + tuple(args)
5055 # make sure the Redis server knows about the script
5056 from redis.client import Pipeline
5058 if isinstance(client, Pipeline):
5059 # Make sure the pipeline can register the script before executing.
5060 client.scripts.add(self)
5061 try:
5062 return client.evalsha(self.sha, len(keys), *args)
5063 except NoScriptError:
5064 # Maybe the client is pointed to a different server than the client
5065 # that created this instance?
5066 # Overwrite the sha just in case there was a discrepancy.
5067 self.sha = client.script_load(self.script)
5068 return client.evalsha(self.sha, len(keys), *args)
5071class AsyncScript:
5072 """
5073 An executable Lua script object returned by ``register_script``
5074 """
5076 def __init__(self, registered_client: "AsyncRedis", script: ScriptTextT):
5077 self.registered_client = registered_client
5078 self.script = script
5079 # Precalculate and store the SHA1 hex digest of the script.
5081 if isinstance(script, str):
5082 # We need the encoding from the client in order to generate an
5083 # accurate byte representation of the script
5084 try:
5085 encoder = registered_client.connection_pool.get_encoder()
5086 except AttributeError:
5087 # Cluster
5088 encoder = registered_client.get_encoder()
5089 script = encoder.encode(script)
5090 self.sha = hashlib.sha1(script).hexdigest()
5092 async def __call__(
5093 self,
5094 keys: Union[Sequence[KeyT], None] = None,
5095 args: Union[Iterable[EncodableT], None] = None,
5096 client: Union["AsyncRedis", None] = None,
5097 ):
5098 """Execute the script, passing any required ``args``"""
5099 keys = keys or []
5100 args = args or []
5101 if client is None:
5102 client = self.registered_client
5103 args = tuple(keys) + tuple(args)
5104 # make sure the Redis server knows about the script
5105 from redis.asyncio.client import Pipeline
5107 if isinstance(client, Pipeline):
5108 # Make sure the pipeline can register the script before executing.
5109 client.scripts.add(self)
5110 try:
5111 return await client.evalsha(self.sha, len(keys), *args)
5112 except NoScriptError:
5113 # Maybe the client is pointed to a different server than the client
5114 # that created this instance?
5115 # Overwrite the sha just in case there was a discrepancy.
5116 self.sha = await client.script_load(self.script)
5117 return await client.evalsha(self.sha, len(keys), *args)
5120class PubSubCommands(CommandsProtocol):
5121 """
5122 Redis PubSub commands.
5123 see https://redis.io/topics/pubsub
5124 """
5126 def publish(self, channel: ChannelT, message: EncodableT, **kwargs) -> ResponseT:
5127 """
5128 Publish ``message`` on ``channel``.
5129 Returns the number of subscribers the message was delivered to.
5131 For more information see https://redis.io/commands/publish
5132 """
5133 return self.execute_command("PUBLISH", channel, message, **kwargs)
5135 def pubsub_channels(self, pattern: PatternT = "*", **kwargs) -> ResponseT:
5136 """
5137 Return a list of channels that have at least one subscriber
5139 For more information see https://redis.io/commands/pubsub-channels
5140 """
5141 return self.execute_command("PUBSUB CHANNELS", pattern, **kwargs)
5143 def pubsub_numpat(self, **kwargs) -> ResponseT:
5144 """
5145 Returns the number of subscriptions to patterns
5147 For more information see https://redis.io/commands/pubsub-numpat
5148 """
5149 return self.execute_command("PUBSUB NUMPAT", **kwargs)
5151 def pubsub_numsub(self, *args: ChannelT, **kwargs) -> ResponseT:
5152 """
5153 Return a list of (channel, number of subscribers) tuples
5154 for each channel given in ``*args``
5156 For more information see https://redis.io/commands/pubsub-numsub
5157 """
5158 return self.execute_command("PUBSUB NUMSUB", *args, **kwargs)
5161AsyncPubSubCommands = PubSubCommands
5164class ScriptCommands(CommandsProtocol):
5165 """
5166 Redis Lua script commands. see:
5167 https://redis.com/ebook/part-3-next-steps/chapter-11-scripting-redis-with-lua/
5168 """
5170 def _eval(
5171 self, command: str, script: str, numkeys: int, *keys_and_args: list
5172 ) -> Union[Awaitable[str], str]:
5173 return self.execute_command(command, script, numkeys, *keys_and_args)
5175 def eval(
5176 self, script: str, numkeys: int, *keys_and_args: list
5177 ) -> Union[Awaitable[str], str]:
5178 """
5179 Execute the Lua ``script``, specifying the ``numkeys`` the script
5180 will touch and the key names and argument values in ``keys_and_args``.
5181 Returns the result of the script.
5183 In practice, use the object returned by ``register_script``. This
5184 function exists purely for Redis API completion.
5186 For more information see https://redis.io/commands/eval
5187 """
5188 return self._eval("EVAL", script, numkeys, *keys_and_args)
5190 def eval_ro(
5191 self, script: str, numkeys: int, *keys_and_args: list
5192 ) -> Union[Awaitable[str], str]:
5193 """
5194 The read-only variant of the EVAL command
5196 Execute the read-only Lua ``script`` specifying the ``numkeys`` the script
5197 will touch and the key names and argument values in ``keys_and_args``.
5198 Returns the result of the script.
5200 For more information see https://redis.io/commands/eval_ro
5201 """
5202 return self._eval("EVAL_RO", script, numkeys, *keys_and_args)
5204 def _evalsha(
5205 self, command: str, sha: str, numkeys: int, *keys_and_args: list
5206 ) -> Union[Awaitable[str], str]:
5207 return self.execute_command(command, sha, numkeys, *keys_and_args)
5209 def evalsha(
5210 self, sha: str, numkeys: int, *keys_and_args: list
5211 ) -> Union[Awaitable[str], str]:
5212 """
5213 Use the ``sha`` to execute a Lua script already registered via EVAL
5214 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the
5215 key names and argument values in ``keys_and_args``. Returns the result
5216 of the script.
5218 In practice, use the object returned by ``register_script``. This
5219 function exists purely for Redis API completion.
5221 For more information see https://redis.io/commands/evalsha
5222 """
5223 return self._evalsha("EVALSHA", sha, numkeys, *keys_and_args)
5225 def evalsha_ro(
5226 self, sha: str, numkeys: int, *keys_and_args: list
5227 ) -> Union[Awaitable[str], str]:
5228 """
5229 The read-only variant of the EVALSHA command
5231 Use the ``sha`` to execute a read-only Lua script already registered via EVAL
5232 or SCRIPT LOAD. Specify the ``numkeys`` the script will touch and the
5233 key names and argument values in ``keys_and_args``. Returns the result
5234 of the script.
5236 For more information see https://redis.io/commands/evalsha_ro
5237 """
5238 return self._evalsha("EVALSHA_RO", sha, numkeys, *keys_and_args)
5240 def script_exists(self, *args: str) -> ResponseT:
5241 """
5242 Check if a script exists in the script cache by specifying the SHAs of
5243 each script as ``args``. Returns a list of boolean values indicating if
5244 if each already script exists in the cache.
5246 For more information see https://redis.io/commands/script-exists
5247 """
5248 return self.execute_command("SCRIPT EXISTS", *args)
5250 def script_debug(self, *args) -> None:
5251 raise NotImplementedError(
5252 "SCRIPT DEBUG is intentionally not implemented in the client."
5253 )
5255 def script_flush(
5256 self, sync_type: Union[Literal["SYNC"], Literal["ASYNC"]] = None
5257 ) -> ResponseT:
5258 """Flush all scripts from the script cache.
5259 ``sync_type`` is by default SYNC (synchronous) but it can also be
5260 ASYNC.
5261 For more information see https://redis.io/commands/script-flush
5262 """
5264 # Redis pre 6 had no sync_type.
5265 if sync_type not in ["SYNC", "ASYNC", None]:
5266 raise DataError(
5267 "SCRIPT FLUSH defaults to SYNC in redis > 6.2, or "
5268 "accepts SYNC/ASYNC. For older versions, "
5269 "of redis leave as None."
5270 )
5271 if sync_type is None:
5272 pieces = []
5273 else:
5274 pieces = [sync_type]
5275 return self.execute_command("SCRIPT FLUSH", *pieces)
5277 def script_kill(self) -> ResponseT:
5278 """
5279 Kill the currently executing Lua script
5281 For more information see https://redis.io/commands/script-kill
5282 """
5283 return self.execute_command("SCRIPT KILL")
5285 def script_load(self, script: ScriptTextT) -> ResponseT:
5286 """
5287 Load a Lua ``script`` into the script cache. Returns the SHA.
5289 For more information see https://redis.io/commands/script-load
5290 """
5291 return self.execute_command("SCRIPT LOAD", script)
5293 def register_script(self: "Redis", script: ScriptTextT) -> Script:
5294 """
5295 Register a Lua ``script`` specifying the ``keys`` it will touch.
5296 Returns a Script object that is callable and hides the complexity of
5297 deal with scripts, keys, and shas. This is the preferred way to work
5298 with Lua scripts.
5299 """
5300 return Script(self, script)
5303class AsyncScriptCommands(ScriptCommands):
5304 async def script_debug(self, *args) -> None:
5305 return super().script_debug()
5307 def register_script(self: "AsyncRedis", script: ScriptTextT) -> AsyncScript:
5308 """
5309 Register a Lua ``script`` specifying the ``keys`` it will touch.
5310 Returns a Script object that is callable and hides the complexity of
5311 deal with scripts, keys, and shas. This is the preferred way to work
5312 with Lua scripts.
5313 """
5314 return AsyncScript(self, script)
5317class GeoCommands(CommandsProtocol):
5318 """
5319 Redis Geospatial commands.
5320 see: https://redis.com/redis-best-practices/indexing-patterns/geospatial/
5321 """
5323 def geoadd(
5324 self,
5325 name: KeyT,
5326 values: Sequence[EncodableT],
5327 nx: bool = False,
5328 xx: bool = False,
5329 ch: bool = False,
5330 ) -> ResponseT:
5331 """
5332 Add the specified geospatial items to the specified key identified
5333 by the ``name`` argument. The Geospatial items are given as ordered
5334 members of the ``values`` argument, each item or place is formed by
5335 the triad longitude, latitude and name.
5337 Note: You can use ZREM to remove elements.
5339 ``nx`` forces ZADD to only create new elements and not to update
5340 scores for elements that already exist.
5342 ``xx`` forces ZADD to only update scores of elements that already
5343 exist. New elements will not be added.
5345 ``ch`` modifies the return value to be the numbers of elements changed.
5346 Changed elements include new elements that were added and elements
5347 whose scores changed.
5349 For more information see https://redis.io/commands/geoadd
5350 """
5351 if nx and xx:
5352 raise DataError("GEOADD allows either 'nx' or 'xx', not both")
5353 if len(values) % 3 != 0:
5354 raise DataError("GEOADD requires places with lon, lat and name values")
5355 pieces = [name]
5356 if nx:
5357 pieces.append("NX")
5358 if xx:
5359 pieces.append("XX")
5360 if ch:
5361 pieces.append("CH")
5362 pieces.extend(values)
5363 return self.execute_command("GEOADD", *pieces)
5365 def geodist(
5366 self, name: KeyT, place1: FieldT, place2: FieldT, unit: Union[str, None] = None
5367 ) -> ResponseT:
5368 """
5369 Return the distance between ``place1`` and ``place2`` members of the
5370 ``name`` key.
5371 The units must be one of the following : m, km mi, ft. By default
5372 meters are used.
5374 For more information see https://redis.io/commands/geodist
5375 """
5376 pieces: list[EncodableT] = [name, place1, place2]
5377 if unit and unit not in ("m", "km", "mi", "ft"):
5378 raise DataError("GEODIST invalid unit")
5379 elif unit:
5380 pieces.append(unit)
5381 return self.execute_command("GEODIST", *pieces)
5383 def geohash(self, name: KeyT, *values: FieldT) -> ResponseT:
5384 """
5385 Return the geo hash string for each item of ``values`` members of
5386 the specified key identified by the ``name`` argument.
5388 For more information see https://redis.io/commands/geohash
5389 """
5390 return self.execute_command("GEOHASH", name, *values)
5392 def geopos(self, name: KeyT, *values: FieldT) -> ResponseT:
5393 """
5394 Return the positions of each item of ``values`` as members of
5395 the specified key identified by the ``name`` argument. Each position
5396 is represented by the pairs lon and lat.
5398 For more information see https://redis.io/commands/geopos
5399 """
5400 return self.execute_command("GEOPOS", name, *values)
5402 def georadius(
5403 self,
5404 name: KeyT,
5405 longitude: float,
5406 latitude: float,
5407 radius: float,
5408 unit: Union[str, None] = None,
5409 withdist: bool = False,
5410 withcoord: bool = False,
5411 withhash: bool = False,
5412 count: Union[int, None] = None,
5413 sort: Union[str, None] = None,
5414 store: Union[KeyT, None] = None,
5415 store_dist: Union[KeyT, None] = None,
5416 any: bool = False,
5417 ) -> ResponseT:
5418 """
5419 Return the members of the specified key identified by the
5420 ``name`` argument which are within the borders of the area specified
5421 with the ``latitude`` and ``longitude`` location and the maximum
5422 distance from the center specified by the ``radius`` value.
5424 The units must be one of the following : m, km mi, ft. By default
5426 ``withdist`` indicates to return the distances of each place.
5428 ``withcoord`` indicates to return the latitude and longitude of
5429 each place.
5431 ``withhash`` indicates to return the geohash string of each place.
5433 ``count`` indicates to return the number of elements up to N.
5435 ``sort`` indicates to return the places in a sorted way, ASC for
5436 nearest to fairest and DESC for fairest to nearest.
5438 ``store`` indicates to save the places names in a sorted set named
5439 with a specific key, each element of the destination sorted set is
5440 populated with the score got from the original geo sorted set.
5442 ``store_dist`` indicates to save the places names in a sorted set
5443 named with a specific key, instead of ``store`` the sorted set
5444 destination score is set with the distance.
5446 For more information see https://redis.io/commands/georadius
5447 """
5448 return self._georadiusgeneric(
5449 "GEORADIUS",
5450 name,
5451 longitude,
5452 latitude,
5453 radius,
5454 unit=unit,
5455 withdist=withdist,
5456 withcoord=withcoord,
5457 withhash=withhash,
5458 count=count,
5459 sort=sort,
5460 store=store,
5461 store_dist=store_dist,
5462 any=any,
5463 )
5465 def georadiusbymember(
5466 self,
5467 name: KeyT,
5468 member: FieldT,
5469 radius: float,
5470 unit: Union[str, None] = None,
5471 withdist: bool = False,
5472 withcoord: bool = False,
5473 withhash: bool = False,
5474 count: Union[int, None] = None,
5475 sort: Union[str, None] = None,
5476 store: Union[KeyT, None] = None,
5477 store_dist: Union[KeyT, None] = None,
5478 any: bool = False,
5479 ) -> ResponseT:
5480 """
5481 This command is exactly like ``georadius`` with the sole difference
5482 that instead of taking, as the center of the area to query, a longitude
5483 and latitude value, it takes the name of a member already existing
5484 inside the geospatial index represented by the sorted set.
5486 For more information see https://redis.io/commands/georadiusbymember
5487 """
5488 return self._georadiusgeneric(
5489 "GEORADIUSBYMEMBER",
5490 name,
5491 member,
5492 radius,
5493 unit=unit,
5494 withdist=withdist,
5495 withcoord=withcoord,
5496 withhash=withhash,
5497 count=count,
5498 sort=sort,
5499 store=store,
5500 store_dist=store_dist,
5501 any=any,
5502 )
5504 def _georadiusgeneric(
5505 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None]
5506 ) -> ResponseT:
5507 pieces = list(args)
5508 if kwargs["unit"] and kwargs["unit"] not in ("m", "km", "mi", "ft"):
5509 raise DataError("GEORADIUS invalid unit")
5510 elif kwargs["unit"]:
5511 pieces.append(kwargs["unit"])
5512 else:
5513 pieces.append("m")
5515 if kwargs["any"] and kwargs["count"] is None:
5516 raise DataError("``any`` can't be provided without ``count``")
5518 for arg_name, byte_repr in (
5519 ("withdist", "WITHDIST"),
5520 ("withcoord", "WITHCOORD"),
5521 ("withhash", "WITHHASH"),
5522 ):
5523 if kwargs[arg_name]:
5524 pieces.append(byte_repr)
5526 if kwargs["count"] is not None:
5527 pieces.extend(["COUNT", kwargs["count"]])
5528 if kwargs["any"]:
5529 pieces.append("ANY")
5531 if kwargs["sort"]:
5532 if kwargs["sort"] == "ASC":
5533 pieces.append("ASC")
5534 elif kwargs["sort"] == "DESC":
5535 pieces.append("DESC")
5536 else:
5537 raise DataError("GEORADIUS invalid sort")
5539 if kwargs["store"] and kwargs["store_dist"]:
5540 raise DataError("GEORADIUS store and store_dist cant be set together")
5542 if kwargs["store"]:
5543 pieces.extend([b"STORE", kwargs["store"]])
5545 if kwargs["store_dist"]:
5546 pieces.extend([b"STOREDIST", kwargs["store_dist"]])
5548 return self.execute_command(command, *pieces, **kwargs)
5550 def geosearch(
5551 self,
5552 name: KeyT,
5553 member: Union[FieldT, None] = None,
5554 longitude: Union[float, None] = None,
5555 latitude: Union[float, None] = None,
5556 unit: str = "m",
5557 radius: Union[float, None] = None,
5558 width: Union[float, None] = None,
5559 height: Union[float, None] = None,
5560 sort: Union[str, None] = None,
5561 count: Union[int, None] = None,
5562 any: bool = False,
5563 withcoord: bool = False,
5564 withdist: bool = False,
5565 withhash: bool = False,
5566 ) -> ResponseT:
5567 """
5568 Return the members of specified key identified by the
5569 ``name`` argument, which are within the borders of the
5570 area specified by a given shape. This command extends the
5571 GEORADIUS command, so in addition to searching within circular
5572 areas, it supports searching within rectangular areas.
5573 This command should be used in place of the deprecated
5574 GEORADIUS and GEORADIUSBYMEMBER commands.
5575 ``member`` Use the position of the given existing
5576 member in the sorted set. Can't be given with ``longitude``
5577 and ``latitude``.
5578 ``longitude`` and ``latitude`` Use the position given by
5579 this coordinates. Can't be given with ``member``
5580 ``radius`` Similar to GEORADIUS, search inside circular
5581 area according the given radius. Can't be given with
5582 ``height`` and ``width``.
5583 ``height`` and ``width`` Search inside an axis-aligned
5584 rectangle, determined by the given height and width.
5585 Can't be given with ``radius``
5586 ``unit`` must be one of the following : m, km, mi, ft.
5587 `m` for meters (the default value), `km` for kilometers,
5588 `mi` for miles and `ft` for feet.
5589 ``sort`` indicates to return the places in a sorted way,
5590 ASC for nearest to furthest and DESC for furthest to nearest.
5591 ``count`` limit the results to the first count matching items.
5592 ``any`` is set to True, the command will return as soon as
5593 enough matches are found. Can't be provided without ``count``
5594 ``withdist`` indicates to return the distances of each place.
5595 ``withcoord`` indicates to return the latitude and longitude of
5596 each place.
5597 ``withhash`` indicates to return the geohash string of each place.
5599 For more information see https://redis.io/commands/geosearch
5600 """
5602 return self._geosearchgeneric(
5603 "GEOSEARCH",
5604 name,
5605 member=member,
5606 longitude=longitude,
5607 latitude=latitude,
5608 unit=unit,
5609 radius=radius,
5610 width=width,
5611 height=height,
5612 sort=sort,
5613 count=count,
5614 any=any,
5615 withcoord=withcoord,
5616 withdist=withdist,
5617 withhash=withhash,
5618 store=None,
5619 store_dist=None,
5620 )
5622 def geosearchstore(
5623 self,
5624 dest: KeyT,
5625 name: KeyT,
5626 member: Union[FieldT, None] = None,
5627 longitude: Union[float, None] = None,
5628 latitude: Union[float, None] = None,
5629 unit: str = "m",
5630 radius: Union[float, None] = None,
5631 width: Union[float, None] = None,
5632 height: Union[float, None] = None,
5633 sort: Union[str, None] = None,
5634 count: Union[int, None] = None,
5635 any: bool = False,
5636 storedist: bool = False,
5637 ) -> ResponseT:
5638 """
5639 This command is like GEOSEARCH, but stores the result in
5640 ``dest``. By default, it stores the results in the destination
5641 sorted set with their geospatial information.
5642 if ``store_dist`` set to True, the command will stores the
5643 items in a sorted set populated with their distance from the
5644 center of the circle or box, as a floating-point number.
5646 For more information see https://redis.io/commands/geosearchstore
5647 """
5648 return self._geosearchgeneric(
5649 "GEOSEARCHSTORE",
5650 dest,
5651 name,
5652 member=member,
5653 longitude=longitude,
5654 latitude=latitude,
5655 unit=unit,
5656 radius=radius,
5657 width=width,
5658 height=height,
5659 sort=sort,
5660 count=count,
5661 any=any,
5662 withcoord=None,
5663 withdist=None,
5664 withhash=None,
5665 store=None,
5666 store_dist=storedist,
5667 )
5669 def _geosearchgeneric(
5670 self, command: str, *args: EncodableT, **kwargs: Union[EncodableT, None]
5671 ) -> ResponseT:
5672 pieces = list(args)
5674 # FROMMEMBER or FROMLONLAT
5675 if kwargs["member"] is None:
5676 if kwargs["longitude"] is None or kwargs["latitude"] is None:
5677 raise DataError("GEOSEARCH must have member or longitude and latitude")
5678 if kwargs["member"]:
5679 if kwargs["longitude"] or kwargs["latitude"]:
5680 raise DataError(
5681 "GEOSEARCH member and longitude or latitude cant be set together"
5682 )
5683 pieces.extend([b"FROMMEMBER", kwargs["member"]])
5684 if kwargs["longitude"] is not None and kwargs["latitude"] is not None:
5685 pieces.extend([b"FROMLONLAT", kwargs["longitude"], kwargs["latitude"]])
5687 # BYRADIUS or BYBOX
5688 if kwargs["radius"] is None:
5689 if kwargs["width"] is None or kwargs["height"] is None:
5690 raise DataError("GEOSEARCH must have radius or width and height")
5691 if kwargs["unit"] is None:
5692 raise DataError("GEOSEARCH must have unit")
5693 if kwargs["unit"].lower() not in ("m", "km", "mi", "ft"):
5694 raise DataError("GEOSEARCH invalid unit")
5695 if kwargs["radius"]:
5696 if kwargs["width"] or kwargs["height"]:
5697 raise DataError(
5698 "GEOSEARCH radius and width or height cant be set together"
5699 )
5700 pieces.extend([b"BYRADIUS", kwargs["radius"], kwargs["unit"]])
5701 if kwargs["width"] and kwargs["height"]:
5702 pieces.extend([b"BYBOX", kwargs["width"], kwargs["height"], kwargs["unit"]])
5704 # sort
5705 if kwargs["sort"]:
5706 if kwargs["sort"].upper() == "ASC":
5707 pieces.append(b"ASC")
5708 elif kwargs["sort"].upper() == "DESC":
5709 pieces.append(b"DESC")
5710 else:
5711 raise DataError("GEOSEARCH invalid sort")
5713 # count any
5714 if kwargs["count"]:
5715 pieces.extend([b"COUNT", kwargs["count"]])
5716 if kwargs["any"]:
5717 pieces.append(b"ANY")
5718 elif kwargs["any"]:
5719 raise DataError("GEOSEARCH ``any`` can't be provided without count")
5721 # other properties
5722 for arg_name, byte_repr in (
5723 ("withdist", b"WITHDIST"),
5724 ("withcoord", b"WITHCOORD"),
5725 ("withhash", b"WITHHASH"),
5726 ("store_dist", b"STOREDIST"),
5727 ):
5728 if kwargs[arg_name]:
5729 pieces.append(byte_repr)
5731 return self.execute_command(command, *pieces, **kwargs)
5734AsyncGeoCommands = GeoCommands
5737class ModuleCommands(CommandsProtocol):
5738 """
5739 Redis Module commands.
5740 see: https://redis.io/topics/modules-intro
5741 """
5743 def module_load(self, path, *args) -> ResponseT:
5744 """
5745 Loads the module from ``path``.
5746 Passes all ``*args`` to the module, during loading.
5747 Raises ``ModuleError`` if a module is not found at ``path``.
5749 For more information see https://redis.io/commands/module-load
5750 """
5751 return self.execute_command("MODULE LOAD", path, *args)
5753 def module_loadex(
5754 self,
5755 path: str,
5756 options: Optional[List[str]] = None,
5757 args: Optional[List[str]] = None,
5758 ) -> ResponseT:
5759 """
5760 Loads a module from a dynamic library at runtime with configuration directives.
5762 For more information see https://redis.io/commands/module-loadex
5763 """
5764 pieces = []
5765 if options is not None:
5766 pieces.append("CONFIG")
5767 pieces.extend(options)
5768 if args is not None:
5769 pieces.append("ARGS")
5770 pieces.extend(args)
5772 return self.execute_command("MODULE LOADEX", path, *pieces)
5774 def module_unload(self, name) -> ResponseT:
5775 """
5776 Unloads the module ``name``.
5777 Raises ``ModuleError`` if ``name`` is not in loaded modules.
5779 For more information see https://redis.io/commands/module-unload
5780 """
5781 return self.execute_command("MODULE UNLOAD", name)
5783 def module_list(self) -> ResponseT:
5784 """
5785 Returns a list of dictionaries containing the name and version of
5786 all loaded modules.
5788 For more information see https://redis.io/commands/module-list
5789 """
5790 return self.execute_command("MODULE LIST")
5792 def command_info(self) -> None:
5793 raise NotImplementedError(
5794 "COMMAND INFO is intentionally not implemented in the client."
5795 )
5797 def command_count(self) -> ResponseT:
5798 return self.execute_command("COMMAND COUNT")
5800 def command_getkeys(self, *args) -> ResponseT:
5801 return self.execute_command("COMMAND GETKEYS", *args)
5803 def command(self) -> ResponseT:
5804 return self.execute_command("COMMAND")
5807class Script:
5808 """
5809 An executable Lua script object returned by ``register_script``
5810 """
5812 def __init__(self, registered_client, script):
5813 self.registered_client = registered_client
5814 self.script = script
5815 # Precalculate and store the SHA1 hex digest of the script.
5817 if isinstance(script, str):
5818 # We need the encoding from the client in order to generate an
5819 # accurate byte representation of the script
5820 encoder = self.get_encoder()
5821 script = encoder.encode(script)
5822 self.sha = hashlib.sha1(script).hexdigest()
5824 def __call__(self, keys=[], args=[], client=None):
5825 "Execute the script, passing any required ``args``"
5826 if client is None:
5827 client = self.registered_client
5828 args = tuple(keys) + tuple(args)
5829 # make sure the Redis server knows about the script
5830 from redis.client import Pipeline
5832 if isinstance(client, Pipeline):
5833 # Make sure the pipeline can register the script before executing.
5834 client.scripts.add(self)
5835 try:
5836 return client.evalsha(self.sha, len(keys), *args)
5837 except NoScriptError:
5838 # Maybe the client is pointed to a different server than the client
5839 # that created this instance?
5840 # Overwrite the sha just in case there was a discrepancy.
5841 self.sha = client.script_load(self.script)
5842 return client.evalsha(self.sha, len(keys), *args)
5844 def get_encoder(self):
5845 """Get the encoder to encode string scripts into bytes."""
5846 try:
5847 return self.registered_client.get_encoder()
5848 except AttributeError:
5849 # DEPRECATED
5850 # In version <=4.1.2, this was the code we used to get the encoder.
5851 # However, after 4.1.2 we added support for scripting in clustered
5852 # redis. ClusteredRedis doesn't have a `.connection_pool` attribute
5853 # so we changed the Script class to use
5854 # `self.registered_client.get_encoder` (see above).
5855 # However, that is technically a breaking change, as consumers who
5856 # use Scripts directly might inject a `registered_client` that
5857 # doesn't have a `.get_encoder` field. This try/except prevents us
5858 # from breaking backward-compatibility. Ideally, it would be
5859 # removed in the next major release.
5860 return self.registered_client.connection_pool.get_encoder()
5863class AsyncModuleCommands(ModuleCommands):
5864 async def command_info(self) -> None:
5865 return super().command_info()
5868class ClusterCommands(CommandsProtocol):
5869 """
5870 Class for Redis Cluster commands
5871 """
5873 def cluster(self, cluster_arg, *args, **kwargs) -> ResponseT:
5874 return self.execute_command(f"CLUSTER {cluster_arg.upper()}", *args, **kwargs)
5876 def readwrite(self, **kwargs) -> ResponseT:
5877 """
5878 Disables read queries for a connection to a Redis Cluster slave node.
5880 For more information see https://redis.io/commands/readwrite
5881 """
5882 return self.execute_command("READWRITE", **kwargs)
5884 def readonly(self, **kwargs) -> ResponseT:
5885 """
5886 Enables read queries for a connection to a Redis Cluster replica node.
5888 For more information see https://redis.io/commands/readonly
5889 """
5890 return self.execute_command("READONLY", **kwargs)
5893AsyncClusterCommands = ClusterCommands
5896class FunctionCommands:
5897 """
5898 Redis Function commands
5899 """
5901 def function_load(
5902 self, code: str, replace: Optional[bool] = False
5903 ) -> Union[Awaitable[str], str]:
5904 """
5905 Load a library to Redis.
5906 :param code: the source code (must start with
5907 Shebang statement that provides a metadata about the library)
5908 :param replace: changes the behavior to overwrite the existing library
5909 with the new contents.
5910 Return the library name that was loaded.
5912 For more information see https://redis.io/commands/function-load
5913 """
5914 pieces = ["REPLACE"] if replace else []
5915 pieces.append(code)
5916 return self.execute_command("FUNCTION LOAD", *pieces)
5918 def function_delete(self, library: str) -> Union[Awaitable[str], str]:
5919 """
5920 Delete the library called ``library`` and all its functions.
5922 For more information see https://redis.io/commands/function-delete
5923 """
5924 return self.execute_command("FUNCTION DELETE", library)
5926 def function_flush(self, mode: str = "SYNC") -> Union[Awaitable[str], str]:
5927 """
5928 Deletes all the libraries.
5930 For more information see https://redis.io/commands/function-flush
5931 """
5932 return self.execute_command("FUNCTION FLUSH", mode)
5934 def function_list(
5935 self, library: Optional[str] = "*", withcode: Optional[bool] = False
5936 ) -> Union[Awaitable[List], List]:
5937 """
5938 Return information about the functions and libraries.
5939 :param library: pecify a pattern for matching library names
5940 :param withcode: cause the server to include the libraries source
5941 implementation in the reply
5942 """
5943 args = ["LIBRARYNAME", library]
5944 if withcode:
5945 args.append("WITHCODE")
5946 return self.execute_command("FUNCTION LIST", *args)
5948 def _fcall(
5949 self, command: str, function, numkeys: int, *keys_and_args: Optional[List]
5950 ) -> Union[Awaitable[str], str]:
5951 return self.execute_command(command, function, numkeys, *keys_and_args)
5953 def fcall(
5954 self, function, numkeys: int, *keys_and_args: Optional[List]
5955 ) -> Union[Awaitable[str], str]:
5956 """
5957 Invoke a function.
5959 For more information see https://redis.io/commands/fcall
5960 """
5961 return self._fcall("FCALL", function, numkeys, *keys_and_args)
5963 def fcall_ro(
5964 self, function, numkeys: int, *keys_and_args: Optional[List]
5965 ) -> Union[Awaitable[str], str]:
5966 """
5967 This is a read-only variant of the FCALL command that cannot
5968 execute commands that modify data.
5970 For more information see https://redis.io/commands/fcal_ro
5971 """
5972 return self._fcall("FCALL_RO", function, numkeys, *keys_and_args)
5974 def function_dump(self) -> Union[Awaitable[str], str]:
5975 """
5976 Return the serialized payload of loaded libraries.
5978 For more information see https://redis.io/commands/function-dump
5979 """
5980 from redis.client import NEVER_DECODE
5982 options = {}
5983 options[NEVER_DECODE] = []
5985 return self.execute_command("FUNCTION DUMP", **options)
5987 def function_restore(
5988 self, payload: str, policy: Optional[str] = "APPEND"
5989 ) -> Union[Awaitable[str], str]:
5990 """
5991 Restore libraries from the serialized ``payload``.
5992 You can use the optional policy argument to provide a policy
5993 for handling existing libraries.
5995 For more information see https://redis.io/commands/function-restore
5996 """
5997 return self.execute_command("FUNCTION RESTORE", payload, policy)
5999 def function_kill(self) -> Union[Awaitable[str], str]:
6000 """
6001 Kill a function that is currently executing.
6003 For more information see https://redis.io/commands/function-kill
6004 """
6005 return self.execute_command("FUNCTION KILL")
6007 def function_stats(self) -> Union[Awaitable[List], List]:
6008 """
6009 Return information about the function that's currently running
6010 and information about the available execution engines.
6012 For more information see https://redis.io/commands/function-stats
6013 """
6014 return self.execute_command("FUNCTION STATS")
6017AsyncFunctionCommands = FunctionCommands
6020class DataAccessCommands(
6021 BasicKeyCommands,
6022 HyperlogCommands,
6023 HashCommands,
6024 GeoCommands,
6025 ListCommands,
6026 ScanCommands,
6027 SetCommands,
6028 StreamCommands,
6029 SortedSetCommands,
6030):
6031 """
6032 A class containing all of the implemented data access redis commands.
6033 This class is to be used as a mixin for synchronous Redis clients.
6034 """
6037class AsyncDataAccessCommands(
6038 AsyncBasicKeyCommands,
6039 AsyncHyperlogCommands,
6040 AsyncHashCommands,
6041 AsyncGeoCommands,
6042 AsyncListCommands,
6043 AsyncScanCommands,
6044 AsyncSetCommands,
6045 AsyncStreamCommands,
6046 AsyncSortedSetCommands,
6047):
6048 """
6049 A class containing all of the implemented data access redis commands.
6050 This class is to be used as a mixin for asynchronous Redis clients.
6051 """
6054class CoreCommands(
6055 ACLCommands,
6056 ClusterCommands,
6057 DataAccessCommands,
6058 ManagementCommands,
6059 ModuleCommands,
6060 PubSubCommands,
6061 ScriptCommands,
6062 FunctionCommands,
6063):
6064 """
6065 A class containing all of the implemented redis commands. This class is
6066 to be used as a mixin for synchronous Redis clients.
6067 """
6070class AsyncCoreCommands(
6071 AsyncACLCommands,
6072 AsyncClusterCommands,
6073 AsyncDataAccessCommands,
6074 AsyncManagementCommands,
6075 AsyncModuleCommands,
6076 AsyncPubSubCommands,
6077 AsyncScriptCommands,
6078 AsyncFunctionCommands,
6079):
6080 """
6081 A class containing all of the implemented redis commands. This class is
6082 to be used as a mixin for asynchronous Redis clients.
6083 """