33import datetime
44import hashlib
55import warnings
6+ from enum import Enum
67from typing import (
78 TYPE_CHECKING ,
89 Any ,
4445 TimeoutSecT ,
4546 ZScoreBoundT ,
4647)
48+ from redis .utils import (
49+ deprecated_function ,
50+ extract_expire_flags ,
51+ )
4752
4853from .helpers import list_or_args
4954
@@ -1837,10 +1842,10 @@ def getdel(self, name: KeyT) -> ResponseT:
18371842 def getex (
18381843 self ,
18391844 name : KeyT ,
1840- ex : Union [ExpiryT , None ] = None ,
1841- px : Union [ExpiryT , None ] = None ,
1842- exat : Union [AbsExpiryT , None ] = None ,
1843- pxat : Union [AbsExpiryT , None ] = None ,
1845+ ex : Optional [ExpiryT ] = None ,
1846+ px : Optional [ExpiryT ] = None ,
1847+ exat : Optional [AbsExpiryT ] = None ,
1848+ pxat : Optional [AbsExpiryT ] = None ,
18441849 persist : bool = False ,
18451850 ) -> ResponseT :
18461851 """
@@ -1863,41 +1868,19 @@ def getex(
18631868
18641869 For more information see https://redis.io/commands/getex
18651870 """
1866-
18671871 opset = {ex , px , exat , pxat }
18681872 if len (opset ) > 2 or len (opset ) > 1 and persist :
18691873 raise DataError (
18701874 "``ex``, ``px``, ``exat``, ``pxat``, "
18711875 "and ``persist`` are mutually exclusive."
18721876 )
18731877
1874- pieces : list [EncodableT ] = []
1875- # similar to set command
1876- if ex is not None :
1877- pieces .append ("EX" )
1878- if isinstance (ex , datetime .timedelta ):
1879- ex = int (ex .total_seconds ())
1880- pieces .append (ex )
1881- if px is not None :
1882- pieces .append ("PX" )
1883- if isinstance (px , datetime .timedelta ):
1884- px = int (px .total_seconds () * 1000 )
1885- pieces .append (px )
1886- # similar to pexpireat command
1887- if exat is not None :
1888- pieces .append ("EXAT" )
1889- if isinstance (exat , datetime .datetime ):
1890- exat = int (exat .timestamp ())
1891- pieces .append (exat )
1892- if pxat is not None :
1893- pieces .append ("PXAT" )
1894- if isinstance (pxat , datetime .datetime ):
1895- pxat = int (pxat .timestamp () * 1000 )
1896- pieces .append (pxat )
1878+ exp_options : list [EncodableT ] = extract_expire_flags (ex , px , exat , pxat )
1879+
18971880 if persist :
1898- pieces .append ("PERSIST" )
1881+ exp_options .append ("PERSIST" )
18991882
1900- return self .execute_command ("GETEX" , name , * pieces )
1883+ return self .execute_command ("GETEX" , name , * exp_options )
19011884
19021885 def __getitem__ (self , name : KeyT ):
19031886 """
@@ -2255,14 +2238,14 @@ def set(
22552238 self ,
22562239 name : KeyT ,
22572240 value : EncodableT ,
2258- ex : Union [ExpiryT , None ] = None ,
2259- px : Union [ExpiryT , None ] = None ,
2241+ ex : Optional [ExpiryT ] = None ,
2242+ px : Optional [ExpiryT ] = None ,
22602243 nx : bool = False ,
22612244 xx : bool = False ,
22622245 keepttl : bool = False ,
22632246 get : bool = False ,
2264- exat : Union [AbsExpiryT , None ] = None ,
2265- pxat : Union [AbsExpiryT , None ] = None ,
2247+ exat : Optional [AbsExpiryT ] = None ,
2248+ pxat : Optional [AbsExpiryT ] = None ,
22662249 ) -> ResponseT :
22672250 """
22682251 Set the value at key ``name`` to ``value``
@@ -2292,36 +2275,21 @@ def set(
22922275
22932276 For more information see https://redis.io/commands/set
22942277 """
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+
22952288 pieces : list [EncodableT ] = [name , value ]
22962289 options = {}
2297- if ex is not None :
2298- pieces .append ("EX" )
2299- if isinstance (ex , datetime .timedelta ):
2300- pieces .append (int (ex .total_seconds ()))
2301- elif isinstance (ex , int ):
2302- pieces .append (ex )
2303- elif isinstance (ex , str ) and ex .isdigit ():
2304- pieces .append (int (ex ))
2305- else :
2306- raise DataError ("ex must be datetime.timedelta or int" )
2307- if px is not None :
2308- pieces .append ("PX" )
2309- if isinstance (px , datetime .timedelta ):
2310- pieces .append (int (px .total_seconds () * 1000 ))
2311- elif isinstance (px , int ):
2312- pieces .append (px )
2313- else :
2314- raise DataError ("px must be datetime.timedelta or int" )
2315- if exat is not None :
2316- pieces .append ("EXAT" )
2317- if isinstance (exat , datetime .datetime ):
2318- exat = int (exat .timestamp ())
2319- pieces .append (exat )
2320- if pxat is not None :
2321- pieces .append ("PXAT" )
2322- if isinstance (pxat , datetime .datetime ):
2323- pxat = int (pxat .timestamp () * 1000 )
2324- pieces .append (pxat )
2290+
2291+ pieces .extend (extract_expire_flags (ex , px , exat , pxat ))
2292+
23252293 if keepttl :
23262294 pieces .append ("KEEPTTL" )
23272295
@@ -4940,6 +4908,16 @@ def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT:
49404908AsyncHyperlogCommands = HyperlogCommands
49414909
49424910
4911+ class 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+
49434921class HashCommands (CommandsProtocol ):
49444922 """
49454923 Redis commands for Hash data type.
@@ -4980,6 +4958,80 @@ def hgetall(self, name: str) -> Union[Awaitable[dict], dict]:
49804958 """
49814959 return self .execute_command ("HGETALL" , name , keys = [name ])
49824960
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+
49835035 def hincrby (
49845036 self , name : str , key : str , amount : int = 1
49855037 ) -> Union [Awaitable [int ], int ]:
@@ -5034,8 +5086,10 @@ def hset(
50345086
50355087 For more information see https://redis.io/commands/hset
50365088 """
5089+
50375090 if key is None and not mapping and not items :
50385091 raise DataError ("'hset' with no key value pairs" )
5092+
50395093 pieces = []
50405094 if items :
50415095 pieces .extend (items )
@@ -5047,6 +5101,89 @@ def hset(
50475101
50485102 return self .execute_command ("HSET" , name , * pieces )
50495103
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+
50505187 def hsetnx (self , name : str , key : str , value : str ) -> Union [Awaitable [bool ], bool ]:
50515188 """
50525189 Set ``key`` to ``value`` within hash ``name`` if ``key`` does not
@@ -5056,19 +5193,18 @@ def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool
50565193 """
50575194 return self .execute_command ("HSETNX" , name , key , value )
50585195
5196+ @deprecated_function (
5197+ version = "4.0.0" ,
5198+ reason = "Use 'hset' instead." ,
5199+ name = "hmset" ,
5200+ )
50595201 def hmset (self , name : str , mapping : dict ) -> Union [Awaitable [str ], str ]:
50605202 """
50615203 Set key to value within hash ``name`` for each corresponding
50625204 key and value from the ``mapping`` dict.
50635205
50645206 For more information see https://redis.io/commands/hmset
50655207 """
5066- warnings .warn (
5067- f"{ self .__class__ .__name__ } .hmset() is deprecated. "
5068- f"Use { self .__class__ .__name__ } .hset() instead." ,
5069- DeprecationWarning ,
5070- stacklevel = 2 ,
5071- )
50725208 if not mapping :
50735209 raise DataError ("'hmset' with 'mapping' of length 0" )
50745210 items = []
0 commit comments