Skip to content

Commit a1053d7

Browse files
authored
Merge pull request #20 from valkey-io/aiven-sal/changes_for_b6
Version 5.1.0b6
2 parents 16192e5 + 9e29e5e commit a1053d7

27 files changed

+1142
-76
lines changed

CHANGES

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
* Update `ResponseT` type hint
12
* Allow to control the minimum SSL version
23
* Add an optional lock_name attribute to LockError.
34
* Fix return types for `get`, `set_path` and `strappend` in JSONCommands

setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
long_description_content_type="text/markdown",
99
keywords=["Valkey", "key-value store", "database"],
1010
license="MIT",
11-
version="5.1.0b5",
11+
version="5.1.0b6",
1212
packages=find_packages(
1313
include=[
1414
"valkey",
@@ -33,7 +33,7 @@
3333
"Issue tracker": "https://github.com/valkey-io/valkey-py/issues",
3434
},
3535
author="valkey-py authors",
36-
author_email="placeholder@valkey.io",
36+
author_email="valkey-py@lists.valkey.io",
3737
python_requires=">=3.8",
3838
install_requires=[
3939
'async-timeout>=4.0.3; python_full_version<"3.11.3"',

tests/ssl_utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ def get_ssl_filename(name):
99
os.path.join(root, "..", "dockers", "stunnel", "keys")
1010
)
1111
if not os.path.isdir(cert_dir):
12-
raise IOError(f"No SSL certificates found. They should be in {cert_dir}")
12+
raise OSError(f"No SSL certificates found. They should be in {cert_dir}")
1313

1414
return os.path.join(cert_dir, name)

tests/test_asyncio/compat.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
try:
55
mock.AsyncMock
66
except AttributeError:
7-
import mock
7+
from unittest import mock
88

99
try:
1010
from contextlib import aclosing

tests/test_asyncio/test_cluster.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1430,7 +1430,7 @@ async def test_memory_stats(self, r: ValkeyCluster) -> None:
14301430
assert isinstance(stats, dict)
14311431
for key, value in stats.items():
14321432
if key.startswith("db."):
1433-
assert isinstance(value, dict)
1433+
assert not isinstance(value, list)
14341434

14351435
@skip_if_server_version_lt("4.0.0")
14361436
async def test_memory_help(self, r: ValkeyCluster) -> None:

tests/test_asyncio/test_commands.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1352,7 +1352,7 @@ async def test_hscan(self, r: valkey.Valkey):
13521352
_, dic = await r.hscan("a_notset", match="a")
13531353
assert dic == {}
13541354

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

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

32403240
@skip_if_server_version_lt("4.0.0")
32413241
async def test_memory_usage(self, r: valkey.Valkey):

tests/test_asyncio/test_hash.py

+300
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
import asyncio
2+
from datetime import datetime, timedelta
3+
4+
from tests.conftest import skip_if_server_version_lt
5+
6+
7+
@skip_if_server_version_lt("7.3.240")
8+
async def test_hexpire_basic(r):
9+
await r.delete("test:hash")
10+
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
11+
assert await r.hexpire("test:hash", 1, "field1") == [1]
12+
await asyncio.sleep(1.1)
13+
assert await r.hexists("test:hash", "field1") is False
14+
assert await r.hexists("test:hash", "field2") is True
15+
16+
17+
@skip_if_server_version_lt("7.3.240")
18+
async def test_hexpire_with_timedelta(r):
19+
await r.delete("test:hash")
20+
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
21+
assert await r.hexpire("test:hash", timedelta(seconds=1), "field1") == [1]
22+
await asyncio.sleep(1.1)
23+
assert await r.hexists("test:hash", "field1") is False
24+
assert await r.hexists("test:hash", "field2") is True
25+
26+
27+
@skip_if_server_version_lt("7.3.240")
28+
async def test_hexpire_conditions(r):
29+
await r.delete("test:hash")
30+
await r.hset("test:hash", mapping={"field1": "value1"})
31+
assert await r.hexpire("test:hash", 2, "field1", xx=True) == [0]
32+
assert await r.hexpire("test:hash", 2, "field1", nx=True) == [1]
33+
assert await r.hexpire("test:hash", 1, "field1", xx=True) == [1]
34+
assert await r.hexpire("test:hash", 2, "field1", nx=True) == [0]
35+
await asyncio.sleep(1.1)
36+
assert await r.hexists("test:hash", "field1") is False
37+
await r.hset("test:hash", "field1", "value1")
38+
await r.hexpire("test:hash", 2, "field1")
39+
assert await r.hexpire("test:hash", 1, "field1", gt=True) == [0]
40+
assert await r.hexpire("test:hash", 1, "field1", lt=True) == [1]
41+
await asyncio.sleep(1.1)
42+
assert await r.hexists("test:hash", "field1") is False
43+
44+
45+
@skip_if_server_version_lt("7.3.240")
46+
async def test_hexpire_nonexistent_key_or_field(r):
47+
await r.delete("test:hash")
48+
assert await r.hexpire("test:hash", 1, "field1") == []
49+
await r.hset("test:hash", "field1", "value1")
50+
assert await r.hexpire("test:hash", 1, "nonexistent_field") == [-2]
51+
52+
53+
@skip_if_server_version_lt("7.3.240")
54+
async def test_hexpire_multiple_fields(r):
55+
await r.delete("test:hash")
56+
await r.hset(
57+
"test:hash",
58+
mapping={"field1": "value1", "field2": "value2", "field3": "value3"},
59+
)
60+
assert await r.hexpire("test:hash", 1, "field1", "field2") == [1, 1]
61+
await asyncio.sleep(1.1)
62+
assert await r.hexists("test:hash", "field1") is False
63+
assert await r.hexists("test:hash", "field2") is False
64+
assert await r.hexists("test:hash", "field3") is True
65+
66+
67+
@skip_if_server_version_lt("7.3.240")
68+
async def test_hpexpire_basic(r):
69+
await r.delete("test:hash")
70+
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
71+
assert await r.hpexpire("test:hash", 500, "field1") == [1]
72+
await asyncio.sleep(0.6)
73+
assert await r.hexists("test:hash", "field1") is False
74+
assert await r.hexists("test:hash", "field2") is True
75+
76+
77+
@skip_if_server_version_lt("7.3.240")
78+
async def test_hpexpire_with_timedelta(r):
79+
await r.delete("test:hash")
80+
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
81+
assert await r.hpexpire("test:hash", timedelta(milliseconds=500), "field1") == [1]
82+
await asyncio.sleep(0.6)
83+
assert await r.hexists("test:hash", "field1") is False
84+
assert await r.hexists("test:hash", "field2") is True
85+
86+
87+
@skip_if_server_version_lt("7.3.240")
88+
async def test_hpexpire_conditions(r):
89+
await r.delete("test:hash")
90+
await r.hset("test:hash", mapping={"field1": "value1"})
91+
assert await r.hpexpire("test:hash", 1500, "field1", xx=True) == [0]
92+
assert await r.hpexpire("test:hash", 1500, "field1", nx=True) == [1]
93+
assert await r.hpexpire("test:hash", 500, "field1", xx=True) == [1]
94+
assert await r.hpexpire("test:hash", 1500, "field1", nx=True) == [0]
95+
await asyncio.sleep(0.6)
96+
assert await r.hexists("test:hash", "field1") is False
97+
await r.hset("test:hash", "field1", "value1")
98+
await r.hpexpire("test:hash", 1000, "field1")
99+
assert await r.hpexpire("test:hash", 500, "field1", gt=True) == [0]
100+
assert await r.hpexpire("test:hash", 500, "field1", lt=True) == [1]
101+
await asyncio.sleep(0.6)
102+
assert await r.hexists("test:hash", "field1") is False
103+
104+
105+
@skip_if_server_version_lt("7.3.240")
106+
async def test_hpexpire_nonexistent_key_or_field(r):
107+
await r.delete("test:hash")
108+
assert await r.hpexpire("test:hash", 500, "field1") == []
109+
await r.hset("test:hash", "field1", "value1")
110+
assert await r.hpexpire("test:hash", 500, "nonexistent_field") == [-2]
111+
112+
113+
@skip_if_server_version_lt("7.3.240")
114+
async def test_hpexpire_multiple_fields(r):
115+
await r.delete("test:hash")
116+
await r.hset(
117+
"test:hash",
118+
mapping={"field1": "value1", "field2": "value2", "field3": "value3"},
119+
)
120+
assert await r.hpexpire("test:hash", 500, "field1", "field2") == [1, 1]
121+
await asyncio.sleep(0.6)
122+
assert await r.hexists("test:hash", "field1") is False
123+
assert await r.hexists("test:hash", "field2") is False
124+
assert await r.hexists("test:hash", "field3") is True
125+
126+
127+
@skip_if_server_version_lt("7.3.240")
128+
async def test_hexpireat_basic(r):
129+
await r.delete("test:hash")
130+
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
131+
exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
132+
assert await r.hexpireat("test:hash", exp_time, "field1") == [1]
133+
await asyncio.sleep(1.1)
134+
assert await r.hexists("test:hash", "field1") is False
135+
assert await r.hexists("test:hash", "field2") is True
136+
137+
138+
@skip_if_server_version_lt("7.3.240")
139+
async def test_hexpireat_with_datetime(r):
140+
await r.delete("test:hash")
141+
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
142+
exp_time = datetime.now() + timedelta(seconds=1)
143+
assert await r.hexpireat("test:hash", exp_time, "field1") == [1]
144+
await asyncio.sleep(1.1)
145+
assert await r.hexists("test:hash", "field1") is False
146+
assert await r.hexists("test:hash", "field2") is True
147+
148+
149+
@skip_if_server_version_lt("7.3.240")
150+
async def test_hexpireat_conditions(r):
151+
await r.delete("test:hash")
152+
await r.hset("test:hash", mapping={"field1": "value1"})
153+
future_exp_time = int((datetime.now() + timedelta(seconds=2)).timestamp())
154+
past_exp_time = int((datetime.now() - timedelta(seconds=1)).timestamp())
155+
assert await r.hexpireat("test:hash", future_exp_time, "field1", xx=True) == [0]
156+
assert await r.hexpireat("test:hash", future_exp_time, "field1", nx=True) == [1]
157+
assert await r.hexpireat("test:hash", past_exp_time, "field1", gt=True) == [0]
158+
assert await r.hexpireat("test:hash", past_exp_time, "field1", lt=True) == [2]
159+
assert await r.hexists("test:hash", "field1") is False
160+
161+
162+
@skip_if_server_version_lt("7.3.240")
163+
async def test_hexpireat_nonexistent_key_or_field(r):
164+
await r.delete("test:hash")
165+
future_exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
166+
assert await r.hexpireat("test:hash", future_exp_time, "field1") == []
167+
await r.hset("test:hash", "field1", "value1")
168+
assert await r.hexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2]
169+
170+
171+
@skip_if_server_version_lt("7.3.240")
172+
async def test_hexpireat_multiple_fields(r):
173+
await r.delete("test:hash")
174+
await r.hset(
175+
"test:hash",
176+
mapping={"field1": "value1", "field2": "value2", "field3": "value3"},
177+
)
178+
exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
179+
assert await r.hexpireat("test:hash", exp_time, "field1", "field2") == [1, 1]
180+
await asyncio.sleep(1.1)
181+
assert await r.hexists("test:hash", "field1") is False
182+
assert await r.hexists("test:hash", "field2") is False
183+
assert await r.hexists("test:hash", "field3") is True
184+
185+
186+
@skip_if_server_version_lt("7.3.240")
187+
async def test_hpexpireat_basic(r):
188+
await r.delete("test:hash")
189+
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
190+
exp_time = int((datetime.now() + timedelta(milliseconds=400)).timestamp() * 1000)
191+
assert await r.hpexpireat("test:hash", exp_time, "field1") == [1]
192+
await asyncio.sleep(0.5)
193+
assert await r.hexists("test:hash", "field1") is False
194+
assert await r.hexists("test:hash", "field2") is True
195+
196+
197+
@skip_if_server_version_lt("7.3.240")
198+
async def test_hpexpireat_with_datetime(r):
199+
await r.delete("test:hash")
200+
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
201+
exp_time = datetime.now() + timedelta(milliseconds=400)
202+
assert await r.hpexpireat("test:hash", exp_time, "field1") == [1]
203+
await asyncio.sleep(0.5)
204+
assert await r.hexists("test:hash", "field1") is False
205+
assert await r.hexists("test:hash", "field2") is True
206+
207+
208+
@skip_if_server_version_lt("7.3.240")
209+
async def test_hpexpireat_conditions(r):
210+
await r.delete("test:hash")
211+
await r.hset("test:hash", mapping={"field1": "value1"})
212+
future_exp_time = int(
213+
(datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000
214+
)
215+
past_exp_time = int(
216+
(datetime.now() - timedelta(milliseconds=500)).timestamp() * 1000
217+
)
218+
assert await r.hpexpireat("test:hash", future_exp_time, "field1", xx=True) == [0]
219+
assert await r.hpexpireat("test:hash", future_exp_time, "field1", nx=True) == [1]
220+
assert await r.hpexpireat("test:hash", past_exp_time, "field1", gt=True) == [0]
221+
assert await r.hpexpireat("test:hash", past_exp_time, "field1", lt=True) == [2]
222+
assert await r.hexists("test:hash", "field1") is False
223+
224+
225+
@skip_if_server_version_lt("7.3.240")
226+
async def test_hpexpireat_nonexistent_key_or_field(r):
227+
await r.delete("test:hash")
228+
future_exp_time = int(
229+
(datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000
230+
)
231+
assert await r.hpexpireat("test:hash", future_exp_time, "field1") == []
232+
await r.hset("test:hash", "field1", "value1")
233+
assert await r.hpexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2]
234+
235+
236+
@skip_if_server_version_lt("7.3.240")
237+
async def test_hpexpireat_multiple_fields(r):
238+
await r.delete("test:hash")
239+
await r.hset(
240+
"test:hash",
241+
mapping={"field1": "value1", "field2": "value2", "field3": "value3"},
242+
)
243+
exp_time = int((datetime.now() + timedelta(milliseconds=400)).timestamp() * 1000)
244+
assert await r.hpexpireat("test:hash", exp_time, "field1", "field2") == [1, 1]
245+
await asyncio.sleep(0.5)
246+
assert await r.hexists("test:hash", "field1") is False
247+
assert await r.hexists("test:hash", "field2") is False
248+
assert await r.hexists("test:hash", "field3") is True
249+
250+
251+
@skip_if_server_version_lt("7.3.240")
252+
async def test_hpersist_multiple_fields_mixed_conditions(r):
253+
await r.delete("test:hash")
254+
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
255+
await r.hexpire("test:hash", 5000, "field1")
256+
assert await r.hpersist("test:hash", "field1", "field2", "field3") == [1, -1, -2]
257+
258+
259+
@skip_if_server_version_lt("7.3.240")
260+
async def test_hexpiretime_multiple_fields_mixed_conditions(r):
261+
await r.delete("test:hash")
262+
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
263+
future_time = int((datetime.now() + timedelta(minutes=30)).timestamp())
264+
await r.hexpireat("test:hash", future_time, "field1")
265+
result = await r.hexpiretime("test:hash", "field1", "field2", "field3")
266+
assert future_time - 10 < result[0] <= future_time
267+
assert result[1:] == [-1, -2]
268+
269+
270+
@skip_if_server_version_lt("7.3.240")
271+
async def test_hpexpiretime_multiple_fields_mixed_conditions(r):
272+
await r.delete("test:hash")
273+
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
274+
future_time = int((datetime.now() + timedelta(minutes=30)).timestamp())
275+
await r.hexpireat("test:hash", future_time, "field1")
276+
result = await r.hpexpiretime("test:hash", "field1", "field2", "field3")
277+
assert future_time * 1000 - 10000 < result[0] <= future_time * 1000
278+
assert result[1:] == [-1, -2]
279+
280+
281+
@skip_if_server_version_lt("7.3.240")
282+
async def test_ttl_multiple_fields_mixed_conditions(r):
283+
await r.delete("test:hash")
284+
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
285+
future_time = int((datetime.now() + timedelta(minutes=30)).timestamp())
286+
await r.hexpireat("test:hash", future_time, "field1")
287+
result = await r.httl("test:hash", "field1", "field2", "field3")
288+
assert 30 * 60 - 10 < result[0] <= 30 * 60
289+
assert result[1:] == [-1, -2]
290+
291+
292+
@skip_if_server_version_lt("7.3.240")
293+
async def test_pttl_multiple_fields_mixed_conditions(r):
294+
await r.delete("test:hash")
295+
await r.hset("test:hash", mapping={"field1": "value1", "field2": "value2"})
296+
future_time = int((datetime.now() + timedelta(minutes=30)).timestamp())
297+
await r.hexpireat("test:hash", future_time, "field1")
298+
result = await r.hpttl("test:hash", "field1", "field2", "field3")
299+
assert 30 * 60000 - 10000 < result[0] <= 30 * 60000
300+
assert result[1:] == [-1, -2]

0 commit comments

Comments
 (0)