Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
* Update `ResponseT` type hint
* Allow to control the minimum SSL version
* Add an optional lock_name attribute to LockError.
* Fix return types for `get`, `set_path` and `strappend` in JSONCommands
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
long_description_content_type="text/markdown",
keywords=["Valkey", "key-value store", "database"],
license="MIT",
version="5.1.0b5",
version="5.1.0b6",
packages=find_packages(
include=[
"valkey",
Expand All @@ -33,7 +33,7 @@
"Issue tracker": "https://github.com/valkey-io/valkey-py/issues",
},
author="valkey-py authors",
author_email="placeholder@valkey.io",
author_email="valkey-py@lists.valkey.io",
python_requires=">=3.8",
install_requires=[
'async-timeout>=4.0.3; python_full_version<"3.11.3"',
Expand Down
2 changes: 1 addition & 1 deletion tests/ssl_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ def get_ssl_filename(name):
os.path.join(root, "..", "dockers", "stunnel", "keys")
)
if not os.path.isdir(cert_dir):
raise IOError(f"No SSL certificates found. They should be in {cert_dir}")
raise OSError(f"No SSL certificates found. They should be in {cert_dir}")

return os.path.join(cert_dir, name)
2 changes: 1 addition & 1 deletion tests/test_asyncio/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
try:
mock.AsyncMock
except AttributeError:
import mock
from unittest import mock

try:
from contextlib import aclosing
Expand Down
2 changes: 1 addition & 1 deletion tests/test_asyncio/test_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -1430,7 +1430,7 @@ async def test_memory_stats(self, r: ValkeyCluster) -> None:
assert isinstance(stats, dict)
for key, value in stats.items():
if key.startswith("db."):
assert isinstance(value, dict)
assert not isinstance(value, list)

@skip_if_server_version_lt("4.0.0")
async def test_memory_help(self, r: ValkeyCluster) -> None:
Expand Down
6 changes: 3 additions & 3 deletions tests/test_asyncio/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -1352,7 +1352,7 @@ async def test_hscan(self, r: valkey.Valkey):
_, dic = await r.hscan("a_notset", match="a")
assert dic == {}

@skip_if_server_version_lt("7.4.0")
@skip_if_server_version_lt("7.3.240")
async def test_hscan_novalues(self, r: valkey.Valkey):
await r.hset("a", mapping={"a": 1, "b": 2, "c": 3})
cursor, keys = await r.hscan("a", no_values=True)
Expand All @@ -1373,7 +1373,7 @@ async def test_hscan_iter(self, r: valkey.Valkey):
dic = {k: v async for k, v in r.hscan_iter("a_notset", match="a")}
assert dic == {}

@skip_if_server_version_lt("7.4.0")
@skip_if_server_version_lt("7.3.240")
async def test_hscan_iter_novalues(self, r: valkey.Valkey):
await r.hset("a", mapping={"a": 1, "b": 2, "c": 3})
keys = list([k async for k in r.hscan_iter("a", no_values=True)])
Expand Down Expand Up @@ -3235,7 +3235,7 @@ async def test_memory_stats(self, r: valkey.Valkey):
assert isinstance(stats, dict)
for key, value in stats.items():
if key.startswith("db."):
assert isinstance(value, dict)
assert not isinstance(value, list)

@skip_if_server_version_lt("4.0.0")
async def test_memory_usage(self, r: valkey.Valkey):
Expand Down
300 changes: 300 additions & 0 deletions tests/test_asyncio/test_hash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
import asyncio
from datetime import datetime, timedelta

from tests.conftest import skip_if_server_version_lt


@skip_if_server_version_lt("7.3.240")
async def test_hexpire_basic(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
assert await r.hexpire("test:hash", 1, "field1") == [1]
await asyncio.sleep(1.1)
assert await r.hexists("test:hash", "field1") is False
assert await r.hexists("test:hash", "field2") is True


@skip_if_server_version_lt("7.3.240")
async def test_hexpire_with_timedelta(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
assert await r.hexpire("test:hash", timedelta(seconds=1), "field1") == [1]
await asyncio.sleep(1.1)
assert await r.hexists("test:hash", "field1") is False
assert await r.hexists("test:hash", "field2") is True


@skip_if_server_version_lt("7.3.240")
async def test_hexpire_conditions(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1"})
assert await r.hexpire("test:hash", 2, "field1", xx=True) == [0]
assert await r.hexpire("test:hash", 2, "field1", nx=True) == [1]
assert await r.hexpire("test:hash", 1, "field1", xx=True) == [1]
assert await r.hexpire("test:hash", 2, "field1", nx=True) == [0]
await asyncio.sleep(1.1)
assert await r.hexists("test:hash", "field1") is False
await r.hset("test:hash", "field1", "value1")
await r.hexpire("test:hash", 2, "field1")
assert await r.hexpire("test:hash", 1, "field1", gt=True) == [0]
assert await r.hexpire("test:hash", 1, "field1", lt=True) == [1]
await asyncio.sleep(1.1)
assert await r.hexists("test:hash", "field1") is False


@skip_if_server_version_lt("7.3.240")
async def test_hexpire_nonexistent_key_or_field(r):
await r.delete("test:hash")
assert await r.hexpire("test:hash", 1, "field1") == []
await r.hset("test:hash", "field1", "value1")
assert await r.hexpire("test:hash", 1, "nonexistent_field") == [-2]


@skip_if_server_version_lt("7.3.240")
async def test_hexpire_multiple_fields(r):
await r.delete("test:hash")
await r.hset(
"test:hash",
mapping={"field1": "value1", "field2": "value2", "field3": "value3"},
)
assert await r.hexpire("test:hash", 1, "field1", "field2") == [1, 1]
await asyncio.sleep(1.1)
assert await r.hexists("test:hash", "field1") is False
assert await r.hexists("test:hash", "field2") is False
assert await r.hexists("test:hash", "field3") is True


@skip_if_server_version_lt("7.3.240")
async def test_hpexpire_basic(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
assert await r.hpexpire("test:hash", 500, "field1") == [1]
await asyncio.sleep(0.6)
assert await r.hexists("test:hash", "field1") is False
assert await r.hexists("test:hash", "field2") is True


@skip_if_server_version_lt("7.3.240")
async def test_hpexpire_with_timedelta(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
assert await r.hpexpire("test:hash", timedelta(milliseconds=500), "field1") == [1]
await asyncio.sleep(0.6)
assert await r.hexists("test:hash", "field1") is False
assert await r.hexists("test:hash", "field2") is True


@skip_if_server_version_lt("7.3.240")
async def test_hpexpire_conditions(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1"})
assert await r.hpexpire("test:hash", 1500, "field1", xx=True) == [0]
assert await r.hpexpire("test:hash", 1500, "field1", nx=True) == [1]
assert await r.hpexpire("test:hash", 500, "field1", xx=True) == [1]
assert await r.hpexpire("test:hash", 1500, "field1", nx=True) == [0]
await asyncio.sleep(0.6)
assert await r.hexists("test:hash", "field1") is False
await r.hset("test:hash", "field1", "value1")
await r.hpexpire("test:hash", 1000, "field1")
assert await r.hpexpire("test:hash", 500, "field1", gt=True) == [0]
assert await r.hpexpire("test:hash", 500, "field1", lt=True) == [1]
await asyncio.sleep(0.6)
assert await r.hexists("test:hash", "field1") is False


@skip_if_server_version_lt("7.3.240")
async def test_hpexpire_nonexistent_key_or_field(r):
await r.delete("test:hash")
assert await r.hpexpire("test:hash", 500, "field1") == []
await r.hset("test:hash", "field1", "value1")
assert await r.hpexpire("test:hash", 500, "nonexistent_field") == [-2]


@skip_if_server_version_lt("7.3.240")
async def test_hpexpire_multiple_fields(r):
await r.delete("test:hash")
await r.hset(
"test:hash",
mapping={"field1": "value1", "field2": "value2", "field3": "value3"},
)
assert await r.hpexpire("test:hash", 500, "field1", "field2") == [1, 1]
await asyncio.sleep(0.6)
assert await r.hexists("test:hash", "field1") is False
assert await r.hexists("test:hash", "field2") is False
assert await r.hexists("test:hash", "field3") is True


@skip_if_server_version_lt("7.3.240")
async def test_hexpireat_basic(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
assert await r.hexpireat("test:hash", exp_time, "field1") == [1]
await asyncio.sleep(1.1)
assert await r.hexists("test:hash", "field1") is False
assert await r.hexists("test:hash", "field2") is True


@skip_if_server_version_lt("7.3.240")
async def test_hexpireat_with_datetime(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
exp_time = datetime.now() + timedelta(seconds=1)
assert await r.hexpireat("test:hash", exp_time, "field1") == [1]
await asyncio.sleep(1.1)
assert await r.hexists("test:hash", "field1") is False
assert await r.hexists("test:hash", "field2") is True


@skip_if_server_version_lt("7.3.240")
async def test_hexpireat_conditions(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1"})
future_exp_time = int((datetime.now() + timedelta(seconds=2)).timestamp())
past_exp_time = int((datetime.now() - timedelta(seconds=1)).timestamp())
assert await r.hexpireat("test:hash", future_exp_time, "field1", xx=True) == [0]
assert await r.hexpireat("test:hash", future_exp_time, "field1", nx=True) == [1]
assert await r.hexpireat("test:hash", past_exp_time, "field1", gt=True) == [0]
assert await r.hexpireat("test:hash", past_exp_time, "field1", lt=True) == [2]
assert await r.hexists("test:hash", "field1") is False


@skip_if_server_version_lt("7.3.240")
async def test_hexpireat_nonexistent_key_or_field(r):
await r.delete("test:hash")
future_exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
assert await r.hexpireat("test:hash", future_exp_time, "field1") == []
await r.hset("test:hash", "field1", "value1")
assert await r.hexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2]


@skip_if_server_version_lt("7.3.240")
async def test_hexpireat_multiple_fields(r):
await r.delete("test:hash")
await r.hset(
"test:hash",
mapping={"field1": "value1", "field2": "value2", "field3": "value3"},
)
exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
assert await r.hexpireat("test:hash", exp_time, "field1", "field2") == [1, 1]
await asyncio.sleep(1.1)
assert await r.hexists("test:hash", "field1") is False
assert await r.hexists("test:hash", "field2") is False
assert await r.hexists("test:hash", "field3") is True


@skip_if_server_version_lt("7.3.240")
async def test_hpexpireat_basic(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
exp_time = int((datetime.now() + timedelta(milliseconds=400)).timestamp() * 1000)
assert await r.hpexpireat("test:hash", exp_time, "field1") == [1]
await asyncio.sleep(0.5)
assert await r.hexists("test:hash", "field1") is False
assert await r.hexists("test:hash", "field2") is True


@skip_if_server_version_lt("7.3.240")
async def test_hpexpireat_with_datetime(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
exp_time = datetime.now() + timedelta(milliseconds=400)
assert await r.hpexpireat("test:hash", exp_time, "field1") == [1]
await asyncio.sleep(0.5)
assert await r.hexists("test:hash", "field1") is False
assert await r.hexists("test:hash", "field2") is True


@skip_if_server_version_lt("7.3.240")
async def test_hpexpireat_conditions(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1"})
future_exp_time = int(
(datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000
)
past_exp_time = int(
(datetime.now() - timedelta(milliseconds=500)).timestamp() * 1000
)
assert await r.hpexpireat("test:hash", future_exp_time, "field1", xx=True) == [0]
assert await r.hpexpireat("test:hash", future_exp_time, "field1", nx=True) == [1]
assert await r.hpexpireat("test:hash", past_exp_time, "field1", gt=True) == [0]
assert await r.hpexpireat("test:hash", past_exp_time, "field1", lt=True) == [2]
assert await r.hexists("test:hash", "field1") is False


@skip_if_server_version_lt("7.3.240")
async def test_hpexpireat_nonexistent_key_or_field(r):
await r.delete("test:hash")
future_exp_time = int(
(datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000
)
assert await r.hpexpireat("test:hash", future_exp_time, "field1") == []
await r.hset("test:hash", "field1", "value1")
assert await r.hpexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2]


@skip_if_server_version_lt("7.3.240")
async def test_hpexpireat_multiple_fields(r):
await r.delete("test:hash")
await r.hset(
"test:hash",
mapping={"field1": "value1", "field2": "value2", "field3": "value3"},
)
exp_time = int((datetime.now() + timedelta(milliseconds=400)).timestamp() * 1000)
assert await r.hpexpireat("test:hash", exp_time, "field1", "field2") == [1, 1]
await asyncio.sleep(0.5)
assert await r.hexists("test:hash", "field1") is False
assert await r.hexists("test:hash", "field2") is False
assert await r.hexists("test:hash", "field3") is True


@skip_if_server_version_lt("7.3.240")
async def test_hpersist_multiple_fields_mixed_conditions(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
await r.hexpire("test:hash", 5000, "field1")
assert await r.hpersist("test:hash", "field1", "field2", "field3") == [1, -1, -2]


@skip_if_server_version_lt("7.3.240")
async def test_hexpiretime_multiple_fields_mixed_conditions(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
future_time = int((datetime.now() + timedelta(minutes=30)).timestamp())
await r.hexpireat("test:hash", future_time, "field1")
result = await r.hexpiretime("test:hash", "field1", "field2", "field3")
assert future_time - 10 < result[0] <= future_time
assert result[1:] == [-1, -2]


@skip_if_server_version_lt("7.3.240")
async def test_hpexpiretime_multiple_fields_mixed_conditions(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
future_time = int((datetime.now() + timedelta(minutes=30)).timestamp())
await r.hexpireat("test:hash", future_time, "field1")
result = await r.hpexpiretime("test:hash", "field1", "field2", "field3")
assert future_time * 1000 - 10000 < result[0] <= future_time * 1000
assert result[1:] == [-1, -2]


@skip_if_server_version_lt("7.3.240")
async def test_ttl_multiple_fields_mixed_conditions(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
future_time = int((datetime.now() + timedelta(minutes=30)).timestamp())
await r.hexpireat("test:hash", future_time, "field1")
result = await r.httl("test:hash", "field1", "field2", "field3")
assert 30 * 60 - 10 < result[0] <= 30 * 60
assert result[1:] == [-1, -2]


@skip_if_server_version_lt("7.3.240")
async def test_pttl_multiple_fields_mixed_conditions(r):
await r.delete("test:hash")
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
future_time = int((datetime.now() + timedelta(minutes=30)).timestamp())
await r.hexpireat("test:hash", future_time, "field1")
result = await r.hpttl("test:hash", "field1", "field2", "field3")
assert 30 * 60000 - 10000 < result[0] <= 30 * 60000
assert result[1:] == [-1, -2]
Loading