From 79d1ba7b6f7b76dcbd168ce4d0b3fa0441565944 Mon Sep 17 00:00:00 2001 From: tuecoder Date: Fri, 27 Feb 2026 16:19:06 +0100 Subject: [PATCH 1/8] task: added repr_tests to test repr for base classes --- tests/unit/test_repr.py | 208 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 tests/unit/test_repr.py diff --git a/tests/unit/test_repr.py b/tests/unit/test_repr.py new file mode 100644 index 00000000..e77ba322 --- /dev/null +++ b/tests/unit/test_repr.py @@ -0,0 +1,208 @@ +"""Unit tests for __repr__ implementations on core RedisVL classes.""" + +from unittest.mock import MagicMock, patch + +import pytest + +from redisvl.index import AsyncSearchIndex, SearchIndex +from redisvl.schema import IndexSchema + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _make_schema(name="my-index", prefix="rvl", storage_type="hash"): + return IndexSchema.from_dict( + {"index": {"name": name, "prefix": prefix, "storage_type": storage_type}} + ) + + +def _make_mock_vectorizer(dims=768, dtype="float32"): + mock_vec = MagicMock() + mock_vec.dims = dims + mock_vec.dtype = dtype + return mock_vec + + +# --------------------------------------------------------------------------- +# SearchIndex / AsyncSearchIndex +# --------------------------------------------------------------------------- + + +def test_search_index_repr_hash(): + schema = _make_schema(name="test-index", prefix="rvl", storage_type="hash") + index = SearchIndex(schema=schema) + assert ( + repr(index) + == "SearchIndex(name='test-index', prefix='rvl', storage_type='hash')" + ) + + +def test_search_index_repr_json(): + schema = _make_schema(name="docs", prefix="doc", storage_type="json") + index = SearchIndex(schema=schema) + assert repr(index) == "SearchIndex(name='docs', prefix='doc', storage_type='json')" + + +def test_async_search_index_repr(): + schema = _make_schema(name="async-idx", prefix="data", storage_type="json") + index = AsyncSearchIndex(schema=schema) + assert ( + repr(index) + == "AsyncSearchIndex(name='async-idx', prefix='data', storage_type='json')" + ) + + +# --------------------------------------------------------------------------- +# SemanticCache +# --------------------------------------------------------------------------- + + +@pytest.fixture() +def patched_semantic_cache(): + """Patch out Redis and HFTextVectorizer so SemanticCache can be instantiated + without a running Redis server or ML model download.""" + mock_vec = _make_mock_vectorizer() + mock_idx = MagicMock() + mock_idx.exists.return_value = False + + with ( + patch( + "redisvl.extensions.cache.llm.semantic.HFTextVectorizer", + return_value=mock_vec, + ), + patch( + "redisvl.extensions.cache.llm.semantic.SearchIndex", + return_value=mock_idx, + ), + ): + yield + + +def test_semantic_cache_repr_defaults(patched_semantic_cache): + from redisvl.extensions.cache.llm.semantic import SemanticCache + + cache = SemanticCache(name="llmcache", distance_threshold=0.1) + assert ( + repr(cache) + == "SemanticCache(name='llmcache', distance_threshold=0.1, ttl=None)" + ) + + +def test_semantic_cache_repr_with_ttl(patched_semantic_cache): + from redisvl.extensions.cache.llm.semantic import SemanticCache + + cache = SemanticCache(name="my-cache", distance_threshold=0.2, ttl=300) + assert ( + repr(cache) == "SemanticCache(name='my-cache', distance_threshold=0.2, ttl=300)" + ) + + +# --------------------------------------------------------------------------- +# SemanticRouter +# --------------------------------------------------------------------------- + + +def test_semantic_router_repr(): + from redisvl.extensions.router.schema import Route + from redisvl.extensions.router.semantic import SemanticRouter + + routes = [ + Route(name="greeting", references=["hello", "hi"]), + Route(name="farewell", references=["bye", "goodbye"]), + ] + # model_construct bypasses __init__ (and therefore Redis/vectorizer setup) + # while still setting the Pydantic fields that __repr__ reads. + router = SemanticRouter.model_construct(name="my-router", routes=routes) + assert repr(router) == "SemanticRouter(name='my-router', routes=2)" + + +def test_semantic_router_repr_single_route(): + from redisvl.extensions.router.schema import Route + from redisvl.extensions.router.semantic import SemanticRouter + + routes = [Route(name="support", references=["help", "issue"])] + router = SemanticRouter.model_construct(name="support-router", routes=routes) + assert repr(router) == "SemanticRouter(name='support-router', routes=1)" + + +# --------------------------------------------------------------------------- +# MessageHistory +# --------------------------------------------------------------------------- + + +@pytest.fixture() +def patched_message_history(): + """Patch SearchIndex.create so MessageHistory init doesn't need Redis.""" + with patch("redisvl.extensions.message_history.message_history.SearchIndex.create"): + yield + + +def test_message_history_repr(patched_message_history): + from redisvl.extensions.message_history.message_history import MessageHistory + + mh = MessageHistory(name="chat", session_tag="abc123") + assert repr(mh) == "MessageHistory(name='chat', session_tag='abc123')" + + +def test_message_history_repr_custom_name(patched_message_history): + from redisvl.extensions.message_history.message_history import MessageHistory + + mh = MessageHistory(name="support-chat", session_tag="sess-001") + assert repr(mh) == "MessageHistory(name='support-chat', session_tag='sess-001')" + + +# --------------------------------------------------------------------------- +# SemanticMessageHistory +# --------------------------------------------------------------------------- + + +@pytest.fixture() +def patched_semantic_message_history(): + """Patch out Redis and HFTextVectorizer for SemanticMessageHistory.""" + mock_vec = _make_mock_vectorizer(dims=384) + mock_idx = MagicMock() + mock_idx.exists.return_value = False + + with ( + patch( + "redisvl.extensions.message_history.semantic_history.HFTextVectorizer", + return_value=mock_vec, + ), + patch( + "redisvl.extensions.message_history.semantic_history.SearchIndex", + return_value=mock_idx, + ), + ): + yield + + +def test_semantic_message_history_repr(patched_semantic_message_history): + from redisvl.extensions.message_history.semantic_history import ( + SemanticMessageHistory, + ) + + smh = SemanticMessageHistory( + name="sem-chat", session_tag="sess-42", distance_threshold=0.3 + ) + assert ( + repr(smh) + == "SemanticMessageHistory(name='sem-chat', session_tag='sess-42', distance_threshold=0.3)" + ) + + +def test_semantic_message_history_repr_custom_threshold( + patched_semantic_message_history, +): + from redisvl.extensions.message_history.semantic_history import ( + SemanticMessageHistory, + ) + + smh = SemanticMessageHistory( + name="history", session_tag="s1", distance_threshold=0.5 + ) + assert ( + repr(smh) + == "SemanticMessageHistory(name='history', session_tag='s1', distance_threshold=0.5)" + ) From aad94a2e1b6d34ab3e10a656fc046070d3847aeb Mon Sep 17 00:00:00 2001 From: tuecoder Date: Fri, 27 Feb 2026 16:25:12 +0100 Subject: [PATCH 2/8] task: add __repr__ to core RedisVL classes Add human-readable __repr__ to SearchIndex/AsyncSearchIndex (via BaseSearchIndex), SemanticCache, SemanticRouter, MessageHistory, and SemanticMessageHistory. Repr output shows non-sensitive identity fields (name, prefix, storage_type, distance_threshold, ttl, route count, session_tag) and is stable for the same construction parameters. --- redisvl/extensions/cache/llm/semantic.py | 9 +++++++++ redisvl/extensions/message_history/message_history.py | 8 ++++++++ redisvl/extensions/message_history/semantic_history.py | 9 +++++++++ redisvl/extensions/router/semantic.py | 5 +++++ redisvl/index/index.py | 9 +++++++++ 5 files changed, 40 insertions(+) diff --git a/redisvl/extensions/cache/llm/semantic.py b/redisvl/extensions/cache/llm/semantic.py index 8824b3be..ac4405f5 100644 --- a/redisvl/extensions/cache/llm/semantic.py +++ b/redisvl/extensions/cache/llm/semantic.py @@ -174,6 +174,15 @@ def __init__( # Create the search index in Redis self._index.create(overwrite=self.overwrite, drop=False) + def __repr__(self) -> str: + return ( + f"SemanticCache(" + f"name={self.name!r}, " + f"distance_threshold={self._distance_threshold}, " + f"ttl={self._ttl}" + f")" + ) + def _modify_schema( self, schema: SemanticCacheIndexSchema, diff --git a/redisvl/extensions/message_history/message_history.py b/redisvl/extensions/message_history/message_history.py index d890fea3..ae877f44 100644 --- a/redisvl/extensions/message_history/message_history.py +++ b/redisvl/extensions/message_history/message_history.py @@ -68,6 +68,14 @@ def __init__( self._default_session_filter = Tag(SESSION_FIELD_NAME) == self._session_tag + def __repr__(self) -> str: + return ( + f"MessageHistory(" + f"name={self._name!r}, " + f"session_tag={self._session_tag!r}" + f")" + ) + def clear(self) -> None: """Clears the conversation message history.""" self._index.clear() diff --git a/redisvl/extensions/message_history/semantic_history.py b/redisvl/extensions/message_history/semantic_history.py index 35bfb0ed..3767c6a6 100644 --- a/redisvl/extensions/message_history/semantic_history.py +++ b/redisvl/extensions/message_history/semantic_history.py @@ -120,6 +120,15 @@ def __init__( self._default_session_filter = Tag(SESSION_FIELD_NAME) == self._session_tag + def __repr__(self) -> str: + return ( + f"SemanticMessageHistory(" + f"name={self._name!r}, " + f"session_tag={self._session_tag!r}, " + f"distance_threshold={self._distance_threshold}" + f")" + ) + def clear(self) -> None: """Clears the message history.""" self._index.clear() diff --git a/redisvl/extensions/router/semantic.py b/redisvl/extensions/router/semantic.py index 66a00d20..efab8a7b 100644 --- a/redisvl/extensions/router/semantic.py +++ b/redisvl/extensions/router/semantic.py @@ -178,6 +178,11 @@ def _initialize_index( # write the routes to Redis self._add_routes(self.routes) + def __repr__(self) -> str: + return ( + f"SemanticRouter(" f"name={self.name!r}, " f"routes={len(self.routes)}" f")" + ) + @property def route_names(self) -> List[str]: """Get the list of route names. diff --git a/redisvl/index/index.py b/redisvl/index/index.py index c1abd011..f198d94c 100644 --- a/redisvl/index/index.py +++ b/redisvl/index/index.py @@ -400,6 +400,15 @@ def key(self, id: str) -> str: key_separator=self.schema.index.key_separator, ) + def __repr__(self) -> str: + return ( + f"{type(self).__name__}(" + f"name={self.name!r}, " + f"prefix={self.prefix!r}, " + f"storage_type={self.storage_type.value!r}" + f")" + ) + class SearchIndex(BaseSearchIndex): """A search index class for interacting with Redis as a vector database. From a5cfece3f2707a70a18649df23e8335e6e4f093f Mon Sep 17 00:00:00 2001 From: Tyler Hutcherson Date: Fri, 27 Feb 2026 14:33:16 -0500 Subject: [PATCH 3/8] Update redisvl/index/index.py Co-authored-by: Vishal Bala --- redisvl/index/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redisvl/index/index.py b/redisvl/index/index.py index f198d94c..5b531465 100644 --- a/redisvl/index/index.py +++ b/redisvl/index/index.py @@ -404,7 +404,7 @@ def __repr__(self) -> str: return ( f"{type(self).__name__}(" f"name={self.name!r}, " - f"prefix={self.prefix!r}, " + f"prefixes={self.prefixes}, " f"storage_type={self.storage_type.value!r}" f")" ) From 36387203e590701bb42956232a6bedc4211cc20d Mon Sep 17 00:00:00 2001 From: Prakash Baburaj Date: Sat, 28 Feb 2026 10:27:23 +0100 Subject: [PATCH 4/8] Update redisvl/index/index.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- redisvl/index/index.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/redisvl/index/index.py b/redisvl/index/index.py index 5b531465..5c4ea54b 100644 --- a/redisvl/index/index.py +++ b/redisvl/index/index.py @@ -401,10 +401,20 @@ def key(self, id: str) -> str: ) def __repr__(self) -> str: + # Prefer showing a single public `prefix` when available, + # and fall back to `prefixes` only when multiple are configured. + prefix_label = "prefix" + prefix_value = self.prefix + + prefixes = getattr(self, "prefixes", None) + if isinstance(prefixes, (list, tuple)) and len(prefixes) != 1: + prefix_label = "prefixes" + prefix_value = prefixes + return ( f"{type(self).__name__}(" f"name={self.name!r}, " - f"prefixes={self.prefixes}, " + f"{prefix_label}={prefix_value!r}, " f"storage_type={self.storage_type.value!r}" f")" ) From b1a8cd356b79bf6f35d75c3c5b04d20b33fdf020 Mon Sep 17 00:00:00 2001 From: tuecoder Date: Sat, 28 Feb 2026 11:43:38 +0100 Subject: [PATCH 5/8] Address PR review comments(changed prefix to prefixes in index.py and test_repr.py) --- redisvl/index/index.py | 12 +----------- tests/unit/test_repr.py | 8 +++++--- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/redisvl/index/index.py b/redisvl/index/index.py index 5c4ea54b..0a5b9a2c 100644 --- a/redisvl/index/index.py +++ b/redisvl/index/index.py @@ -401,20 +401,10 @@ def key(self, id: str) -> str: ) def __repr__(self) -> str: - # Prefer showing a single public `prefix` when available, - # and fall back to `prefixes` only when multiple are configured. - prefix_label = "prefix" - prefix_value = self.prefix - - prefixes = getattr(self, "prefixes", None) - if isinstance(prefixes, (list, tuple)) and len(prefixes) != 1: - prefix_label = "prefixes" - prefix_value = prefixes - return ( f"{type(self).__name__}(" f"name={self.name!r}, " - f"{prefix_label}={prefix_value!r}, " + f"prefixes={self.prefixes!r}, " f"storage_type={self.storage_type.value!r}" f")" ) diff --git a/tests/unit/test_repr.py b/tests/unit/test_repr.py index e77ba322..3fb485af 100644 --- a/tests/unit/test_repr.py +++ b/tests/unit/test_repr.py @@ -35,14 +35,16 @@ def test_search_index_repr_hash(): index = SearchIndex(schema=schema) assert ( repr(index) - == "SearchIndex(name='test-index', prefix='rvl', storage_type='hash')" + == "SearchIndex(name='test-index', prefixes=['rvl'], storage_type='hash')" ) def test_search_index_repr_json(): schema = _make_schema(name="docs", prefix="doc", storage_type="json") index = SearchIndex(schema=schema) - assert repr(index) == "SearchIndex(name='docs', prefix='doc', storage_type='json')" + assert ( + repr(index) == "SearchIndex(name='docs', prefixes=['doc'], storage_type='json')" + ) def test_async_search_index_repr(): @@ -50,7 +52,7 @@ def test_async_search_index_repr(): index = AsyncSearchIndex(schema=schema) assert ( repr(index) - == "AsyncSearchIndex(name='async-idx', prefix='data', storage_type='json')" + == "AsyncSearchIndex(name='async-idx', prefixes=['data'], storage_type='json')" ) From 912a9e1e7e732e6dc3fd8ece279c89d26a278a81 Mon Sep 17 00:00:00 2001 From: tuecoder Date: Mon, 2 Mar 2026 17:30:35 +0100 Subject: [PATCH 6/8] Address single fstring for repr methods and python 3.9 support(with statements) --- redisvl/extensions/cache/llm/semantic.py | 7 ++-- .../message_history/message_history.py | 7 +--- .../message_history/semantic_history.py | 7 ++-- redisvl/extensions/router/semantic.py | 4 +-- redisvl/index/index.py | 7 ++-- tests/unit/test_repr.py | 32 ++++++++----------- 6 files changed, 22 insertions(+), 42 deletions(-) diff --git a/redisvl/extensions/cache/llm/semantic.py b/redisvl/extensions/cache/llm/semantic.py index ac4405f5..bcf72764 100644 --- a/redisvl/extensions/cache/llm/semantic.py +++ b/redisvl/extensions/cache/llm/semantic.py @@ -176,11 +176,8 @@ def __init__( def __repr__(self) -> str: return ( - f"SemanticCache(" - f"name={self.name!r}, " - f"distance_threshold={self._distance_threshold}, " - f"ttl={self._ttl}" - f")" + f"SemanticCache(name={self.name!r}, " + f"distance_threshold={self._distance_threshold}, ttl={self._ttl})" ) def _modify_schema( diff --git a/redisvl/extensions/message_history/message_history.py b/redisvl/extensions/message_history/message_history.py index ae877f44..4d2fa7d7 100644 --- a/redisvl/extensions/message_history/message_history.py +++ b/redisvl/extensions/message_history/message_history.py @@ -69,12 +69,7 @@ def __init__( self._default_session_filter = Tag(SESSION_FIELD_NAME) == self._session_tag def __repr__(self) -> str: - return ( - f"MessageHistory(" - f"name={self._name!r}, " - f"session_tag={self._session_tag!r}" - f")" - ) + return f"MessageHistory(name={self._name!r}, session_tag={self._session_tag!r})" def clear(self) -> None: """Clears the conversation message history.""" diff --git a/redisvl/extensions/message_history/semantic_history.py b/redisvl/extensions/message_history/semantic_history.py index 3767c6a6..8bc5fa14 100644 --- a/redisvl/extensions/message_history/semantic_history.py +++ b/redisvl/extensions/message_history/semantic_history.py @@ -122,11 +122,8 @@ def __init__( def __repr__(self) -> str: return ( - f"SemanticMessageHistory(" - f"name={self._name!r}, " - f"session_tag={self._session_tag!r}, " - f"distance_threshold={self._distance_threshold}" - f")" + f"SemanticMessageHistory(name={self._name!r}, " + f"session_tag={self._session_tag!r}, distance_threshold={self._distance_threshold})" ) def clear(self) -> None: diff --git a/redisvl/extensions/router/semantic.py b/redisvl/extensions/router/semantic.py index efab8a7b..a7656c2d 100644 --- a/redisvl/extensions/router/semantic.py +++ b/redisvl/extensions/router/semantic.py @@ -179,9 +179,7 @@ def _initialize_index( self._add_routes(self.routes) def __repr__(self) -> str: - return ( - f"SemanticRouter(" f"name={self.name!r}, " f"routes={len(self.routes)}" f")" - ) + return f"SemanticRouter(name={self.name!r}, routes={len(self.routes)})" @property def route_names(self) -> List[str]: diff --git a/redisvl/index/index.py b/redisvl/index/index.py index 0a5b9a2c..5f7c947b 100644 --- a/redisvl/index/index.py +++ b/redisvl/index/index.py @@ -402,11 +402,8 @@ def key(self, id: str) -> str: def __repr__(self) -> str: return ( - f"{type(self).__name__}(" - f"name={self.name!r}, " - f"prefixes={self.prefixes!r}, " - f"storage_type={self.storage_type.value!r}" - f")" + f"{type(self).__name__}(name={self.name!r}, prefixes={self.prefixes!r}, " + f"storage_type={self.storage_type.value!r})" ) diff --git a/tests/unit/test_repr.py b/tests/unit/test_repr.py index 3fb485af..079d3a87 100644 --- a/tests/unit/test_repr.py +++ b/tests/unit/test_repr.py @@ -69,17 +69,15 @@ def patched_semantic_cache(): mock_idx = MagicMock() mock_idx.exists.return_value = False - with ( - patch( - "redisvl.extensions.cache.llm.semantic.HFTextVectorizer", - return_value=mock_vec, - ), - patch( + with patch( + "redisvl.extensions.cache.llm.semantic.HFTextVectorizer", + return_value=mock_vec, + ): + with patch( "redisvl.extensions.cache.llm.semantic.SearchIndex", return_value=mock_idx, - ), - ): - yield + ): + yield def test_semantic_cache_repr_defaults(patched_semantic_cache): @@ -167,17 +165,15 @@ def patched_semantic_message_history(): mock_idx = MagicMock() mock_idx.exists.return_value = False - with ( - patch( - "redisvl.extensions.message_history.semantic_history.HFTextVectorizer", - return_value=mock_vec, - ), - patch( + with patch( + "redisvl.extensions.message_history.semantic_history.HFTextVectorizer", + return_value=mock_vec, + ): + with patch( "redisvl.extensions.message_history.semantic_history.SearchIndex", return_value=mock_idx, - ), - ): - yield + ): + yield def test_semantic_message_history_repr(patched_semantic_message_history): From 3da10f21e1a5bcc1691fcbd231c995b5c7853e13 Mon Sep 17 00:00:00 2001 From: Prakash Baburaj Date: Mon, 2 Mar 2026 17:37:20 +0100 Subject: [PATCH 7/8] Update redisvl/extensions/cache/llm/semantic.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- redisvl/extensions/cache/llm/semantic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redisvl/extensions/cache/llm/semantic.py b/redisvl/extensions/cache/llm/semantic.py index bcf72764..508da3de 100644 --- a/redisvl/extensions/cache/llm/semantic.py +++ b/redisvl/extensions/cache/llm/semantic.py @@ -177,7 +177,7 @@ def __init__( def __repr__(self) -> str: return ( f"SemanticCache(name={self.name!r}, " - f"distance_threshold={self._distance_threshold}, ttl={self._ttl})" + f"distance_threshold={self.distance_threshold}, ttl={self.ttl})" ) def _modify_schema( From 8bbb90b5433713a9aba7021555f7e8862fa90f0f Mon Sep 17 00:00:00 2001 From: Prakash Baburaj Date: Mon, 2 Mar 2026 17:37:39 +0100 Subject: [PATCH 8/8] Update redisvl/extensions/message_history/semantic_history.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- redisvl/extensions/message_history/semantic_history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redisvl/extensions/message_history/semantic_history.py b/redisvl/extensions/message_history/semantic_history.py index 8bc5fa14..3def5ef7 100644 --- a/redisvl/extensions/message_history/semantic_history.py +++ b/redisvl/extensions/message_history/semantic_history.py @@ -123,7 +123,7 @@ def __init__( def __repr__(self) -> str: return ( f"SemanticMessageHistory(name={self._name!r}, " - f"session_tag={self._session_tag!r}, distance_threshold={self._distance_threshold})" + f"session_tag={self._session_tag!r}, distance_threshold={self.distance_threshold})" ) def clear(self) -> None: