@@ -1559,6 +1559,16 @@ def execute(self) -> ResponseT:
15591559 return self .client .execute_command (* command )
15601560
15611561
1562+ class DataPersistOptions (Enum ):
1563+ # set the value for each provided key to each
1564+ # provided value only if all do not already exist.
1565+ NX = "NX"
1566+
1567+ # set the value for each provided key to each
1568+ # provided value only if all already exist.
1569+ XX = "XX"
1570+
1571+
15621572class BasicKeyCommands (CommandsProtocol ):
15631573 """
15641574 Redis basic key-based commands
@@ -1987,6 +1997,10 @@ def mget(self, keys: KeysT, *args: EncodableT) -> ResponseT:
19871997 """
19881998 Returns a list of values ordered identically to ``keys``
19891999
2000+ ** Important ** When this method is used with Cluster clients, all keys
2001+ must be in the same hash slot, otherwise a RedisClusterException
2002+ will be raised.
2003+
19902004 For more information, see https://redis.io/commands/mget
19912005 """
19922006 from redis .client import EMPTY_RESPONSE
@@ -2004,20 +2018,94 @@ def mset(self, mapping: Mapping[AnyKeyT, EncodableT]) -> ResponseT:
20042018 key/value pairs. Both keys and values should be strings or types that
20052019 can be cast to a string via str().
20062020
2021+ ** Important ** When this method is used with Cluster clients, all keys
2022+ must be in the same hash slot, otherwise a RedisClusterException
2023+ will be raised.
2024+
20072025 For more information, see https://redis.io/commands/mset
20082026 """
20092027 items = []
20102028 for pair in mapping .items ():
20112029 items .extend (pair )
20122030 return self .execute_command ("MSET" , * items )
20132031
2032+ def msetex (
2033+ self ,
2034+ mapping : Mapping [AnyKeyT , EncodableT ],
2035+ data_persist_option : Optional [DataPersistOptions ] = None ,
2036+ ex : Optional [ExpiryT ] = None ,
2037+ px : Optional [ExpiryT ] = None ,
2038+ exat : Optional [AbsExpiryT ] = None ,
2039+ pxat : Optional [AbsExpiryT ] = None ,
2040+ keepttl : bool = False ,
2041+ ) -> Union [Awaitable [int ], int ]:
2042+ """
2043+ Sets key/values based on the provided ``mapping`` items.
2044+
2045+ ** Important ** When this method is used with Cluster clients, all keys
2046+ must be in the same hash slot, otherwise a RedisClusterException
2047+ will be raised.
2048+
2049+ ``mapping`` accepts a dict of key/value pairs that will be added to the database.
2050+
2051+ ``data_persist_option`` can be set to ``NX`` or ``XX`` to control the
2052+ behavior of the command.
2053+ ``NX`` will set the value for each provided key to each
2054+ provided value only if all do not already exist.
2055+ ``XX`` will set the value for each provided key to each
2056+ provided value only if all already exist.
2057+
2058+ ``ex`` sets an expire flag on the keys in ``mapping`` for ``ex`` seconds.
2059+
2060+ ``px`` sets an expire flag on the keys in ``mapping`` for ``px`` milliseconds.
2061+
2062+ ``exat`` sets an expire flag on the keys in ``mapping`` for ``exat`` seconds,
2063+ specified in unix time.
2064+
2065+ ``pxat`` sets an expire flag on the keys in ``mapping`` for ``pxat`` milliseconds,
2066+ specified in unix time.
2067+
2068+ ``keepttl`` if True, retain the time to live associated with the keys.
2069+
2070+ Returns the number of fields that were added.
2071+
2072+ Available since Redis 8.4
2073+ For more information, see https://redis.io/commands/msetex
2074+ """
2075+ opset = {ex , px , exat , pxat }
2076+ if len (opset ) > 2 or len (opset ) > 1 and keepttl :
2077+ raise DataError (
2078+ "``ex``, ``px``, ``exat``, ``pxat``, "
2079+ "and ``keepttl`` are mutually exclusive."
2080+ )
2081+
2082+ exp_options : list [EncodableT ] = []
2083+ if data_persist_option :
2084+ exp_options .append (data_persist_option .value )
2085+
2086+ exp_options .extend (extract_expire_flags (ex , px , exat , pxat ))
2087+
2088+ if keepttl :
2089+ exp_options .append ("KEEPTTL" )
2090+
2091+ pieces = ["MSETEX" , len (mapping )]
2092+
2093+ for pair in mapping .items ():
2094+ pieces .extend (pair )
2095+
2096+ return self .execute_command (* pieces , * exp_options )
2097+
20142098 def msetnx (self , mapping : Mapping [AnyKeyT , EncodableT ]) -> ResponseT :
20152099 """
20162100 Sets key/values based on a mapping if none of the keys are already set.
20172101 Mapping is a dictionary of key/value pairs. Both keys and values
20182102 should be strings or types that can be cast to a string via str().
20192103 Returns a boolean indicating if the operation was successful.
20202104
2105+ ** Important ** When this method is used with Cluster clients, all keys
2106+ must be in the same hash slot, otherwise a RedisClusterException
2107+ will be raised.
2108+
20212109 For more information, see https://redis.io/commands/msetnx
20222110 """
20232111 items = []
@@ -4013,6 +4101,7 @@ def xreadgroup(
40134101 count : Optional [int ] = None ,
40144102 block : Optional [int ] = None ,
40154103 noack : bool = False ,
4104+ claim_min_idle_time : Optional [int ] = None ,
40164105 ) -> ResponseT :
40174106 """
40184107 Read from a stream via a consumer group.
@@ -4030,8 +4119,12 @@ def xreadgroup(
40304119 block: number of milliseconds to wait, if nothing already present.
40314120 noack: do not add messages to the PEL
40324121
4122+ claim_min_idle_time: accepts an integer type and represents a
4123+ time interval in milliseconds
4124+
40334125 For more information, see https://redis.io/commands/xreadgroup
40344126 """
4127+ options = {}
40354128 pieces : list [EncodableT ] = [b"GROUP" , groupname , consumername ]
40364129 if count is not None :
40374130 if not isinstance (count , int ) or count < 1 :
@@ -4045,12 +4138,20 @@ def xreadgroup(
40454138 pieces .append (str (block ))
40464139 if noack :
40474140 pieces .append (b"NOACK" )
4141+ if claim_min_idle_time is not None :
4142+ if not isinstance (claim_min_idle_time , int ) or claim_min_idle_time < 0 :
4143+ raise DataError (
4144+ "XREADGROUP claim_min_idle_time must be a non-negative integer"
4145+ )
4146+ pieces .append (b"CLAIM" )
4147+ pieces .append (claim_min_idle_time )
4148+ options ["claim_min_idle_time" ] = claim_min_idle_time
40484149 if not isinstance (streams , dict ) or len (streams ) == 0 :
40494150 raise DataError ("XREADGROUP streams must be a non empty dict" )
40504151 pieces .append (b"STREAMS" )
40514152 pieces .extend (streams .keys ())
40524153 pieces .extend (streams .values ())
4053- return self .execute_command ("XREADGROUP" , * pieces )
4154+ return self .execute_command ("XREADGROUP" , * pieces , ** options )
40544155
40554156 def xrevrange (
40564157 self ,
0 commit comments