From 3bfdd7ff9d7bb50833a3df1878d6dd771a36ff1c Mon Sep 17 00:00:00 2001 From: Petar Kostov Date: Mon, 3 Nov 2025 05:46:45 +0200 Subject: [PATCH 1/3] Add **options to parse functions for sentinel --- redis/_parsers/helpers.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/redis/_parsers/helpers.py b/redis/_parsers/helpers.py index 80531bb249..bb0a380738 100644 --- a/redis/_parsers/helpers.py +++ b/redis/_parsers/helpers.py @@ -137,11 +137,11 @@ def parse_sentinel_state(item): return result -def parse_sentinel_master(response): +def parse_sentinel_master(response, **options): return parse_sentinel_state(map(str_if_bytes, response)) -def parse_sentinel_state_resp3(response): +def parse_sentinel_state_resp3(response, **options): result = {} for key in response: try: @@ -154,7 +154,7 @@ def parse_sentinel_state_resp3(response): return result -def parse_sentinel_masters(response): +def parse_sentinel_masters(response, **options): result = {} for item in response: state = parse_sentinel_state(map(str_if_bytes, item)) @@ -162,19 +162,19 @@ def parse_sentinel_masters(response): return result -def parse_sentinel_masters_resp3(response): +def parse_sentinel_masters_resp3(response, **options): return [parse_sentinel_state(master) for master in response] -def parse_sentinel_slaves_and_sentinels(response): +def parse_sentinel_slaves_and_sentinels(response, **options): return [parse_sentinel_state(map(str_if_bytes, item)) for item in response] -def parse_sentinel_slaves_and_sentinels_resp3(response): - return [parse_sentinel_state_resp3(item) for item in response] +def parse_sentinel_slaves_and_sentinels_resp3(response, **options): + return [parse_sentinel_state_resp3(item, **options) for item in response] -def parse_sentinel_get_master(response): +def parse_sentinel_get_master(response, **options): return response and (response[0], int(response[1])) or None From 1ce809dbfa7bcc63077654a5fd3887abaa8d2e67 Mon Sep 17 00:00:00 2001 From: Petar Kostov Date: Mon, 3 Nov 2025 10:31:17 +0200 Subject: [PATCH 2/3] Add unit tests --- tests/test_asyncio/test_sentinel.py | 26 ++++++++++++++++++++++++++ tests/test_sentinel.py | 24 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/tests/test_asyncio/test_sentinel.py b/tests/test_asyncio/test_sentinel.py index 867ff15405..e15c6bf63c 100644 --- a/tests/test_asyncio/test_sentinel.py +++ b/tests/test_asyncio/test_sentinel.py @@ -3,6 +3,8 @@ import pytest import pytest_asyncio +from redis.asyncio.client import StrictRedis + import redis.asyncio.sentinel from redis import exceptions from redis.asyncio.sentinel import ( @@ -363,3 +365,27 @@ async def test_redis_master_usage(deployed_sentinel): r = await deployed_sentinel.master_for("redis-py-test", db=0) await r.set("foo", "bar") assert (await r.get("foo")) == "bar" + + +@pytest.mark.onlynoncluster +async def test_sentinel_commands_with_strict_redis_client(request): + sentinel_ips = request.config.getoption("--sentinels") + sentinel_host, sentinel_port = sentinel_ips.split(",")[0].split(":") + protocol = request.config.getoption("--protocol", 2) + + client = StrictRedis( + host=sentinel_host, port=sentinel_port, decode_responses=True, protocol=protocol + ) + # skipping commands that change the state of the sentinel setup + assert isinstance( + await client.sentinel_get_master_addr_by_name("redis-py-test"), tuple + ) + assert isinstance(await client.sentinel_master("redis-py-test"), dict) + assert isinstance(await client.sentinel_masters(), dict) + + assert isinstance(await client.sentinel_sentinels("redis-py-test"), list) + assert isinstance(await client.sentinel_slaves("redis-py-test"), list) + + assert isinstance(await client.sentinel_ckquorum("redis-py-test"), bool) + + await client.close() diff --git a/tests/test_sentinel.py b/tests/test_sentinel.py index 0e7624c836..499677b2f9 100644 --- a/tests/test_sentinel.py +++ b/tests/test_sentinel.py @@ -2,6 +2,8 @@ from unittest import mock import pytest +from redis.client import StrictRedis + import redis.sentinel from redis import exceptions from redis.sentinel import ( @@ -342,3 +344,25 @@ def test_redis_master_usage(deployed_sentinel): r = deployed_sentinel.master_for("redis-py-test", db=0) r.set("foo", "bar") assert r.get("foo") == "bar" + + +@pytest.mark.onlynoncluster +def test_sentinel_commands_with_strict_redis_client(request): + sentinel_ips = request.config.getoption("--sentinels") + sentinel_host, sentinel_port = sentinel_ips.split(",")[0].split(":") + protocol = request.config.getoption("--protocol", 2) + + client = StrictRedis( + host=sentinel_host, port=sentinel_port, decode_responses=True, protocol=protocol + ) + # skipping commands that change the state of the sentinel setup + assert isinstance(client.sentinel_get_master_addr_by_name("redis-py-test"), tuple) + assert isinstance(client.sentinel_master("redis-py-test"), dict) + assert isinstance(client.sentinel_masters(), dict) + + assert isinstance(client.sentinel_sentinels("redis-py-test"), list) + assert isinstance(client.sentinel_slaves("redis-py-test"), list) + + assert isinstance(client.sentinel_ckquorum("redis-py-test"), bool) + + client.close() From ade3b8f59f989815d7df048c33a702624b70d737 Mon Sep 17 00:00:00 2001 From: Petar Kostov Date: Mon, 3 Nov 2025 13:54:30 +0200 Subject: [PATCH 3/3] Fixed the tests and the resp parser --- redis/_parsers/helpers.py | 2 +- tests/test_asyncio/test_sentinel.py | 9 ++++++++- tests/test_sentinel.py | 9 ++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/redis/_parsers/helpers.py b/redis/_parsers/helpers.py index bb0a380738..bf3b2ee2f2 100644 --- a/redis/_parsers/helpers.py +++ b/redis/_parsers/helpers.py @@ -163,7 +163,7 @@ def parse_sentinel_masters(response, **options): def parse_sentinel_masters_resp3(response, **options): - return [parse_sentinel_state(master) for master in response] + return [parse_sentinel_state_resp3(master) for master in response] def parse_sentinel_slaves_and_sentinels(response, **options): diff --git a/tests/test_asyncio/test_sentinel.py b/tests/test_asyncio/test_sentinel.py index e15c6bf63c..87532ae5d5 100644 --- a/tests/test_asyncio/test_sentinel.py +++ b/tests/test_asyncio/test_sentinel.py @@ -13,6 +13,7 @@ SentinelConnectionPool, SlaveNotFoundError, ) +from tests.conftest import is_resp2_connection @pytest_asyncio.fixture(scope="module", loop_scope="module") @@ -381,7 +382,13 @@ async def test_sentinel_commands_with_strict_redis_client(request): await client.sentinel_get_master_addr_by_name("redis-py-test"), tuple ) assert isinstance(await client.sentinel_master("redis-py-test"), dict) - assert isinstance(await client.sentinel_masters(), dict) + if is_resp2_connection(client): + assert isinstance(await client.sentinel_masters(), dict) + else: + masters = await client.sentinel_masters() + assert isinstance(masters, list) + for master in masters: + assert isinstance(master, dict) assert isinstance(await client.sentinel_sentinels("redis-py-test"), list) assert isinstance(await client.sentinel_slaves("redis-py-test"), list) diff --git a/tests/test_sentinel.py b/tests/test_sentinel.py index 499677b2f9..fb4a2d4056 100644 --- a/tests/test_sentinel.py +++ b/tests/test_sentinel.py @@ -12,6 +12,7 @@ SentinelConnectionPool, SlaveNotFoundError, ) +from tests.conftest import is_resp2_connection @pytest.fixture(scope="module") @@ -358,7 +359,13 @@ def test_sentinel_commands_with_strict_redis_client(request): # skipping commands that change the state of the sentinel setup assert isinstance(client.sentinel_get_master_addr_by_name("redis-py-test"), tuple) assert isinstance(client.sentinel_master("redis-py-test"), dict) - assert isinstance(client.sentinel_masters(), dict) + if is_resp2_connection(client): + assert isinstance(client.sentinel_masters(), dict) + else: + masters = client.sentinel_masters() + assert isinstance(masters, list) + for master in masters: + assert isinstance(master, dict) assert isinstance(client.sentinel_sentinels("redis-py-test"), list) assert isinstance(client.sentinel_slaves("redis-py-test"), list)