Skip to content

Commit

Permalink
Add hash expiry commands
Browse files Browse the repository at this point in the history
- Added HEXPIRE
- Added HEXPIREAT
  • Loading branch information
alisaifee committed Dec 3, 2024
1 parent 1188618 commit fda955a
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 20 deletions.
55 changes: 55 additions & 0 deletions coredis/commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2003,6 +2003,61 @@ async def hexists(self, key: KeyT, field: StringT) -> bool:
CommandName.HEXISTS, key, field, callback=BoolCallback()
)

@versionadded(version="4.18.0")
@redis_command(
CommandName.HEXPIRE, version_introduced="7.4.0", group=CommandGroup.HASH
)
async def hexpire(
self,
key: KeyT,
seconds: Union[int, datetime.timedelta],
fields: Parameters[StringT],
condition: Optional[
Literal[PureToken.GT, PureToken.LT, PureToken.NX, PureToken.XX]
] = None,
) -> Tuple[int, ...]:
"""
Set expiry for hash field using relative time to expire (seconds)
"""
pieces: CommandArgList = [key, normalized_seconds(seconds)]

if condition is not None:
pieces.append(condition)
pieces.append(PrefixToken.FIELDS)
pieces.append(len(fields))
pieces.extend(fields)

return await self.execute_command(
CommandName.HEXPIRE, *pieces, callback=TupleCallback[int]()
)

@versionadded(version="4.18.0")
@redis_command(
CommandName.HEXPIREAT, version_introduced="7.4.0", group=CommandGroup.HASH
)
async def hexpireat(
self,
key: KeyT,
unix_time_seconds: Union[int, datetime.datetime],
fields: Parameters[StringT],
condition: Optional[
Literal[PureToken.GT, PureToken.LT, PureToken.NX, PureToken.XX]
] = None,
) -> Tuple[int, ...]:
"""
Set expiry for hash field using an absolute Unix timestamp (seconds)
"""
pieces: CommandArgList = [key, normalized_time_seconds(unix_time_seconds)]
if condition is not None:
pieces.append(condition)
pieces.append(PrefixToken.FIELDS)
pieces.append(len(fields))
pieces.extend(fields)

return await self.execute_command(
CommandName.HEXPIREAT, *pieces, callback=TupleCallback[int]()
)

@redis_command(
CommandName.HGET,
group=CommandGroup.HASH,
Expand Down
56 changes: 36 additions & 20 deletions docs/source/compatibility.rst
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,42 @@ Determines whether a field exists in a hash.



HEXPIRE
*******

Set expiry for hash field using relative time to expire (seconds)

- Documentation: `HEXPIRE <https://redis.io/commands/hexpire>`_
- Implementation: :meth:`~coredis.Redis.hexpire`

- New in redis: 7.4.0



- .. versionadded:: 4.18.0





HEXPIREAT
*********

Set expiry for hash field using an absolute Unix timestamp (seconds)

- Documentation: `HEXPIREAT <https://redis.io/commands/hexpireat>`_
- Implementation: :meth:`~coredis.Redis.hexpireat`

- New in redis: 7.4.0



- .. versionadded:: 4.18.0





HGET
****

Expand Down Expand Up @@ -1192,26 +1228,6 @@ Returns all values in a hash.



HEXPIRE [X]
***********

Set expiry for hash field using relative time to expire (seconds)

- Documentation: `HEXPIRE <https://redis.io/commands/hexpire>`_

- Not Implemented


HEXPIREAT [X]
*************

Set expiry for hash field using an absolute Unix timestamp (seconds)

- Documentation: `HEXPIREAT <https://redis.io/commands/hexpireat>`_

- Not Implemented


HEXPIRETIME [X]
***************

Expand Down
48 changes: 48 additions & 0 deletions tests/commands/test_hash.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from __future__ import annotations

import asyncio
import datetime
import time

import pytest

from coredis import PureToken
from tests.conftest import server_deprecation_warning, targets


Expand Down Expand Up @@ -52,6 +57,49 @@ async def test_hexists(self, client, _s):
assert await client.hexists("a", "1")
assert not await client.hexists("a", "4")

@pytest.mark.min_server_version("7.4.0")
async def test_hexpire(self, client, _s):
await client.hset("a", {"1": 1, "2": 2, "3": 3, "4": 4})
assert (1,) == await client.hexpire("a", 5, ["1"])
assert (-2,) == await client.hexpire("missing", 1, ["missing"])
assert (0, 1, -2) == await client.hexpire("a", 5, ["1", "3", "5"], PureToken.NX)
assert (1, 1, -2) == await client.hexpire("a", 5, ["1", "3", "5"], PureToken.XX)
assert (0, 0, -2) == await client.hexpire("a", 1, ["1", "3", "5"], PureToken.GT)
assert (1, -2) == await client.hexpire("a", 1, ["4", "5"], PureToken.LT)
assert (2, 2, -2) == await client.hexpire(
"a", datetime.timedelta(seconds=0), ["1", "3", "5"], PureToken.LT
)
await asyncio.sleep(1)
assert {_s("2"): _s("2")} == await client.hgetall(_s("a"))

@pytest.mark.min_server_version("7.4.0")
async def test_hexpireat(self, client, _s, redis_server_time):
now = await redis_server_time(client)
now_int = int(time.mktime(now.timetuple()))
await client.hset("a", {"1": 1, "2": 2, "3": 3, "4": 4})
assert (1,) == await client.hexpireat("a", now_int + 5, ["1"])
assert (-2,) == await client.hexpireat("missing", now_int + 1, ["missing"])
assert (0, 1, -2) == await client.hexpireat(
"a", now_int + 5, ["1", "3", "5"], PureToken.NX
)
assert (1, 1, -2) == await client.hexpireat(
"a", now_int + 5, ["1", "3", "5"], PureToken.XX
)
assert (0, 0, -2) == await client.hexpireat(
"a", now_int + 1, ["1", "3", "5"], PureToken.GT
)
assert (1, -2) == await client.hexpireat(
"a", now_int + 1, ["4", "5"], PureToken.LT
)
assert (2, 2, -2) == await client.hexpireat(
"a",
now - datetime.timedelta(seconds=1),
["1", "3", "5"],
PureToken.LT,
)
await asyncio.sleep(1)
assert {_s("2"): _s("2")} == await client.hgetall(_s("a"))

async def test_hgetall(self, client, _s):
h = {_s("a1"): _s("1"), _s("a2"): _s("2"), _s("a3"): _s("3")}
await client.hset("a", h)
Expand Down

0 comments on commit fda955a

Please sign in to comment.