3
3
import datetime
4
4
import hashlib
5
5
import warnings
6
+ from enum import Enum
6
7
from typing import (
7
8
TYPE_CHECKING ,
8
9
Any ,
44
45
TimeoutSecT ,
45
46
ZScoreBoundT ,
46
47
)
48
+ from redis .utils import (
49
+ deprecated_function ,
50
+ extract_expire_flags ,
51
+ )
47
52
48
53
from .helpers import list_or_args
49
54
@@ -1837,10 +1842,10 @@ def getdel(self, name: KeyT) -> ResponseT:
1837
1842
def getex (
1838
1843
self ,
1839
1844
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 ,
1844
1849
persist : bool = False ,
1845
1850
) -> ResponseT :
1846
1851
"""
@@ -1863,41 +1868,19 @@ def getex(
1863
1868
1864
1869
For more information see https://redis.io/commands/getex
1865
1870
"""
1866
-
1867
1871
opset = {ex , px , exat , pxat }
1868
1872
if len (opset ) > 2 or len (opset ) > 1 and persist :
1869
1873
raise DataError (
1870
1874
"``ex``, ``px``, ``exat``, ``pxat``, "
1871
1875
"and ``persist`` are mutually exclusive."
1872
1876
)
1873
1877
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
+
1897
1880
if persist :
1898
- pieces .append ("PERSIST" )
1881
+ exp_options .append ("PERSIST" )
1899
1882
1900
- return self .execute_command ("GETEX" , name , * pieces )
1883
+ return self .execute_command ("GETEX" , name , * exp_options )
1901
1884
1902
1885
def __getitem__ (self , name : KeyT ):
1903
1886
"""
@@ -2255,14 +2238,14 @@ def set(
2255
2238
self ,
2256
2239
name : KeyT ,
2257
2240
value : EncodableT ,
2258
- ex : Union [ExpiryT , None ] = None ,
2259
- px : Union [ExpiryT , None ] = None ,
2241
+ ex : Optional [ExpiryT ] = None ,
2242
+ px : Optional [ExpiryT ] = None ,
2260
2243
nx : bool = False ,
2261
2244
xx : bool = False ,
2262
2245
keepttl : bool = False ,
2263
2246
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 ,
2266
2249
) -> ResponseT :
2267
2250
"""
2268
2251
Set the value at key ``name`` to ``value``
@@ -2292,36 +2275,21 @@ def set(
2292
2275
2293
2276
For more information see https://redis.io/commands/set
2294
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
+
2295
2288
pieces : list [EncodableT ] = [name , value ]
2296
2289
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
+
2325
2293
if keepttl :
2326
2294
pieces .append ("KEEPTTL" )
2327
2295
@@ -4940,6 +4908,16 @@ def pfmerge(self, dest: KeyT, *sources: KeyT) -> ResponseT:
4940
4908
AsyncHyperlogCommands = HyperlogCommands
4941
4909
4942
4910
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
+
4943
4921
class HashCommands (CommandsProtocol ):
4944
4922
"""
4945
4923
Redis commands for Hash data type.
@@ -4980,6 +4958,80 @@ def hgetall(self, name: str) -> Union[Awaitable[dict], dict]:
4980
4958
"""
4981
4959
return self .execute_command ("HGETALL" , name , keys = [name ])
4982
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
+
4983
5035
def hincrby (
4984
5036
self , name : str , key : str , amount : int = 1
4985
5037
) -> Union [Awaitable [int ], int ]:
@@ -5034,8 +5086,10 @@ def hset(
5034
5086
5035
5087
For more information see https://redis.io/commands/hset
5036
5088
"""
5089
+
5037
5090
if key is None and not mapping and not items :
5038
5091
raise DataError ("'hset' with no key value pairs" )
5092
+
5039
5093
pieces = []
5040
5094
if items :
5041
5095
pieces .extend (items )
@@ -5047,6 +5101,89 @@ def hset(
5047
5101
5048
5102
return self .execute_command ("HSET" , name , * pieces )
5049
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
+
5050
5187
def hsetnx (self , name : str , key : str , value : str ) -> Union [Awaitable [bool ], bool ]:
5051
5188
"""
5052
5189
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
5056
5193
"""
5057
5194
return self .execute_command ("HSETNX" , name , key , value )
5058
5195
5196
+ @deprecated_function (
5197
+ version = "4.0.0" ,
5198
+ reason = "Use 'hset' instead." ,
5199
+ name = "hmset" ,
5200
+ )
5059
5201
def hmset (self , name : str , mapping : dict ) -> Union [Awaitable [str ], str ]:
5060
5202
"""
5061
5203
Set key to value within hash ``name`` for each corresponding
5062
5204
key and value from the ``mapping`` dict.
5063
5205
5064
5206
For more information see https://redis.io/commands/hmset
5065
5207
"""
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
- )
5072
5208
if not mapping :
5073
5209
raise DataError ("'hmset' with 'mapping' of length 0" )
5074
5210
items = []
0 commit comments