1313)
1414from redis .exceptions import ResponseError
1515
16- from redisvl .redis .constants import REDIS_REQUIRED_MODULES
16+ from redisvl .redis .constants import DEFAULT_REQUIRED_MODULES
1717from redisvl .redis .utils import convert_bytes
1818from redisvl .version import __version__
1919
2020
21+ def unpack_redis_modules (module_list : List [Dict [str , Any ]]) -> Dict [str , Any ]:
22+ """Unpack a list of Redis modules pulled from the MODULES LIST command."""
23+ return {module ["name" ]: module ["ver" ] for module in module_list }
24+
25+
2126def get_address_from_env () -> str :
2227 """Get a redis connection from environment variables.
2328
@@ -43,6 +48,82 @@ def make_lib_name(*args) -> str:
4348 return f"redis-py({ custom_libs } )"
4449
4550
51+ def convert_index_info_to_schema (index_info : Dict [str , Any ]) -> Dict [str , Any ]:
52+ """Convert the output of FT.INFO into a schema-ready dictionary.
53+
54+ Args:
55+ index_info (Dict[str, Any]): Output of the Redis FT.INFO command.
56+
57+ Returns:
58+ Dict[str, Any]: Schema dictionary.
59+ """
60+ index_name = index_info ["index_name" ]
61+ prefixes = index_info ["index_definition" ][3 ][0 ]
62+ storage_type = index_info ["index_definition" ][1 ].lower ()
63+
64+ index_fields = index_info ["attributes" ]
65+
66+ def parse_vector_attrs (attrs ):
67+ vector_attrs = {attrs [i ].lower (): attrs [i + 1 ] for i in range (6 , len (attrs ), 2 )}
68+ vector_attrs ["dims" ] = int (vector_attrs .pop ("dim" ))
69+ vector_attrs ["distance_metric" ] = vector_attrs .pop ("distance_metric" ).lower ()
70+ vector_attrs ["algorithm" ] = vector_attrs .pop ("algorithm" ).lower ()
71+ vector_attrs ["datatype" ] = vector_attrs .pop ("data_type" ).lower ()
72+ return vector_attrs
73+
74+ def parse_attrs (attrs ):
75+ return {attrs [i ].lower (): attrs [i + 1 ] for i in range (6 , len (attrs ), 2 )}
76+
77+ schema_fields = []
78+
79+ for field_attrs in index_fields :
80+ # parse field info
81+ name = field_attrs [1 ] if storage_type == "hash" else field_attrs [3 ]
82+ field = {"name" : name , "type" : field_attrs [5 ].lower ()}
83+ if storage_type == "json" :
84+ field ["path" ] = field_attrs [1 ]
85+ # parse field attrs
86+ if field_attrs [5 ] == "VECTOR" :
87+ field ["attrs" ] = parse_vector_attrs (field_attrs )
88+ else :
89+ field ["attrs" ] = parse_attrs (field_attrs )
90+ # append field
91+ schema_fields .append (field )
92+
93+ return {
94+ "index" : {"name" : index_name , "prefix" : prefixes , "storage_type" : storage_type },
95+ "fields" : schema_fields ,
96+ }
97+
98+
99+ def validate_modules (
100+ installed_modules : Dict [str , Any ],
101+ required_modules : Optional [List [Dict [str , Any ]]] = None ,
102+ ) -> None :
103+ """
104+ Validates if required Redis modules are installed.
105+
106+ Args:
107+ installed_modules: List of installed modules.
108+ required_modules: List of required modules.
109+
110+ Raises:
111+ ValueError: If required Redis modules are not installed.
112+ """
113+ required_modules = required_modules or DEFAULT_REQUIRED_MODULES
114+
115+ for required_module in required_modules :
116+ if required_module ["name" ] in installed_modules :
117+ installed_version = installed_modules [required_module ["name" ]] # type: ignore
118+ if int (installed_version ) >= int (required_module ["ver" ]): # type: ignore
119+ return
120+
121+ raise ValueError (
122+ f"Required Redis database module { required_module ['name' ]} with version >= { required_module ['ver' ]} not installed. "
123+ "See Redis Stack documentation: https://redis.io/docs/stack/"
124+ )
125+
126+
46127class RedisConnectionFactory :
47128 """Builds connections to a Redis database, supporting both synchronous and
48129 asynchronous clients.
@@ -128,14 +209,14 @@ def get_async_redis_connection(url: Optional[str] = None, **kwargs) -> AsyncRedi
128209 def validate_redis (
129210 client : Union [Redis , AsyncRedis ],
130211 lib_name : Optional [str ] = None ,
131- redis_required_modules : Optional [List [Dict [str , Any ]]] = None ,
212+ required_modules : Optional [List [Dict [str , Any ]]] = None ,
132213 ) -> None :
133214 """Validates the Redis connection.
134215
135216 Args:
136217 client (Redis or AsyncRedis): Redis client.
137218 lib_name (str): Library name to set on the Redis client.
138- redis_required_modules (List[Dict[str, Any]]): List of required modules and their versions.
219+ required_modules (List[Dict[str, Any]]): List of required modules and their versions.
139220
140221 Raises:
141222 ValueError: If required Redis modules are not installed.
@@ -145,18 +226,26 @@ def validate_redis(
145226 RedisConnectionFactory ._validate_async_redis ,
146227 client ,
147228 lib_name ,
148- redis_required_modules ,
229+ required_modules ,
149230 )
150231 else :
151232 RedisConnectionFactory ._validate_sync_redis (
152- client , lib_name , redis_required_modules
233+ client , lib_name , required_modules
153234 )
154235
236+ @staticmethod
237+ def _get_modules (client : Redis ) -> Dict [str , Any ]:
238+ return unpack_redis_modules (convert_bytes (client .module_list ()))
239+
240+ @staticmethod
241+ async def _get_modules_async (client : AsyncRedis ) -> Dict [str , Any ]:
242+ return unpack_redis_modules (convert_bytes (await client .module_list ()))
243+
155244 @staticmethod
156245 def _validate_sync_redis (
157246 client : Redis ,
158247 lib_name : Optional [str ],
159- redis_required_modules : Optional [List [Dict [str , Any ]]],
248+ required_modules : Optional [List [Dict [str , Any ]]],
160249 ) -> None :
161250 """Validates the sync client."""
162251 # Set client library name
@@ -168,16 +257,16 @@ def _validate_sync_redis(
168257 client .echo (_lib_name )
169258
170259 # Get list of modules
171- modules_list = convert_bytes ( client . module_list () )
260+ installed_modules = RedisConnectionFactory . _get_modules ( client )
172261
173262 # Validate available modules
174- RedisConnectionFactory . _validate_modules ( modules_list , redis_required_modules )
263+ validate_modules ( installed_modules , required_modules )
175264
176265 @staticmethod
177266 async def _validate_async_redis (
178267 client : AsyncRedis ,
179268 lib_name : Optional [str ],
180- redis_required_modules : Optional [List [Dict [str , Any ]]],
269+ required_modules : Optional [List [Dict [str , Any ]]],
181270 ) -> None :
182271 """Validates the async client."""
183272 # Set client library name
@@ -189,10 +278,10 @@ async def _validate_async_redis(
189278 await client .echo (_lib_name )
190279
191280 # Get list of modules
192- modules_list = convert_bytes ( await client . module_list () )
281+ installed_modules = await RedisConnectionFactory . _get_modules_async ( client )
193282
194283 # Validate available modules
195- RedisConnectionFactory . _validate_modules ( modules_list , redis_required_modules )
284+ validate_modules ( installed_modules , required_modules )
196285
197286 @staticmethod
198287 def _run_async (coro , * args , ** kwargs ):
@@ -232,31 +321,3 @@ def _run_async(coro, *args, **kwargs):
232321 finally :
233322 # Close the event loop to release resources
234323 loop .close ()
235-
236- @staticmethod
237- def _validate_modules (
238- installed_modules , redis_required_modules : Optional [List [Dict [str , Any ]]] = None
239- ) -> None :
240- """
241- Validates if required Redis modules are installed.
242-
243- Args:
244- installed_modules: List of installed modules.
245- redis_required_modules: List of required modules.
246-
247- Raises:
248- ValueError: If required Redis modules are not installed.
249- """
250- installed_modules = {module ["name" ]: module for module in installed_modules }
251- redis_required_modules = redis_required_modules or REDIS_REQUIRED_MODULES
252-
253- for required_module in redis_required_modules :
254- if required_module ["name" ] in installed_modules :
255- installed_version = installed_modules [required_module ["name" ]]["ver" ]
256- if int (installed_version ) >= int (required_module ["ver" ]): # type: ignore
257- return
258-
259- raise ValueError (
260- f"Required Redis database module { required_module ['name' ]} with version >= { required_module ['ver' ]} not installed. "
261- "Refer to Redis Stack documentation: https://redis.io/docs/stack/"
262- )
0 commit comments