Skip to content

Commit 267c807

Browse files
committed
Allow user to override cluster detection
1 parent 2fbc882 commit 267c807

File tree

4 files changed

+49
-239
lines changed

4 files changed

+49
-239
lines changed

langgraph/store/redis/__init__.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,13 @@ def __init__(
8484
*,
8585
index: Optional[IndexConfig] = None,
8686
ttl: Optional[TTLConfig] = None,
87+
cluster_mode: Optional[bool] = None,
8788
) -> None:
8889
BaseStore.__init__(self)
89-
BaseRedisStore.__init__(self, conn, index=index, ttl=ttl)
90-
self._detect_cluster_mode()
90+
BaseRedisStore.__init__(
91+
self, conn, index=index, ttl=ttl, cluster_mode=cluster_mode
92+
)
93+
# Detection will happen in setup()
9194

9295
@classmethod
9396
@contextmanager
@@ -151,17 +154,23 @@ def batch(self, ops: Iterable[Op]) -> list[Result]:
151154
return results
152155

153156
def _detect_cluster_mode(self) -> None:
154-
"""Detect if the Redis client is connected to a cluster."""
155-
try:
156-
# Try to run a cluster command
157-
# This will succeed for Redis clusters and fail for non-cluster servers
158-
self._redis.cluster("info")
157+
"""Detect if the Redis client is a cluster client by inspecting its class."""
158+
# If we passed in_cluster_mode explicitly, respect it
159+
if self.cluster_mode is not None:
160+
logger.info(
161+
f"Redis cluster_mode explicitly set to {self.cluster_mode}, skipping detection."
162+
)
163+
return
164+
165+
# Check if client is a Redis Cluster instance
166+
from redis.cluster import RedisCluster as SyncRedisCluster
167+
168+
if isinstance(self._redis, SyncRedisCluster):
159169
self.cluster_mode = True
160-
logger.info("Redis cluster mode detected for RedisStore.")
161-
except (ResponseError, AttributeError):
170+
logger.info("Redis cluster client detected for RedisStore.")
171+
else:
162172
self.cluster_mode = False
163-
logger.info("Redis standalone mode detected for RedisStore.")
164-
173+
logger.info("Redis standalone client detected for RedisStore.")
165174

166175
def _batch_list_namespaces_ops(
167176
self,

langgraph/store/redis/aio.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@
66
from contextlib import asynccontextmanager
77
from datetime import datetime, timedelta, timezone
88
from types import TracebackType
9-
from typing import Any, AsyncIterator, Iterable, Optional, Sequence, cast
9+
from typing import Any, AsyncIterator, Iterable, Optional, Sequence, cast, Union
1010

1111
from langgraph.store.base import (
12-
BaseStore,
1312
GetOp,
1413
IndexConfig,
1514
ListNamespacesOp,
@@ -22,7 +21,7 @@
2221
get_text_at_path,
2322
tokenize_path,
2423
)
25-
from langgraph.store.base.batch import AsyncBatchedBaseStore, _dedupe_ops
24+
from langgraph.store.base.batch import AsyncBatchedBaseStore
2625
from redis import ResponseError
2726
from redis.asyncio import Redis as AsyncRedis
2827
from redis.commands.search.query import Query
@@ -50,6 +49,7 @@
5049

5150
_token_escaper = TokenEscaper()
5251
_token_unescaper = TokenUnescaper()
52+
from redis.asyncio.cluster import RedisCluster as AsyncRedisCluster
5353

5454

5555
class AsyncRedisStore(
@@ -65,7 +65,8 @@ class AsyncRedisStore(
6565
_async_ttl_stop_event: asyncio.Event | None = None
6666
_ttl_sweeper_task: asyncio.Task | None = None
6767
ttl_config: Optional[TTLConfig] = None
68-
cluster_mode: bool = False
68+
# Whether to assume the Redis server is a cluster; None triggers auto-detection
69+
cluster_mode: Optional[bool] = None
6970

7071
def __init__(
7172
self,
@@ -75,6 +76,7 @@ def __init__(
7576
index: Optional[IndexConfig] = None,
7677
connection_args: Optional[dict[str, Any]] = None,
7778
ttl: Optional[dict[str, Any]] = None,
79+
cluster_mode: Optional[bool] = None,
7880
) -> None:
7981
"""Initialize store with Redis connection and optional index config."""
8082
if redis_url is None and redis_client is None:
@@ -112,6 +114,11 @@ def __init__(
112114
connection_args=connection_args or {},
113115
)
114116

117+
# Validate and store cluster_mode; None means auto-detect later
118+
if cluster_mode is not None and not isinstance(cluster_mode, bool):
119+
raise TypeError("cluster_mode must be a boolean or None")
120+
self.cluster_mode: Optional[bool] = cluster_mode
121+
115122
# Create store index
116123
self.store_index = AsyncSearchIndex.from_dict(
117124
self.SCHEMAS[0], redis_client=self._redis
@@ -184,24 +191,28 @@ async def setup(self) -> None:
184191
self.index_config.get("embed"),
185192
)
186193

187-
await self._detect_cluster_mode()
194+
# Auto-detect cluster mode if not explicitly set
195+
if self.cluster_mode is None:
196+
await self._detect_cluster_mode()
197+
else:
198+
logger.info(
199+
f"Redis cluster_mode explicitly set to {self.cluster_mode}, skipping detection."
200+
)
188201

189202
# Create indices in Redis
190203
await self.store_index.create(overwrite=False)
191204
if self.index_config:
192205
await self.vector_index.create(overwrite=False)
193206

194207
async def _detect_cluster_mode(self) -> None:
195-
"""Detect if the Redis client is a cluster client."""
196-
try:
197-
# Try to run a cluster command
198-
# This will succeed for cluster clients and fail for non-cluster clients
199-
await self._redis.cluster("info")
208+
"""Detect if the Redis client is a cluster client by inspecting its class."""
209+
# Determine cluster mode based on client class
210+
if isinstance(self._redis, AsyncRedisCluster):
200211
self.cluster_mode = True
201-
logger.info("Redis cluster mode detected for AsyncRedisStore.")
202-
except (ResponseError, AttributeError):
212+
logger.info("Redis cluster client detected for AsyncRedisStore.")
213+
else:
203214
self.cluster_mode = False
204-
logger.info("Redis standalone mode detected for AsyncRedisStore.")
215+
logger.info("Redis standalone client detected for AsyncRedisStore.")
205216

206217
# This can't be properly typed due to covariance issues with async methods
207218
async def _apply_ttl_to_keys(

langgraph/store/redis/base.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ class BaseRedisStore(Generic[RedisClientType, IndexType]):
109109
vector_index: IndexType
110110
_ttl_sweeper_thread: Optional[threading.Thread] = None
111111
_ttl_stop_event: threading.Event | None = None
112-
cluster_mode: bool = False
112+
# Whether to operate in Redis cluster mode; None triggers auto-detection
113+
cluster_mode: Optional[bool] = None
113114
SCHEMAS = SCHEMAS
114115

115116
supports_ttl: bool = True
@@ -195,14 +196,14 @@ def __init__(
195196
*,
196197
index: Optional[IndexConfig] = None,
197198
ttl: Optional[TTLConfig] = None, # Corrected type hint for ttl
199+
cluster_mode: Optional[bool] = None,
198200
) -> None:
199201
"""Initialize store with Redis connection and optional index config."""
200202
self.index_config = index
201203
self.ttl_config = ttl
202204
self._redis = conn
203-
self.cluster_mode = (
204-
False # Default to False, will be set by RedisStore or AsyncRedisStore
205-
)
205+
# Store cluster_mode; None means auto-detect in RedisStore or AsyncRedisStore
206+
self.cluster_mode = cluster_mode
206207

207208
if self.index_config:
208209
self.index_config = self.index_config.copy()

tests/test_async_cluster_mode_backup.py

Lines changed: 0 additions & 211 deletions
This file was deleted.

0 commit comments

Comments
 (0)