Skip to content

Commit 659da06

Browse files
authored
Merge branch 'master' into issue/3139
2 parents 87484ad + cd92428 commit 659da06

File tree

4 files changed

+97
-7
lines changed

4 files changed

+97
-7
lines changed

redis/_parsers/helpers.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,12 @@ def parse_scan(response, **options):
354354

355355
def parse_hscan(response, **options):
356356
cursor, r = response
357-
return int(cursor), r and pairs_to_dict(r) or {}
357+
no_values = options.get("no_values", False)
358+
if no_values:
359+
payload = r or []
360+
else:
361+
payload = r and pairs_to_dict(r) or {}
362+
return int(cursor), payload
358363

359364

360365
def parse_zscan(response, **options):

redis/commands/core.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ def client_kill_filter(
459459
skipme: Union[bool, None] = None,
460460
laddr: Union[bool, None] = None,
461461
user: str = None,
462+
maxage: Union[int, None] = None,
462463
**kwargs,
463464
) -> ResponseT:
464465
"""
@@ -472,6 +473,7 @@ def client_kill_filter(
472473
options. If skipme is not provided, the server defaults to skipme=True
473474
:param laddr: Kills a client by its 'local (bind) address:port'
474475
:param user: Kills a client for a specific user name
476+
:param maxage: Kills clients that are older than the specified age in seconds
475477
"""
476478
args = []
477479
if _type is not None:
@@ -494,6 +496,8 @@ def client_kill_filter(
494496
args.extend((b"LADDR", laddr))
495497
if user is not None:
496498
args.extend((b"USER", user))
499+
if maxage is not None:
500+
args.extend((b"MAXAGE", maxage))
497501
if not args:
498502
raise DataError(
499503
"CLIENT KILL <filter> <value> ... ... <filter> "
@@ -3102,6 +3106,7 @@ def hscan(
31023106
cursor: int = 0,
31033107
match: Union[PatternT, None] = None,
31043108
count: Union[int, None] = None,
3109+
no_values: Union[bool, None] = None,
31053110
) -> ResponseT:
31063111
"""
31073112
Incrementally return key/value slices in a hash. Also return a cursor
@@ -3111,20 +3116,25 @@ def hscan(
31113116
31123117
``count`` allows for hint the minimum number of returns
31133118
3119+
``no_values`` indicates to return only the keys, without values.
3120+
31143121
For more information see https://redis.io/commands/hscan
31153122
"""
31163123
pieces: list[EncodableT] = [name, cursor]
31173124
if match is not None:
31183125
pieces.extend([b"MATCH", match])
31193126
if count is not None:
31203127
pieces.extend([b"COUNT", count])
3121-
return self.execute_command("HSCAN", *pieces)
3128+
if no_values is not None:
3129+
pieces.extend([b"NOVALUES"])
3130+
return self.execute_command("HSCAN", *pieces, no_values=no_values)
31223131

31233132
def hscan_iter(
31243133
self,
31253134
name: str,
31263135
match: Union[PatternT, None] = None,
31273136
count: Union[int, None] = None,
3137+
no_values: Union[bool, None] = None,
31283138
) -> Iterator:
31293139
"""
31303140
Make an iterator using the HSCAN command so that the client doesn't
@@ -3133,11 +3143,18 @@ def hscan_iter(
31333143
``match`` allows for filtering the keys by pattern
31343144
31353145
``count`` allows for hint the minimum number of returns
3146+
3147+
``no_values`` indicates to return only the keys, without values
31363148
"""
31373149
cursor = "0"
31383150
while cursor != 0:
3139-
cursor, data = self.hscan(name, cursor=cursor, match=match, count=count)
3140-
yield from data.items()
3151+
cursor, data = self.hscan(
3152+
name, cursor=cursor, match=match, count=count, no_values=no_values
3153+
)
3154+
if no_values:
3155+
yield from data
3156+
else:
3157+
yield from data.items()
31413158

31423159
def zscan(
31433160
self,
@@ -3253,6 +3270,7 @@ async def hscan_iter(
32533270
name: str,
32543271
match: Union[PatternT, None] = None,
32553272
count: Union[int, None] = None,
3273+
no_values: Union[bool, None] = None,
32563274
) -> AsyncIterator:
32573275
"""
32583276
Make an iterator using the HSCAN command so that the client doesn't
@@ -3261,14 +3279,20 @@ async def hscan_iter(
32613279
``match`` allows for filtering the keys by pattern
32623280
32633281
``count`` allows for hint the minimum number of returns
3282+
3283+
``no_values`` indicates to return only the keys, without values
32643284
"""
32653285
cursor = "0"
32663286
while cursor != 0:
32673287
cursor, data = await self.hscan(
3268-
name, cursor=cursor, match=match, count=count
3288+
name, cursor=cursor, match=match, count=count, no_values=no_values
32693289
)
3270-
for it in data.items():
3271-
yield it
3290+
if no_values:
3291+
for it in data:
3292+
yield it
3293+
else:
3294+
for it in data.items():
3295+
yield it
32723296

32733297
async def zscan_iter(
32743298
self,

tests/test_asyncio/test_commands.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,6 +1349,19 @@ async def test_hscan(self, r: redis.Redis):
13491349
assert dic == {b"a": b"1", b"b": b"2", b"c": b"3"}
13501350
_, dic = await r.hscan("a", match="a")
13511351
assert dic == {b"a": b"1"}
1352+
_, dic = await r.hscan("a_notset", match="a")
1353+
assert dic == {}
1354+
1355+
@skip_if_server_version_lt("7.4.0")
1356+
async def test_hscan_novalues(self, r: redis.Redis):
1357+
await r.hset("a", mapping={"a": 1, "b": 2, "c": 3})
1358+
cursor, keys = await r.hscan("a", no_values=True)
1359+
assert cursor == 0
1360+
assert sorted(keys) == [b"a", b"b", b"c"]
1361+
_, keys = await r.hscan("a", match="a", no_values=True)
1362+
assert keys == [b"a"]
1363+
_, keys = await r.hscan("a_notset", match="a", no_values=True)
1364+
assert keys == []
13521365

13531366
@skip_if_server_version_lt("2.8.0")
13541367
async def test_hscan_iter(self, r: redis.Redis):
@@ -1357,6 +1370,20 @@ async def test_hscan_iter(self, r: redis.Redis):
13571370
assert dic == {b"a": b"1", b"b": b"2", b"c": b"3"}
13581371
dic = {k: v async for k, v in r.hscan_iter("a", match="a")}
13591372
assert dic == {b"a": b"1"}
1373+
dic = {k: v async for k, v in r.hscan_iter("a_notset", match="a")}
1374+
assert dic == {}
1375+
1376+
@skip_if_server_version_lt("7.4.0")
1377+
async def test_hscan_iter_novalues(self, r: redis.Redis):
1378+
await r.hset("a", mapping={"a": 1, "b": 2, "c": 3})
1379+
keys = list([k async for k in r.hscan_iter("a", no_values=True)])
1380+
assert sorted(keys) == [b"a", b"b", b"c"]
1381+
keys = list([k async for k in r.hscan_iter("a", match="a", no_values=True)])
1382+
assert keys == [b"a"]
1383+
keys = list(
1384+
[k async for k in r.hscan_iter("a", match="a_notset", no_values=True)]
1385+
)
1386+
assert keys == []
13601387

13611388
@skip_if_server_version_lt("2.8.0")
13621389
async def test_zscan(self, r: redis.Redis):

tests/test_commands.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,15 @@ def test_client_kill_filter_by_user(self, r, request):
707707
assert c["user"] != killuser
708708
r.acl_deluser(killuser)
709709

710+
@skip_if_server_version_lt("7.4.0")
711+
@skip_if_redis_enterprise()
712+
def test_client_kill_filter_by_maxage(self, r, request):
713+
_get_client(redis.Redis, request, flushdb=False)
714+
time.sleep(4)
715+
assert len(r.client_list()) == 2
716+
r.client_kill_filter(maxage=2)
717+
assert len(r.client_list()) == 1
718+
710719
@pytest.mark.onlynoncluster
711720
@skip_if_server_version_lt("2.9.50")
712721
@skip_if_redis_enterprise()
@@ -2162,6 +2171,19 @@ def test_hscan(self, r):
21622171
assert dic == {b"a": b"1", b"b": b"2", b"c": b"3"}
21632172
_, dic = r.hscan("a", match="a")
21642173
assert dic == {b"a": b"1"}
2174+
_, dic = r.hscan("a_notset")
2175+
assert dic == {}
2176+
2177+
@skip_if_server_version_lt("7.4.0")
2178+
def test_hscan_novalues(self, r):
2179+
r.hset("a", mapping={"a": 1, "b": 2, "c": 3})
2180+
cursor, keys = r.hscan("a", no_values=True)
2181+
assert cursor == 0
2182+
assert sorted(keys) == [b"a", b"b", b"c"]
2183+
_, keys = r.hscan("a", match="a", no_values=True)
2184+
assert keys == [b"a"]
2185+
_, keys = r.hscan("a_notset", no_values=True)
2186+
assert keys == []
21652187

21662188
@skip_if_server_version_lt("2.8.0")
21672189
def test_hscan_iter(self, r):
@@ -2170,6 +2192,18 @@ def test_hscan_iter(self, r):
21702192
assert dic == {b"a": b"1", b"b": b"2", b"c": b"3"}
21712193
dic = dict(r.hscan_iter("a", match="a"))
21722194
assert dic == {b"a": b"1"}
2195+
dic = dict(r.hscan_iter("a_notset"))
2196+
assert dic == {}
2197+
2198+
@skip_if_server_version_lt("7.4.0")
2199+
def test_hscan_iter_novalues(self, r):
2200+
r.hset("a", mapping={"a": 1, "b": 2, "c": 3})
2201+
keys = list(r.hscan_iter("a", no_values=True))
2202+
assert keys == [b"a", b"b", b"c"]
2203+
keys = list(r.hscan_iter("a", match="a", no_values=True))
2204+
assert keys == [b"a"]
2205+
keys = list(r.hscan_iter("a_notset", no_values=True))
2206+
assert keys == []
21732207

21742208
@skip_if_server_version_lt("2.8.0")
21752209
def test_zscan(self, r):

0 commit comments

Comments
 (0)