Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 5.1.0b6 #20

Merged
merged 10 commits into from
Jun 14, 2024
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
Loading