Skip to content

Commit ccaecd9

Browse files
committed
Add async-specific cluster mode detection
1 parent d2bff7f commit ccaecd9

File tree

3 files changed

+53
-21
lines changed

3 files changed

+53
-21
lines changed

langgraph/store/redis/aio.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
_row_to_item,
4646
_row_to_search_item,
4747
get_key_with_hash_tag,
48+
logger,
4849
)
4950

5051
from .token_unescaper import TokenUnescaper
@@ -176,6 +177,9 @@ def configure_client(
176177
else:
177178
self._redis = redis_client
178179

180+
# Initialize cluster_mode to False, will be set properly in setup()
181+
self.cluster_mode = False
182+
179183
async def setup(self) -> None:
180184
"""Initialize store indices."""
181185
# Handle embeddings in same way as sync store
@@ -184,11 +188,31 @@ async def setup(self) -> None:
184188
self.index_config.get("embed"),
185189
)
186190

191+
# Detect if we're connected to a Redis cluster
192+
await self.detect_cluster_mode()
193+
187194
# Create indices in Redis
188195
await self.store_index.create(overwrite=False)
189196
if self.index_config:
190197
await self.vector_index.create(overwrite=False)
191198

199+
async def detect_cluster_mode(self) -> None:
200+
"""Detect if the Redis client is a cluster client.
201+
202+
This is the async version of the cluster mode detection logic in BaseRedisStore.__init__.
203+
"""
204+
from redis.exceptions import ResponseError
205+
206+
try:
207+
# Try to run a cluster command
208+
# This will succeed for cluster clients and fail for non-cluster clients
209+
await self._redis.cluster("info")
210+
self.cluster_mode = True
211+
logger.info("Redis cluster mode detected")
212+
except (ResponseError, AttributeError):
213+
self.cluster_mode = False
214+
logger.info("Redis standalone mode detected")
215+
192216
# This can't be properly typed due to covariance issues with async methods
193217
async def _apply_ttl_to_keys(
194218
self,

langgraph/store/redis/base.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
)
2626
from redis import Redis
2727
from redis.asyncio import Redis as AsyncRedis
28+
from redis.exceptions import ResponseError
2829
from redisvl.index import SearchIndex
2930
from redisvl.query.filter import Tag, Text
3031
from redisvl.utils.token_escaper import TokenEscaper
@@ -211,9 +212,6 @@ def __init__(
211212
self.ttl_config = ttl # type: ignore
212213
self.embeddings: Optional[Embeddings] = None
213214

214-
# Detect if Redis client is a cluster client
215-
from redis.exceptions import ResponseError
216-
217215
try:
218216
# Try to run a cluster command
219217
# This will succeed for cluster clients and fail for non-cluster clients

tests/test_async_cluster_mode.py

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,6 @@ def __init__(self, *args, **kwargs):
2929
self.delete_calls = []
3030
self.json_get_calls = []
3131

32-
def cluster(self, command, *args, **kwargs):
33-
"""Mock synchronous cluster command that returns cluster info.
34-
35-
This is called by BaseRedisStore.__init__ to detect cluster mode.
36-
"""
37-
if command == "info":
38-
return {"cluster_state": "ok"}
39-
raise ResponseError(f"Unknown cluster command: {command}")
40-
4132
async def cluster(self, command, *args, **kwargs): # type: ignore
4233
"""Mock asynchronous cluster command that returns cluster info.
4334
@@ -125,9 +116,12 @@ async def async_cluster_store(mock_async_redis_cluster):
125116
# Create a store with the mock Redis client
126117
# Pass the mock client explicitly as redis_client to avoid URL parsing
127118
async with AsyncRedisStore(redis_client=mock_async_redis_cluster) as store:
128-
# Manually set cluster_mode to True for testing
129-
# This bypasses the automatic detection which is hard to mock in async context
130-
store.cluster_mode = True
119+
# The cluster_mode will be automatically detected during setup
120+
# Call setup to ensure cluster mode is detected
121+
await store.detect_cluster_mode()
122+
123+
# Verify that cluster mode was detected
124+
assert store.cluster_mode is True
131125

132126
# Mock the store_index and vector_index
133127
mock_index = AsyncMock()
@@ -145,16 +139,32 @@ async def async_cluster_store(mock_async_redis_cluster):
145139

146140
@pytest.mark.asyncio
147141
async def test_async_cluster_mode_detection(mock_async_redis_cluster):
148-
"""Test that cluster mode can be manually set."""
142+
"""Test that cluster mode is automatically detected."""
149143
# Pass the mock client explicitly as redis_client to avoid URL parsing
150144
async with AsyncRedisStore(redis_client=mock_async_redis_cluster) as store:
151-
# Manually set cluster_mode for testing
152-
store.cluster_mode = True
145+
# Cluster mode should be initialized to False
146+
assert store.cluster_mode is False
147+
148+
# Call detect_cluster_mode to detect cluster mode
149+
await store.detect_cluster_mode()
150+
151+
# Cluster mode should be detected as True
153152
assert store.cluster_mode is True
154153

155-
# Test with cluster_mode set to False
156-
store.cluster_mode = False
157-
assert store.cluster_mode is False
154+
# Test with a non-cluster Redis client by patching the cluster method
155+
with patch.object(
156+
mock_async_redis_cluster,
157+
"cluster",
158+
side_effect=ResponseError("cluster command not allowed"),
159+
):
160+
# Reset cluster_mode to False
161+
store.cluster_mode = False
162+
163+
# Call detect_cluster_mode again
164+
await store.detect_cluster_mode()
165+
166+
# Cluster mode should remain False
167+
assert store.cluster_mode is False
158168

159169

160170
@pytest.mark.asyncio

0 commit comments

Comments
 (0)