13
13
)
14
14
from redis .exceptions import ResponseError
15
15
16
- from redisvl .redis .constants import REDIS_REQUIRED_MODULES
16
+ from redisvl .redis .constants import DEFAULT_REQUIRED_MODULES
17
17
from redisvl .redis .utils import convert_bytes
18
18
from redisvl .version import __version__
19
19
20
20
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
+
21
26
def get_address_from_env () -> str :
22
27
"""Get a redis connection from environment variables.
23
28
@@ -43,6 +48,82 @@ def make_lib_name(*args) -> str:
43
48
return f"redis-py({ custom_libs } )"
44
49
45
50
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
+
46
127
class RedisConnectionFactory :
47
128
"""Builds connections to a Redis database, supporting both synchronous and
48
129
asynchronous clients.
@@ -128,14 +209,14 @@ def get_async_redis_connection(url: Optional[str] = None, **kwargs) -> AsyncRedi
128
209
def validate_redis (
129
210
client : Union [Redis , AsyncRedis ],
130
211
lib_name : Optional [str ] = None ,
131
- redis_required_modules : Optional [List [Dict [str , Any ]]] = None ,
212
+ required_modules : Optional [List [Dict [str , Any ]]] = None ,
132
213
) -> None :
133
214
"""Validates the Redis connection.
134
215
135
216
Args:
136
217
client (Redis or AsyncRedis): Redis client.
137
218
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.
139
220
140
221
Raises:
141
222
ValueError: If required Redis modules are not installed.
@@ -145,18 +226,26 @@ def validate_redis(
145
226
RedisConnectionFactory ._validate_async_redis ,
146
227
client ,
147
228
lib_name ,
148
- redis_required_modules ,
229
+ required_modules ,
149
230
)
150
231
else :
151
232
RedisConnectionFactory ._validate_sync_redis (
152
- client , lib_name , redis_required_modules
233
+ client , lib_name , required_modules
153
234
)
154
235
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
+
155
244
@staticmethod
156
245
def _validate_sync_redis (
157
246
client : Redis ,
158
247
lib_name : Optional [str ],
159
- redis_required_modules : Optional [List [Dict [str , Any ]]],
248
+ required_modules : Optional [List [Dict [str , Any ]]],
160
249
) -> None :
161
250
"""Validates the sync client."""
162
251
# Set client library name
@@ -168,16 +257,16 @@ def _validate_sync_redis(
168
257
client .echo (_lib_name )
169
258
170
259
# Get list of modules
171
- modules_list = convert_bytes ( client . module_list () )
260
+ installed_modules = RedisConnectionFactory . _get_modules ( client )
172
261
173
262
# Validate available modules
174
- RedisConnectionFactory . _validate_modules ( modules_list , redis_required_modules )
263
+ validate_modules ( installed_modules , required_modules )
175
264
176
265
@staticmethod
177
266
async def _validate_async_redis (
178
267
client : AsyncRedis ,
179
268
lib_name : Optional [str ],
180
- redis_required_modules : Optional [List [Dict [str , Any ]]],
269
+ required_modules : Optional [List [Dict [str , Any ]]],
181
270
) -> None :
182
271
"""Validates the async client."""
183
272
# Set client library name
@@ -189,10 +278,10 @@ async def _validate_async_redis(
189
278
await client .echo (_lib_name )
190
279
191
280
# Get list of modules
192
- modules_list = convert_bytes ( await client . module_list () )
281
+ installed_modules = await RedisConnectionFactory . _get_modules_async ( client )
193
282
194
283
# Validate available modules
195
- RedisConnectionFactory . _validate_modules ( modules_list , redis_required_modules )
284
+ validate_modules ( installed_modules , required_modules )
196
285
197
286
@staticmethod
198
287
def _run_async (coro , * args , ** kwargs ):
@@ -232,31 +321,3 @@ def _run_async(coro, *args, **kwargs):
232
321
finally :
233
322
# Close the event loop to release resources
234
323
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