Skip to content

Commit db6f378

Browse files
gerzseagnesnatasya
authored andcommitted
Hash field expiration commands (redis#3218)
Support hash field expiration commands that become available with Redis 7.4. Adapt some tests to match recent server-side changes. Update tests related to memory stats. Make CLIENT KILL test not run with cluster. Disable tests related to Graph module. The Graph module is no longer part of Redis Stack, so for the moment disable all related tests. --------- Co-authored-by: Gabriel Erzse <[email protected]>
1 parent 873426b commit db6f378

File tree

9 files changed

+1086
-7
lines changed

9 files changed

+1086
-7
lines changed

redis/commands/core.py

+368
Original file line numberDiff line numberDiff line change
@@ -5125,6 +5125,374 @@ def hstrlen(self, name: str, key: str) -> Union[Awaitable[int], int]:
51255125
"""
51265126
return self.execute_command("HSTRLEN", name, key, keys=[name])
51275127

5128+
def hexpire(
5129+
self,
5130+
name: KeyT,
5131+
seconds: ExpiryT,
5132+
*fields: str,
5133+
nx: bool = False,
5134+
xx: bool = False,
5135+
gt: bool = False,
5136+
lt: bool = False,
5137+
) -> ResponseT:
5138+
"""
5139+
Sets or updates the expiration time for fields within a hash key, using relative
5140+
time in seconds.
5141+
5142+
If a field already has an expiration time, the behavior of the update can be
5143+
controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5144+
5145+
The return value provides detailed information about the outcome for each field.
5146+
5147+
For more information, see https://redis.io/commands/hexpire
5148+
5149+
Args:
5150+
name: The name of the hash key.
5151+
seconds: Expiration time in seconds, relative. Can be an integer, or a
5152+
Python `timedelta` object.
5153+
fields: List of fields within the hash to apply the expiration time to.
5154+
nx: Set expiry only when the field has no expiry.
5155+
xx: Set expiry only when the field has an existing expiry.
5156+
gt: Set expiry only when the new expiry is greater than the current one.
5157+
lt: Set expiry only when the new expiry is less than the current one.
5158+
5159+
Returns:
5160+
If the key does not exist, returns an empty list. If the key exists, returns
5161+
a list which contains for each field in the request:
5162+
- `-2` if the field does not exist.
5163+
- `0` if the specified NX | XX | GT | LT condition was not met.
5164+
- `1` if the expiration time was set or updated.
5165+
- `2` if the field was deleted because the specified expiration time is
5166+
in the past.
5167+
"""
5168+
conditions = [nx, xx, gt, lt]
5169+
if sum(conditions) > 1:
5170+
raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5171+
5172+
if isinstance(seconds, datetime.timedelta):
5173+
seconds = int(seconds.total_seconds())
5174+
5175+
options = []
5176+
if nx:
5177+
options.append("NX")
5178+
if xx:
5179+
options.append("XX")
5180+
if gt:
5181+
options.append("GT")
5182+
if lt:
5183+
options.append("LT")
5184+
5185+
return self.execute_command(
5186+
"HEXPIRE", name, seconds, *options, "FIELDS", len(fields), *fields
5187+
)
5188+
5189+
def hpexpire(
5190+
self,
5191+
name: KeyT,
5192+
milliseconds: ExpiryT,
5193+
*fields: str,
5194+
nx: bool = False,
5195+
xx: bool = False,
5196+
gt: bool = False,
5197+
lt: bool = False,
5198+
) -> ResponseT:
5199+
"""
5200+
Sets or updates the expiration time for fields within a hash key, using relative
5201+
time in milliseconds.
5202+
5203+
If a field already has an expiration time, the behavior of the update can be
5204+
controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5205+
5206+
The return value provides detailed information about the outcome for each field.
5207+
5208+
For more information, see https://redis.io/commands/hpexpire
5209+
5210+
Args:
5211+
name: The name of the hash key.
5212+
milliseconds: Expiration time in milliseconds, relative. Can be an integer,
5213+
or a Python `timedelta` object.
5214+
fields: List of fields within the hash to apply the expiration time to.
5215+
nx: Set expiry only when the field has no expiry.
5216+
xx: Set expiry only when the field has an existing expiry.
5217+
gt: Set expiry only when the new expiry is greater than the current one.
5218+
lt: Set expiry only when the new expiry is less than the current one.
5219+
5220+
Returns:
5221+
If the key does not exist, returns an empty list. If the key exists, returns
5222+
a list which contains for each field in the request:
5223+
- `-2` if the field does not exist.
5224+
- `0` if the specified NX | XX | GT | LT condition was not met.
5225+
- `1` if the expiration time was set or updated.
5226+
- `2` if the field was deleted because the specified expiration time is
5227+
in the past.
5228+
"""
5229+
conditions = [nx, xx, gt, lt]
5230+
if sum(conditions) > 1:
5231+
raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5232+
5233+
if isinstance(milliseconds, datetime.timedelta):
5234+
milliseconds = int(milliseconds.total_seconds() * 1000)
5235+
5236+
options = []
5237+
if nx:
5238+
options.append("NX")
5239+
if xx:
5240+
options.append("XX")
5241+
if gt:
5242+
options.append("GT")
5243+
if lt:
5244+
options.append("LT")
5245+
5246+
return self.execute_command(
5247+
"HPEXPIRE", name, milliseconds, *options, "FIELDS", len(fields), *fields
5248+
)
5249+
5250+
def hexpireat(
5251+
self,
5252+
name: KeyT,
5253+
unix_time_seconds: AbsExpiryT,
5254+
*fields: str,
5255+
nx: bool = False,
5256+
xx: bool = False,
5257+
gt: bool = False,
5258+
lt: bool = False,
5259+
) -> ResponseT:
5260+
"""
5261+
Sets or updates the expiration time for fields within a hash key, using an
5262+
absolute Unix timestamp in seconds.
5263+
5264+
If a field already has an expiration time, the behavior of the update can be
5265+
controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5266+
5267+
The return value provides detailed information about the outcome for each field.
5268+
5269+
For more information, see https://redis.io/commands/hexpireat
5270+
5271+
Args:
5272+
name: The name of the hash key.
5273+
unix_time_seconds: Expiration time as Unix timestamp in seconds. Can be an
5274+
integer or a Python `datetime` object.
5275+
fields: List of fields within the hash to apply the expiration time to.
5276+
nx: Set expiry only when the field has no expiry.
5277+
xx: Set expiry only when the field has an existing expiration time.
5278+
gt: Set expiry only when the new expiry is greater than the current one.
5279+
lt: Set expiry only when the new expiry is less than the current one.
5280+
5281+
Returns:
5282+
If the key does not exist, returns an empty list. If the key exists, returns
5283+
a list which contains for each field in the request:
5284+
- `-2` if the field does not exist.
5285+
- `0` if the specified NX | XX | GT | LT condition was not met.
5286+
- `1` if the expiration time was set or updated.
5287+
- `2` if the field was deleted because the specified expiration time is
5288+
in the past.
5289+
"""
5290+
conditions = [nx, xx, gt, lt]
5291+
if sum(conditions) > 1:
5292+
raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5293+
5294+
if isinstance(unix_time_seconds, datetime.datetime):
5295+
unix_time_seconds = int(unix_time_seconds.timestamp())
5296+
5297+
options = []
5298+
if nx:
5299+
options.append("NX")
5300+
if xx:
5301+
options.append("XX")
5302+
if gt:
5303+
options.append("GT")
5304+
if lt:
5305+
options.append("LT")
5306+
5307+
return self.execute_command(
5308+
"HEXPIREAT",
5309+
name,
5310+
unix_time_seconds,
5311+
*options,
5312+
"FIELDS",
5313+
len(fields),
5314+
*fields,
5315+
)
5316+
5317+
def hpexpireat(
5318+
self,
5319+
name: KeyT,
5320+
unix_time_milliseconds: AbsExpiryT,
5321+
*fields: str,
5322+
nx: bool = False,
5323+
xx: bool = False,
5324+
gt: bool = False,
5325+
lt: bool = False,
5326+
) -> ResponseT:
5327+
"""
5328+
Sets or updates the expiration time for fields within a hash key, using an
5329+
absolute Unix timestamp in milliseconds.
5330+
5331+
If a field already has an expiration time, the behavior of the update can be
5332+
controlled using the `nx`, `xx`, `gt`, and `lt` parameters.
5333+
5334+
The return value provides detailed information about the outcome for each field.
5335+
5336+
For more information, see https://redis.io/commands/hpexpireat
5337+
5338+
Args:
5339+
name: The name of the hash key.
5340+
unix_time_milliseconds: Expiration time as Unix timestamp in milliseconds.
5341+
Can be an integer or a Python `datetime` object.
5342+
fields: List of fields within the hash to apply the expiry.
5343+
nx: Set expiry only when the field has no expiry.
5344+
xx: Set expiry only when the field has an existing expiry.
5345+
gt: Set expiry only when the new expiry is greater than the current one.
5346+
lt: Set expiry only when the new expiry is less than the current one.
5347+
5348+
Returns:
5349+
If the key does not exist, returns an empty list. If the key exists, returns
5350+
a list which contains for each field in the request:
5351+
- `-2` if the field does not exist.
5352+
- `0` if the specified NX | XX | GT | LT condition was not met.
5353+
- `1` if the expiration time was set or updated.
5354+
- `2` if the field was deleted because the specified expiration time is
5355+
in the past.
5356+
"""
5357+
conditions = [nx, xx, gt, lt]
5358+
if sum(conditions) > 1:
5359+
raise ValueError("Only one of 'nx', 'xx', 'gt', 'lt' can be specified.")
5360+
5361+
if isinstance(unix_time_milliseconds, datetime.datetime):
5362+
unix_time_milliseconds = int(unix_time_milliseconds.timestamp() * 1000)
5363+
5364+
options = []
5365+
if nx:
5366+
options.append("NX")
5367+
if xx:
5368+
options.append("XX")
5369+
if gt:
5370+
options.append("GT")
5371+
if lt:
5372+
options.append("LT")
5373+
5374+
return self.execute_command(
5375+
"HPEXPIREAT",
5376+
name,
5377+
unix_time_milliseconds,
5378+
*options,
5379+
"FIELDS",
5380+
len(fields),
5381+
*fields,
5382+
)
5383+
5384+
def hpersist(self, name: KeyT, *fields: str) -> ResponseT:
5385+
"""
5386+
Removes the expiration time for each specified field in a hash.
5387+
5388+
For more information, see https://redis.io/commands/hpersist
5389+
5390+
Args:
5391+
name: The name of the hash key.
5392+
fields: A list of fields within the hash from which to remove the
5393+
expiration time.
5394+
5395+
Returns:
5396+
If the key does not exist, returns an empty list. If the key exists, returns
5397+
a list which contains for each field in the request:
5398+
- `-2` if the field does not exist.
5399+
- `-1` if the field exists but has no associated expiration time.
5400+
- `1` if the expiration time was successfully removed from the field.
5401+
"""
5402+
return self.execute_command("HPERSIST", name, "FIELDS", len(fields), *fields)
5403+
5404+
def hexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
5405+
"""
5406+
Returns the expiration times of hash fields as Unix timestamps in seconds.
5407+
5408+
For more information, see https://redis.io/commands/hexpiretime
5409+
5410+
Args:
5411+
key: The hash key.
5412+
fields: A list of fields within the hash for which to get the expiration
5413+
time.
5414+
5415+
Returns:
5416+
If the key does not exist, returns an empty list. If the key exists, returns
5417+
a list which contains for each field in the request:
5418+
- `-2` if the field does not exist.
5419+
- `-1` if the field exists but has no associated expire time.
5420+
- A positive integer representing the expiration Unix timestamp in
5421+
seconds, if the field has an associated expiration time.
5422+
"""
5423+
return self.execute_command(
5424+
"HEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key]
5425+
)
5426+
5427+
def hpexpiretime(self, key: KeyT, *fields: str) -> ResponseT:
5428+
"""
5429+
Returns the expiration times of hash fields as Unix timestamps in milliseconds.
5430+
5431+
For more information, see https://redis.io/commands/hpexpiretime
5432+
5433+
Args:
5434+
key: The hash key.
5435+
fields: A list of fields within the hash for which to get the expiration
5436+
time.
5437+
5438+
Returns:
5439+
If the key does not exist, returns an empty list. If the key exists, returns
5440+
a list which contains for each field in the request:
5441+
- `-2` if the field does not exist.
5442+
- `-1` if the field exists but has no associated expire time.
5443+
- A positive integer representing the expiration Unix timestamp in
5444+
milliseconds, if the field has an associated expiration time.
5445+
"""
5446+
return self.execute_command(
5447+
"HPEXPIRETIME", key, "FIELDS", len(fields), *fields, keys=[key]
5448+
)
5449+
5450+
def httl(self, key: KeyT, *fields: str) -> ResponseT:
5451+
"""
5452+
Returns the TTL (Time To Live) in seconds for each specified field within a hash
5453+
key.
5454+
5455+
For more information, see https://redis.io/commands/httl
5456+
5457+
Args:
5458+
key: The hash key.
5459+
fields: A list of fields within the hash for which to get the TTL.
5460+
5461+
Returns:
5462+
If the key does not exist, returns an empty list. If the key exists, returns
5463+
a list which contains for each field in the request:
5464+
- `-2` if the field does not exist.
5465+
- `-1` if the field exists but has no associated expire time.
5466+
- A positive integer representing the TTL in seconds if the field has
5467+
an associated expiration time.
5468+
"""
5469+
return self.execute_command(
5470+
"HTTL", key, "FIELDS", len(fields), *fields, keys=[key]
5471+
)
5472+
5473+
def hpttl(self, key: KeyT, *fields: str) -> ResponseT:
5474+
"""
5475+
Returns the TTL (Time To Live) in milliseconds for each specified field within a
5476+
hash key.
5477+
5478+
For more information, see https://redis.io/commands/hpttl
5479+
5480+
Args:
5481+
key: The hash key.
5482+
fields: A list of fields within the hash for which to get the TTL.
5483+
5484+
Returns:
5485+
If the key does not exist, returns an empty list. If the key exists, returns
5486+
a list which contains for each field in the request:
5487+
- `-2` if the field does not exist.
5488+
- `-1` if the field exists but has no associated expire time.
5489+
- A positive integer representing the TTL in milliseconds if the field
5490+
has an associated expiration time.
5491+
"""
5492+
return self.execute_command(
5493+
"HPTTL", key, "FIELDS", len(fields), *fields, keys=[key]
5494+
)
5495+
51285496

51295497
AsyncHashCommands = HashCommands
51305498

tests/test_asyncio/test_cluster.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1453,7 +1453,7 @@ async def test_memory_stats(self, r: RedisCluster) -> None:
14531453
assert isinstance(stats, dict)
14541454
for key, value in stats.items():
14551455
if key.startswith("db."):
1456-
assert isinstance(value, dict)
1456+
assert not isinstance(value, list)
14571457

14581458
@skip_if_server_version_lt("4.0.0")
14591459
async def test_memory_help(self, r: RedisCluster) -> None:

0 commit comments

Comments
 (0)