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