Skip to content

Commit 7beaaea

Browse files
committed
Support NOVALUES parameter for HSCAN
Issue #3153 The NOVALUES parameter instructs HSCAN to only return the hash keys, without values.
1 parent 037d108 commit 7beaaea

File tree

3 files changed

+52
-5
lines changed

3 files changed

+52
-5
lines changed

redis/_parsers/helpers.py

+6-1
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

+21-4
Original file line numberDiff line numberDiff line change
@@ -3102,6 +3102,7 @@ def hscan(
31023102
cursor: int = 0,
31033103
match: Union[PatternT, None] = None,
31043104
count: Union[int, None] = None,
3105+
no_values: Union[bool, None] = None,
31053106
) -> ResponseT:
31063107
"""
31073108
Incrementally return key/value slices in a hash. Also return a cursor
@@ -3111,20 +3112,26 @@ def hscan(
31113112
31123113
``count`` allows for hint the minimum number of returns
31133114
3115+
``no_values`` indicates to return only the keys, without values. The
3116+
return type in this case is a list of keys.
3117+
31143118
For more information see https://redis.io/commands/hscan
31153119
"""
31163120
pieces: list[EncodableT] = [name, cursor]
31173121
if match is not None:
31183122
pieces.extend([b"MATCH", match])
31193123
if count is not None:
31203124
pieces.extend([b"COUNT", count])
3121-
return self.execute_command("HSCAN", *pieces)
3125+
if no_values is not None:
3126+
pieces.extend([b"NOVALUES"])
3127+
return self.execute_command("HSCAN", *pieces, no_values=no_values)
31223128

31233129
def hscan_iter(
31243130
self,
31253131
name: str,
31263132
match: Union[PatternT, None] = None,
31273133
count: Union[int, None] = None,
3134+
no_values: Union[bool, None] = None,
31283135
) -> Iterator:
31293136
"""
31303137
Make an iterator using the HSCAN command so that the client doesn't
@@ -3133,11 +3140,18 @@ def hscan_iter(
31333140
``match`` allows for filtering the keys by pattern
31343141
31353142
``count`` allows for hint the minimum number of returns
3143+
3144+
``no_values`` indicates to return only the keys, without values
31363145
"""
31373146
cursor = "0"
31383147
while cursor != 0:
3139-
cursor, data = self.hscan(name, cursor=cursor, match=match, count=count)
3140-
yield from data.items()
3148+
cursor, data = self.hscan(
3149+
name, cursor=cursor, match=match, count=count, no_values=no_values
3150+
)
3151+
if no_values:
3152+
yield from data
3153+
else:
3154+
yield from data.items()
31413155

31423156
def zscan(
31433157
self,
@@ -3253,6 +3267,7 @@ async def hscan_iter(
32533267
name: str,
32543268
match: Union[PatternT, None] = None,
32553269
count: Union[int, None] = None,
3270+
no_values: Union[bool, None] = None,
32563271
) -> AsyncIterator:
32573272
"""
32583273
Make an iterator using the HSCAN command so that the client doesn't
@@ -3261,11 +3276,13 @@ async def hscan_iter(
32613276
``match`` allows for filtering the keys by pattern
32623277
32633278
``count`` allows for hint the minimum number of returns
3279+
3280+
``no_values`` indicates to return only the keys, without values
32643281
"""
32653282
cursor = "0"
32663283
while cursor != 0:
32673284
cursor, data = await self.hscan(
3268-
name, cursor=cursor, match=match, count=count
3285+
name, cursor=cursor, match=match, count=count, no_values=no_values
32693286
)
32703287
for it in data.items():
32713288
yield it

tests/test_commands.py

+25
Original file line numberDiff line numberDiff line change
@@ -2162,6 +2162,19 @@ def test_hscan(self, r):
21622162
assert dic == {b"a": b"1", b"b": b"2", b"c": b"3"}
21632163
_, dic = r.hscan("a", match="a")
21642164
assert dic == {b"a": b"1"}
2165+
_, dic = r.hscan("a_notset")
2166+
assert dic == {}
2167+
2168+
@skip_if_server_version_lt("7.4.0")
2169+
def test_hscan_novalues(self, r):
2170+
r.hset("a", mapping={"a": 1, "b": 2, "c": 3})
2171+
cursor, keys = r.hscan("a", no_values=True)
2172+
assert cursor == 0
2173+
assert keys == [b"a", b"b", b"c"]
2174+
_, keys = r.hscan("a", match="a", no_values=True)
2175+
assert keys == [b"a"]
2176+
_, keys = r.hscan("a_notset", no_values=True)
2177+
assert keys == []
21652178

21662179
@skip_if_server_version_lt("2.8.0")
21672180
def test_hscan_iter(self, r):
@@ -2170,6 +2183,18 @@ def test_hscan_iter(self, r):
21702183
assert dic == {b"a": b"1", b"b": b"2", b"c": b"3"}
21712184
dic = dict(r.hscan_iter("a", match="a"))
21722185
assert dic == {b"a": b"1"}
2186+
dic = dict(r.hscan_iter("a_notset"))
2187+
assert dic == {}
2188+
2189+
@skip_if_server_version_lt("7.4.0")
2190+
def test_hscan_iter_novalues(self, r):
2191+
r.hset("a", mapping={"a": 1, "b": 2, "c": 3})
2192+
keys = list(r.hscan_iter("a", no_values=True))
2193+
assert keys == [b"a", b"b", b"c"]
2194+
keys = list(r.hscan_iter("a", match="a", no_values=True))
2195+
assert keys == [b"a"]
2196+
keys = list(r.hscan_iter("a_notset", no_values=True))
2197+
assert keys == []
21732198

21742199
@skip_if_server_version_lt("2.8.0")
21752200
def test_zscan(self, r):

0 commit comments

Comments
 (0)