Skip to content

Commit

Permalink
Handle lists in the response of INFO (#3277)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
gerzse authored Jun 13, 2024
1 parent 6f55c02 commit 40a9092
Show file tree
Hide file tree
Showing 18 changed files with 137 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
- cron: '0 1 * * *' # nightly build

concurrency:
group: ${{ github.event.pull_request.number || github.ref }}
group: ${{ github.event.pull_request.number || github.ref }}-docs
cancel-in-progress: true

permissions:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ on:
- cron: '0 1 * * *' # nightly build

concurrency:
group: ${{ github.event.pull_request.number || github.ref }}
group: ${{ github.event.pull_request.number || github.ref }}-integration
cancel-in-progress: true

permissions:
Expand Down
1 change: 0 additions & 1 deletion dockers/cluster.redis.conf
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
protected-mode no
enable-debug-command yes
loadmodule /opt/redis-stack/lib/redisearch.so
loadmodule /opt/redis-stack/lib/redisgraph.so
loadmodule /opt/redis-stack/lib/redistimeseries.so
loadmodule /opt/redis-stack/lib/rejson.so
loadmodule /opt/redis-stack/lib/redisbloom.so
Expand Down
13 changes: 10 additions & 3 deletions redis/_parsers/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,18 @@ def get_value(value):
return int(value)
except ValueError:
return value
elif "=" not in value:
return [get_value(v) for v in value.split(",") if v]
else:
sub_dict = {}
for item in value.split(","):
k, v = item.rsplit("=", 1)
sub_dict[k] = get_value(v)
if not item:
continue
if "=" in item:
k, v = item.rsplit("=", 1)
sub_dict[k] = get_value(v)
else:
sub_dict[item] = True
return sub_dict

for line in response.splitlines():
Expand Down Expand Up @@ -80,7 +87,7 @@ def parse_memory_stats(response, **kwargs):
"""Parse the results of MEMORY STATS"""
stats = pairs_to_dict(response, decode_keys=True, decode_string_values=True)
for key, value in stats.items():
if key.startswith("db."):
if key.startswith("db.") and isinstance(value, list):
stats[key] = pairs_to_dict(
value, decode_keys=True, decode_string_values=True
)
Expand Down
6 changes: 5 additions & 1 deletion redis/commands/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ def parse_to_dict(response):

res = {}
for det in response:
if isinstance(det[1], list):
if not isinstance(det, list) or not det:
continue
if len(det) == 1:
res[det[0]] = True
elif isinstance(det[1], list):
res[det[0]] = parse_list_to_dict(det[1])
else:
try: # try to set the attribute. may be provided without value
Expand Down
2 changes: 1 addition & 1 deletion 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=["Redis", "key-value store", "database"],
license="MIT",
version="5.0.5",
version="5.0.6",
packages=find_packages(
include=[
"redis",
Expand Down
2 changes: 1 addition & 1 deletion tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
def devenv(c):
"""Brings up the test environment, by wrapping docker compose."""
clean(c)
cmd = "docker-compose --profile all up -d"
cmd = "docker-compose --profile all up -d --build"
run(cmd)


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 @@ -1445,7 +1445,7 @@ async def test_memory_stats(self, r: RedisCluster) -> 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: RedisCluster) -> None:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_asyncio/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -3207,7 +3207,7 @@ async def test_memory_stats(self, r: redis.Redis):
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: redis.Redis):
Expand Down
38 changes: 38 additions & 0 deletions tests/test_asyncio/test_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ async def test_bulk(decoded_r):
await decoded_r.graph().bulk(foo="bar!")


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

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


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

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


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


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


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

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


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


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


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

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


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


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

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


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


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


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


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


@skip_if_redis_enterprise()
@pytest.mark.redismod
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
async def test_config(decoded_r: redis.Redis):
config_name = "RESULTSET_SIZE"
config_value = 3
Expand Down Expand Up @@ -328,6 +358,8 @@ async def test_config(decoded_r: redis.Redis):


@pytest.mark.onlynoncluster
@pytest.mark.redismod
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
async def test_list_keys(decoded_r: redis.Redis):
result = await decoded_r.graph().list_keys()
assert result == []
Expand All @@ -350,6 +382,8 @@ async def test_list_keys(decoded_r: redis.Redis):
assert result == []


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

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


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


@pytest.mark.redismod
@pytest.mark.skip(reason="Graph module removed from Redis Stack")
async def test_explain(decoded_r: redis.Redis):
redis_graph = decoded_r.graph("execution_plan")
# graph creation / population
Expand Down
1 change: 1 addition & 0 deletions tests/test_asyncio/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ async def test_jsonsetexistentialmodifiersshouldsucceed(decoded_r: redis.Redis):
await decoded_r.json().set("obj", Path("foo"), "baz", nx=True, xx=True)


@pytest.mark.onlynoncluster
async def test_mgetshouldsucceed(decoded_r: redis.Redis):
await decoded_r.json().set("1", Path.root_path(), 1)
await decoded_r.json().set("2", Path.root_path(), 2)
Expand Down
1 change: 1 addition & 0 deletions tests/test_asyncio/test_timeseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ async def test_incrby_decrby(decoded_r: redis.Redis):
assert_resp_response(decoded_r, 128, info.get("chunk_size"), info.get("chunkSize"))


@pytest.mark.onlynoncluster
async def test_create_and_delete_rule(decoded_r: redis.Redis):
# test rule creation
time = 100
Expand Down
2 changes: 1 addition & 1 deletion tests/test_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -1569,7 +1569,7 @@ def test_memory_stats(self, r):
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")
def test_memory_help(self, r):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -4880,7 +4880,7 @@ def test_memory_stats(self, r):
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")
def test_memory_usage(self, r):
Expand Down
Loading

0 comments on commit 40a9092

Please sign in to comment.