Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions tests/test_asyncio/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2151,6 +2151,32 @@ async def test_hstrlen(self, r: valkey.Valkey):
assert await r.hstrlen("a", "1") == 2
assert await r.hstrlen("a", "2") == 3

@skip_if_server_version_lt("9.0.0")
async def test_hsetex(self, r):
assert await r.hsetex("a", "field1", "value1", ex=5) == 1
assert await r.hget("a", "field1") == b"value1"
assert await r.hsetex("a", "field1", "value2", ex=5) == 1
assert await r.hget("a", "field1") == b"value2"

@skip_if_server_version_lt("9.0.0")
async def test_hsetex_invalid_params(self, r):
with pytest.raises(exceptions.DataError):
await r.hsetex("a", "field1", "value1", ex=5, px=5000)

@skip_if_server_version_lt("9.0.0")
async def test_hsetex_px(self, r):
assert await r.hsetex("a", "field1", "value1", px=5000) == 1
assert await r.hget("a", "field1") == b"value1"
assert await r.hsetex("a", "field1", "value2", px=5000) == 1
assert await r.hget("a", "field1") == b"value2"

@skip_if_server_version_lt("9.0.0")
async def test_hsetex_mapping(self, r):
mapping = {"field1": "value1", "field2": "value2"}
assert await r.hsetex("a", mapping=mapping, ex=5) == 1
assert await r.hget("a", "field1") == b"value1"
assert await r.hget("a", "field2") == b"value2"

# SORT
async def test_sort_basic(self, r: valkey.Valkey):
await r.rpush("a", "3", "2", "1", "4")
Expand Down
26 changes: 26 additions & 0 deletions tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -3236,6 +3236,32 @@ def test_hstrlen(self, r):
assert r.hstrlen("a", "1") == 2
assert r.hstrlen("a", "2") == 3

@skip_if_server_version_lt("9.0.0")
def test_hsetex(self, r):
assert r.hsetex("a", "field1", "value1", ex=5) == 1
assert r.hget("a", "field1") == b"value1"
assert r.hsetex("a", "field1", "value2", ex=5) == 1
assert r.hget("a", "field1") == b"value2"

@skip_if_server_version_lt("9.0.0")
def test_hsetex_invalid_params(self, r):
with pytest.raises(exceptions.DataError):
r.hsetex("a", "field1", "value1", ex=5, px=5000) # Both ex and px provided

@skip_if_server_version_lt("9.0.0")
def test_hsetex_px(self, r):
assert r.hsetex("a", "field1", "value1", px=5000) == 1
assert r.hget("a", "field1") == b"value1"
assert r.hsetex("a", "field1", "value2", px=5000) == 1
assert r.hget("a", "field1") == b"value2"

@skip_if_server_version_lt("9.0.0")
def test_hsetex_mapping(self, r):
mapping = {"field1": "value1", "field2": "value2"}
assert r.hsetex("a", mapping=mapping, ex=5) == 1
assert r.hget("a", "field1") == b"value1"
assert r.hget("a", "field2") == b"value2"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also add some tests that would check at least EX and PX?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried this but for this the command HTTL is not implemented yet. Maybe it should be worth to include also this command?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you could handle this, I would greatly appreciate it! :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't have time for this, you can ignore this for now. I can add the HTTL and add the corresponding tests later

# SORT
def test_sort_basic(self, r):
r.rpush("a", "3", "2", "1", "4")
Expand Down
72 changes: 72 additions & 0 deletions valkey/commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5055,6 +5055,78 @@ def hsetnx(self, name: str, key: str, value: str) -> Union[Awaitable[bool], bool
"""
return self.execute_command("HSETNX", name, key, value)

def hsetex(
self,
name: str,
key: Optional[str] = None,
value: Optional[str] = None,
mapping: Optional[dict] = None,
items: Optional[list] = None,
ex: Union[ExpiryT, None] = None,
px: Union[ExpiryT, None] = None,
exat: Union[AbsExpiryT, None] = None,
pxat: Union[AbsExpiryT, None] = None,
keepttl: bool = False,
nx: bool = False,
xx: bool = False,
fnx: bool = False,
fxx: bool = False,
) -> Union[Awaitable[bool], bool]:
"""
Set key to value within hash ``name``,
``mapping`` accepts a dict of key/value pairs to be added to hash ``name``.
``items`` accepts a list of key/value pairs to be added to hash ``name``.
Set expiration options for the hash fields.

For more information see https://valkey.io/commands/hsetex
"""

if key is None and not mapping and not items:
raise DataError("'hsetex' with no key value pairs")

if int(keepttl) + sum(arg is not None for arg in [ex, px, exat, pxat]) > 1:
raise DataError(
"Only one of 'ex', 'px', 'exat', 'pxat', or 'keepttl' can be specified."
)
if nx and xx:
raise DataError("Only one of 'nx' or 'xx' can be specified.")
if fnx and fxx:
raise DataError("Only one of 'fnx' or 'fxx' can be specified.")
pieces = []
if ex is not None:
pieces.extend(["EX", ex])
if px is not None:
pieces.extend(["PX", px])
if exat is not None:
pieces.extend(["EXAT", exat])
if pxat is not None:
pieces.extend(["PXAT", pxat])
if nx:
pieces.append("NX")
if xx:
pieces.append("XX")
if fnx:
pieces.append("FNX")
if fxx:
pieces.append("FXX")
pieces.append("FIELDS")
if key is not None and value is not None:
pieces.append(1) # for one field
pieces.append(key)
pieces.append(value)
if mapping:
pieces.append(len(mapping))
for key, value in mapping.items():
if key is None or value is None:
raise DataError("'hsetex' mapping contains None key or value")
pieces.append(key)
pieces.append(value)
if items:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably checking for only one of key + value, mapping or items would be great

pieces.append(len(items) // 2)
pieces.extend(items)

return self.execute_command("HSETEX", name, *pieces)

def hmset(self, name: str, mapping: dict) -> Union[Awaitable[str], str]:
"""
Set key to value within hash ``name`` for each corresponding
Expand Down