Skip to content

Commit 40a9092

Browse files
authored
Handle lists in the response of INFO (#3277)
Parse lists in the response of INFO, and even lines where list items are mixed with key=value items, in which case the overall structure will be a dict, and the items without value get `True` as their value. Do maintenance stuff around the latest Redis Stack release. The Graph module is no longer part of Redis Stack. Skip the tests, to not break the CI. Backport a couple of fixes done on the master branch. Avoid workflows canceling each other out.
1 parent 6f55c02 commit 40a9092

18 files changed

+137
-13
lines changed

.github/workflows/docs.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ on:
1313
- cron: '0 1 * * *' # nightly build
1414

1515
concurrency:
16-
group: ${{ github.event.pull_request.number || github.ref }}
16+
group: ${{ github.event.pull_request.number || github.ref }}-docs
1717
cancel-in-progress: true
1818

1919
permissions:

.github/workflows/integration.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ on:
1717
- cron: '0 1 * * *' # nightly build
1818

1919
concurrency:
20-
group: ${{ github.event.pull_request.number || github.ref }}
20+
group: ${{ github.event.pull_request.number || github.ref }}-integration
2121
cancel-in-progress: true
2222

2323
permissions:

dockers/cluster.redis.conf

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
protected-mode no
22
enable-debug-command yes
33
loadmodule /opt/redis-stack/lib/redisearch.so
4-
loadmodule /opt/redis-stack/lib/redisgraph.so
54
loadmodule /opt/redis-stack/lib/redistimeseries.so
65
loadmodule /opt/redis-stack/lib/rejson.so
76
loadmodule /opt/redis-stack/lib/redisbloom.so

redis/_parsers/helpers.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,18 @@ def get_value(value):
4646
return int(value)
4747
except ValueError:
4848
return value
49+
elif "=" not in value:
50+
return [get_value(v) for v in value.split(",") if v]
4951
else:
5052
sub_dict = {}
5153
for item in value.split(","):
52-
k, v = item.rsplit("=", 1)
53-
sub_dict[k] = get_value(v)
54+
if not item:
55+
continue
56+
if "=" in item:
57+
k, v = item.rsplit("=", 1)
58+
sub_dict[k] = get_value(v)
59+
else:
60+
sub_dict[item] = True
5461
return sub_dict
5562

5663
for line in response.splitlines():
@@ -80,7 +87,7 @@ def parse_memory_stats(response, **kwargs):
8087
"""Parse the results of MEMORY STATS"""
8188
stats = pairs_to_dict(response, decode_keys=True, decode_string_values=True)
8289
for key, value in stats.items():
83-
if key.startswith("db."):
90+
if key.startswith("db.") and isinstance(value, list):
8491
stats[key] = pairs_to_dict(
8592
value, decode_keys=True, decode_string_values=True
8693
)

redis/commands/helpers.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,11 @@ def parse_to_dict(response):
8585

8686
res = {}
8787
for det in response:
88-
if isinstance(det[1], list):
88+
if not isinstance(det, list) or not det:
89+
continue
90+
if len(det) == 1:
91+
res[det[0]] = True
92+
elif isinstance(det[1], list):
8993
res[det[0]] = parse_list_to_dict(det[1])
9094
else:
9195
try: # try to set the attribute. may be provided without value

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
long_description_content_type="text/markdown",
99
keywords=["Redis", "key-value store", "database"],
1010
license="MIT",
11-
version="5.0.5",
11+
version="5.0.6",
1212
packages=find_packages(
1313
include=[
1414
"redis",

tasks.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
def devenv(c):
1414
"""Brings up the test environment, by wrapping docker compose."""
1515
clean(c)
16-
cmd = "docker-compose --profile all up -d"
16+
cmd = "docker-compose --profile all up -d --build"
1717
run(cmd)
1818

1919

tests/test_asyncio/test_cluster.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1445,7 +1445,7 @@ async def test_memory_stats(self, r: RedisCluster) -> None:
14451445
assert isinstance(stats, dict)
14461446
for key, value in stats.items():
14471447
if key.startswith("db."):
1448-
assert isinstance(value, dict)
1448+
assert not isinstance(value, list)
14491449

14501450
@skip_if_server_version_lt("4.0.0")
14511451
async def test_memory_help(self, r: RedisCluster) -> None:

tests/test_asyncio/test_commands.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3207,7 +3207,7 @@ async def test_memory_stats(self, r: redis.Redis):
32073207
assert isinstance(stats, dict)
32083208
for key, value in stats.items():
32093209
if key.startswith("db."):
3210-
assert isinstance(value, dict)
3210+
assert not isinstance(value, list)
32113211

32123212
@skip_if_server_version_lt("4.0.0")
32133213
async def test_memory_usage(self, r: redis.Redis):

tests/test_asyncio/test_graph.py

+38
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ async def test_bulk(decoded_r):
1212
await decoded_r.graph().bulk(foo="bar!")
1313

1414

15+
@pytest.mark.redismod
16+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
1517
async def test_graph_creation(decoded_r: redis.Redis):
1618
graph = decoded_r.graph()
1719

@@ -56,6 +58,8 @@ async def test_graph_creation(decoded_r: redis.Redis):
5658
await graph.delete()
5759

5860

61+
@pytest.mark.redismod
62+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
5963
async def test_array_functions(decoded_r: redis.Redis):
6064
graph = decoded_r.graph()
6165

@@ -78,6 +82,8 @@ async def test_array_functions(decoded_r: redis.Redis):
7882
assert [a] == result.result_set[0][0]
7983

8084

85+
@pytest.mark.redismod
86+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
8187
async def test_path(decoded_r: redis.Redis):
8288
node0 = Node(node_id=0, label="L1")
8389
node1 = Node(node_id=1, label="L1")
@@ -97,6 +103,8 @@ async def test_path(decoded_r: redis.Redis):
97103
assert expected_results == result.result_set
98104

99105

106+
@pytest.mark.redismod
107+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
100108
async def test_param(decoded_r: redis.Redis):
101109
params = [1, 2.3, "str", True, False, None, [0, 1, 2]]
102110
query = "RETURN $param"
@@ -106,6 +114,8 @@ async def test_param(decoded_r: redis.Redis):
106114
assert expected_results == result.result_set
107115

108116

117+
@pytest.mark.redismod
118+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
109119
async def test_map(decoded_r: redis.Redis):
110120
query = "RETURN {a:1, b:'str', c:NULL, d:[1,2,3], e:True, f:{x:1, y:2}}"
111121

@@ -122,6 +132,8 @@ async def test_map(decoded_r: redis.Redis):
122132
assert actual == expected
123133

124134

135+
@pytest.mark.redismod
136+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
125137
async def test_point(decoded_r: redis.Redis):
126138
query = "RETURN point({latitude: 32.070794860, longitude: 34.820751118})"
127139
expected_lat = 32.070794860
@@ -138,6 +150,8 @@ async def test_point(decoded_r: redis.Redis):
138150
assert abs(actual["longitude"] - expected_lon) < 0.001
139151

140152

153+
@pytest.mark.redismod
154+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
141155
async def test_index_response(decoded_r: redis.Redis):
142156
result_set = await decoded_r.graph().query("CREATE INDEX ON :person(age)")
143157
assert 1 == result_set.indices_created
@@ -152,6 +166,8 @@ async def test_index_response(decoded_r: redis.Redis):
152166
await decoded_r.graph().query("DROP INDEX ON :person(age)")
153167

154168

169+
@pytest.mark.redismod
170+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
155171
async def test_stringify_query_result(decoded_r: redis.Redis):
156172
graph = decoded_r.graph()
157173

@@ -205,6 +221,8 @@ async def test_stringify_query_result(decoded_r: redis.Redis):
205221
await graph.delete()
206222

207223

224+
@pytest.mark.redismod
225+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
208226
async def test_optional_match(decoded_r: redis.Redis):
209227
# Build a graph of form (a)-[R]->(b)
210228
node0 = Node(node_id=0, label="L1", properties={"value": "a"})
@@ -229,6 +247,8 @@ async def test_optional_match(decoded_r: redis.Redis):
229247
await graph.delete()
230248

231249

250+
@pytest.mark.redismod
251+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
232252
async def test_cached_execution(decoded_r: redis.Redis):
233253
await decoded_r.graph().query("CREATE ()")
234254

@@ -248,6 +268,8 @@ async def test_cached_execution(decoded_r: redis.Redis):
248268
assert cached_result.cached_execution
249269

250270

271+
@pytest.mark.redismod
272+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
251273
async def test_slowlog(decoded_r: redis.Redis):
252274
create_query = """CREATE
253275
(:Rider {name:'Valentino Rossi'})-[:rides]->(:Team {name:'Yamaha'}),
@@ -261,6 +283,8 @@ async def test_slowlog(decoded_r: redis.Redis):
261283

262284

263285
@pytest.mark.xfail(strict=False)
286+
@pytest.mark.redismod
287+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
264288
async def test_query_timeout(decoded_r: redis.Redis):
265289
# Build a sample graph with 1000 nodes.
266290
await decoded_r.graph().query("UNWIND range(0,1000) as val CREATE ({v: val})")
@@ -274,6 +298,8 @@ async def test_query_timeout(decoded_r: redis.Redis):
274298
assert False is False
275299

276300

301+
@pytest.mark.redismod
302+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
277303
async def test_read_only_query(decoded_r: redis.Redis):
278304
with pytest.raises(Exception):
279305
# Issue a write query, specifying read-only true,
@@ -282,6 +308,8 @@ async def test_read_only_query(decoded_r: redis.Redis):
282308
assert False is False
283309

284310

311+
@pytest.mark.redismod
312+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
285313
async def test_profile(decoded_r: redis.Redis):
286314
q = """UNWIND range(1, 3) AS x CREATE (p:Person {v:x})"""
287315
profile = (await decoded_r.graph().profile(q)).result_set
@@ -297,6 +325,8 @@ async def test_profile(decoded_r: redis.Redis):
297325

298326

299327
@skip_if_redis_enterprise()
328+
@pytest.mark.redismod
329+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
300330
async def test_config(decoded_r: redis.Redis):
301331
config_name = "RESULTSET_SIZE"
302332
config_value = 3
@@ -328,6 +358,8 @@ async def test_config(decoded_r: redis.Redis):
328358

329359

330360
@pytest.mark.onlynoncluster
361+
@pytest.mark.redismod
362+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
331363
async def test_list_keys(decoded_r: redis.Redis):
332364
result = await decoded_r.graph().list_keys()
333365
assert result == []
@@ -350,6 +382,8 @@ async def test_list_keys(decoded_r: redis.Redis):
350382
assert result == []
351383

352384

385+
@pytest.mark.redismod
386+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
353387
async def test_multi_label(decoded_r: redis.Redis):
354388
redis_graph = decoded_r.graph("g")
355389

@@ -375,6 +409,8 @@ async def test_multi_label(decoded_r: redis.Redis):
375409
assert True
376410

377411

412+
@pytest.mark.redismod
413+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
378414
async def test_execution_plan(decoded_r: redis.Redis):
379415
redis_graph = decoded_r.graph("execution_plan")
380416
create_query = """CREATE
@@ -393,6 +429,8 @@ async def test_execution_plan(decoded_r: redis.Redis):
393429
await redis_graph.delete()
394430

395431

432+
@pytest.mark.redismod
433+
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
396434
async def test_explain(decoded_r: redis.Redis):
397435
redis_graph = decoded_r.graph("execution_plan")
398436
# graph creation / population

tests/test_asyncio/test_json.py

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ async def test_jsonsetexistentialmodifiersshouldsucceed(decoded_r: redis.Redis):
9595
await decoded_r.json().set("obj", Path("foo"), "baz", nx=True, xx=True)
9696

9797

98+
@pytest.mark.onlynoncluster
9899
async def test_mgetshouldsucceed(decoded_r: redis.Redis):
99100
await decoded_r.json().set("1", Path.root_path(), 1)
100101
await decoded_r.json().set("2", Path.root_path(), 2)

tests/test_asyncio/test_timeseries.py

+1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ async def test_incrby_decrby(decoded_r: redis.Redis):
175175
assert_resp_response(decoded_r, 128, info.get("chunk_size"), info.get("chunkSize"))
176176

177177

178+
@pytest.mark.onlynoncluster
178179
async def test_create_and_delete_rule(decoded_r: redis.Redis):
179180
# test rule creation
180181
time = 100

tests/test_cluster.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1569,7 +1569,7 @@ def test_memory_stats(self, r):
15691569
assert isinstance(stats, dict)
15701570
for key, value in stats.items():
15711571
if key.startswith("db."):
1572-
assert isinstance(value, dict)
1572+
assert not isinstance(value, list)
15731573

15741574
@skip_if_server_version_lt("4.0.0")
15751575
def test_memory_help(self, r):

tests/test_commands.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4880,7 +4880,7 @@ def test_memory_stats(self, r):
48804880
assert isinstance(stats, dict)
48814881
for key, value in stats.items():
48824882
if key.startswith("db."):
4883-
assert isinstance(value, dict)
4883+
assert not isinstance(value, list)
48844884

48854885
@skip_if_server_version_lt("4.0.0")
48864886
def test_memory_usage(self, r):

0 commit comments

Comments
 (0)