diff --git a/redisvl/exceptions.py b/redisvl/exceptions.py new file mode 100644 index 00000000..79b165e3 --- /dev/null +++ b/redisvl/exceptions.py @@ -0,0 +1,6 @@ +class RedisVLException(Exception): + """Base RedisVL exception""" + + +class RedisModuleVersionError(RedisVLException): + """Invalid module versions installed""" diff --git a/redisvl/index/index.py b/redisvl/index/index.py index b4adb6b3..0c4d3e2a 100644 --- a/redisvl/index/index.py +++ b/redisvl/index/index.py @@ -25,6 +25,7 @@ import redis.asyncio as aredis from redis.commands.search.indexDefinition import IndexDefinition +from redisvl.exceptions import RedisModuleVersionError from redisvl.index.storage import BaseStorage, HashStorage, JsonStorage from redisvl.query import BaseQuery, CountQuery, FilterQuery from redisvl.query.filter import FilterExpression @@ -354,7 +355,17 @@ def from_existing( # Validate modules installed_modules = RedisConnectionFactory.get_modules(redis_client) - validate_modules(installed_modules, [{"name": "search", "ver": 20810}]) + + try: + required_modules = [ + {"name": "search", "ver": 20810}, + {"name": "searchlight", "ver": 20810}, + ] + validate_modules(installed_modules, required_modules) + except RedisModuleVersionError as e: + raise RedisModuleVersionError( + f"Loading from existing index failed. {str(e)}" + ) # Fetch index info and convert to schema index_info = cls._info(name, redis_client) @@ -860,7 +871,17 @@ async def from_existing( # Validate modules installed_modules = await RedisConnectionFactory.get_modules_async(redis_client) - validate_modules(installed_modules, [{"name": "search", "ver": 20810}]) + + try: + required_modules = [ + {"name": "search", "ver": 20810}, + {"name": "searchlight", "ver": 20810}, + ] + validate_modules(installed_modules, required_modules) + except RedisModuleVersionError as e: + raise RedisModuleVersionError( + f"Loading from existing index failed. {str(e)}" + ) from e # Fetch index info and convert to schema index_info = await cls._info(name, redis_client) diff --git a/redisvl/redis/connection.py b/redisvl/redis/connection.py index 21095cde..560004c0 100644 --- a/redisvl/redis/connection.py +++ b/redisvl/redis/connection.py @@ -9,6 +9,7 @@ from redis.connection import AbstractConnection, SSLConnection from redis.exceptions import ResponseError +from redisvl.exceptions import RedisModuleVersionError from redisvl.redis.constants import DEFAULT_REQUIRED_MODULES from redisvl.redis.utils import convert_bytes from redisvl.version import __version__ @@ -143,10 +144,16 @@ def validate_modules( if int(installed_version) >= int(required_module["ver"]): # type: ignore return - raise ValueError( - f"Required Redis database module {required_module['name']} with version >= {required_module['ver']} not installed. " - "See Redis Stack documentation: https://redis.io/docs/stack/" + # Build the error message dynamically + required_modules_str = " OR ".join( + [f'{module["name"]} >= {module["ver"]}' for module in required_modules] ) + error_message = ( + f"Required Redis db module {required_modules_str} not installed. " + "See Redis Stack docs at https://redis.io/docs/latest/operate/oss_and_stack/install/install-stack/." + ) + + raise RedisModuleVersionError(error_message) class RedisConnectionFactory: diff --git a/tests/integration/test_connection.py b/tests/integration/test_connection.py index 9a6035f2..80556cdb 100644 --- a/tests/integration/test_connection.py +++ b/tests/integration/test_connection.py @@ -5,6 +5,7 @@ from redis.asyncio import Redis as AsyncRedis from redis.exceptions import ConnectionError +from redisvl.exceptions import RedisModuleVersionError from redisvl.redis.connection import ( RedisConnectionFactory, compare_versions, @@ -102,7 +103,7 @@ def test_convert_index_info_to_schema(): assert schema.index.name == index_info["index_name"] -def test_validate_modules_exist(): +def test_validate_modules_exist_search(): validate_modules( installed_modules={"search": 20811}, required_modules=[ @@ -112,8 +113,18 @@ def test_validate_modules_exist(): ) +def test_validate_modules_exist_searchlight(): + validate_modules( + installed_modules={"searchlight": 20819}, + required_modules=[ + {"name": "search", "ver": 20810}, + {"name": "searchlight", "ver": 20810}, + ], + ) + + def test_validate_modules_not_exist(): - with pytest.raises(ValueError): + with pytest.raises(RedisModuleVersionError): validate_modules( installed_modules={"search": 20811}, required_modules=[ diff --git a/tests/integration/test_llmcache.py b/tests/integration/test_llmcache.py index 4eb4d6f7..c2b80e8a 100644 --- a/tests/integration/test_llmcache.py +++ b/tests/integration/test_llmcache.py @@ -7,6 +7,7 @@ from pydantic.v1 import ValidationError from redis.exceptions import ConnectionError +from redisvl.exceptions import RedisModuleVersionError from redisvl.extensions.llmcache import SemanticCache from redisvl.index.index import AsyncSearchIndex, SearchIndex from redisvl.query.filter import Num, Tag, Text @@ -782,7 +783,8 @@ def test_index_updating(redis_url): ) assert response == [] - with pytest.raises(ValueError): + with pytest.raises((RedisModuleVersionError, ValueError)): + cache_with_tags = SemanticCache( name="test_cache", redis_url=redis_url, @@ -870,7 +872,8 @@ def test_bad_dtype_connecting_to_existing_cache(): try: cache = SemanticCache(name="float64_cache", dtype="float64") same_type = SemanticCache(name="float64_cache", dtype="float64") - except ValueError: + # under the hood uses from_existing + except RedisModuleVersionError: pytest.skip("Not using a late enough version of Redis") with pytest.raises(ValueError): diff --git a/tests/integration/test_semantic_router.py b/tests/integration/test_semantic_router.py index 194a6f98..8c1f6cf8 100644 --- a/tests/integration/test_semantic_router.py +++ b/tests/integration/test_semantic_router.py @@ -4,6 +4,7 @@ import pytest from redis.exceptions import ConnectionError +from redisvl.exceptions import RedisModuleVersionError from redisvl.extensions.router import SemanticRouter from redisvl.extensions.router.schema import Route, RoutingConfig from redisvl.redis.connection import compare_versions @@ -285,7 +286,8 @@ def test_bad_dtype_connecting_to_exiting_router(routes): routes=routes, dtype="float64", ) - except ValueError: + # under the hood uses from_existing + except RedisModuleVersionError: pytest.skip("Not using a late enough version of Redis") with pytest.raises(ValueError): diff --git a/tests/integration/test_session_manager.py b/tests/integration/test_session_manager.py index 898c3ab5..54267c54 100644 --- a/tests/integration/test_session_manager.py +++ b/tests/integration/test_session_manager.py @@ -3,6 +3,7 @@ import pytest from redis.exceptions import ConnectionError +from redisvl.exceptions import RedisModuleVersionError from redisvl.extensions.constants import ID_FIELD_NAME from redisvl.extensions.session_manager import ( SemanticSessionManager, @@ -566,7 +567,8 @@ def test_bad_dtype_connecting_to_exiting_session(): try: session = SemanticSessionManager(name="float64 session", dtype="float64") same_type = SemanticSessionManager(name="float64 session", dtype="float64") - except ValueError: + # under the hood uses from_existing + except RedisModuleVersionError: pytest.skip("Not using a late enough version of Redis") with pytest.raises(ValueError):