@@ -115,9 +115,20 @@ class MeilisearchEngine(SearchEngine):
115
115
compliant with edx-search's ElasticSearchEngine.
116
116
"""
117
117
118
- def __init__ (self , index = None ):
118
+ def __init__ (self , index = None ) -> None :
119
119
super ().__init__ (index = index )
120
- self .meilisearch_index = get_meilisearch_index (self .index_name )
120
+ self ._meilisearch_index : t .Optional [meilisearch .index .Index ] = None
121
+
122
+ @property
123
+ def meilisearch_index (self ) -> meilisearch .index .Index :
124
+ """
125
+ Lazy load meilisearch index.
126
+ """
127
+ if self ._meilisearch_index is None :
128
+ meilisearch_index_name = get_meilisearch_index_name (self .index_name )
129
+ meilisearch_client = get_meilisearch_client ()
130
+ self ._meilisearch_index = meilisearch_client .index (meilisearch_index_name )
131
+ return self ._meilisearch_index
121
132
122
133
@property
123
134
def meilisearch_index_name (self ):
@@ -211,7 +222,7 @@ def print_failed_meilisearch_tasks(count: int = 10):
211
222
print (result )
212
223
213
224
214
- def create_indexes (index_filterables : dict [str , list [str ]] = None ):
225
+ def create_indexes (index_filterables : t . Optional [ dict [str , list [str ] ]] = None ):
215
226
"""
216
227
This is an initialization function that creates indexes and makes sure that they
217
228
support the right facetting.
@@ -225,38 +236,68 @@ def create_indexes(index_filterables: dict[str, list[str]] = None):
225
236
client = get_meilisearch_client ()
226
237
for index_name , filterables in index_filterables .items ():
227
238
meilisearch_index_name = get_meilisearch_index_name (index_name )
228
- try :
229
- index = client .get_index (meilisearch_index_name )
230
- except meilisearch .errors .MeilisearchApiError as e :
231
- if e .code != "index_not_found" :
232
- raise
233
- client .create_index (
234
- meilisearch_index_name , {"primaryKey" : PRIMARY_KEY_FIELD_NAME }
235
- )
236
- # Get the index again
237
- index = client .get_index (meilisearch_index_name )
239
+ index = get_or_create_meilisearch_index (client , meilisearch_index_name )
240
+ update_index_filterables (client , index , filterables )
238
241
239
- # Update filterables if there are some new elements
240
- if filterables :
241
- existing_filterables = set (index .get_filterable_attributes ())
242
- if not set (filterables ).issubset (existing_filterables ):
243
- all_filterables = list (existing_filterables .union (filterables ))
244
- index .update_filterable_attributes (all_filterables )
245
242
243
+ def get_or_create_meilisearch_index (
244
+ client : meilisearch .Client , index_name : str
245
+ ) -> meilisearch .index .Index :
246
+ """
247
+ Get an index. If it does not exist, create it.
246
248
247
- def get_meilisearch_index (index_name : str ):
249
+ This will fail with a RuntimeError if we fail to create the index. It will fail with
250
+ a MeilisearchApiError in other failure cases.
248
251
"""
249
- Return a meilisearch index.
252
+ try :
253
+ return client .get_index (index_name )
254
+ except meilisearch .errors .MeilisearchApiError as e :
255
+ if e .code != "index_not_found" :
256
+ raise
257
+ task_info = client .create_index (
258
+ index_name , {"primaryKey" : PRIMARY_KEY_FIELD_NAME }
259
+ )
260
+ wait_for_task_to_succeed (client , task_info )
261
+ # Get the index again
262
+ return client .get_index (index_name )
250
263
251
- Note that the index may not exist, and it will be created on first insertion.
252
- ideally, the initialisation function `create_indexes` should be run first.
264
+
265
+ def update_index_filterables (
266
+ client : meilisearch .Client , index : meilisearch .index .Index , filterables : list [str ]
267
+ ) -> None :
253
268
"""
254
- meilisearch_client = get_meilisearch_client ()
255
- meilisearch_index_name = get_meilisearch_index_name (index_name )
256
- return meilisearch_client .index (meilisearch_index_name )
269
+ Make sure that the filterable fields of an index include the given list of fields.
270
+
271
+ If existing fields are present, they are preserved.
272
+ """
273
+ if not filterables :
274
+ return
275
+ existing_filterables = set (index .get_filterable_attributes ())
276
+ if set (filterables ).issubset (existing_filterables ):
277
+ # all filterables fields are already present
278
+ return
279
+ all_filterables = list (existing_filterables .union (filterables ))
280
+ task_info = index .update_filterable_attributes (all_filterables )
281
+ wait_for_task_to_succeed (client , task_info )
282
+
283
+
284
+ def wait_for_task_to_succeed (
285
+ client : meilisearch .Client ,
286
+ task_info : meilisearch .task .TaskInfo ,
287
+ timeout_in_ms : int = 5000 ,
288
+ ) -> None :
289
+ """
290
+ Wait for a Meilisearch task to succeed. If it does not, raise RuntimeError.
291
+ """
292
+ task = client .wait_for_task (task_info .task_uid , timeout_in_ms = timeout_in_ms )
293
+ if task .status != "succeeded" :
294
+ raise RuntimeError (f"Failed meilisearch task: { task } " )
257
295
258
296
259
297
def get_meilisearch_client ():
298
+ """
299
+ Return a Meilisearch client with the right settings.
300
+ """
260
301
return meilisearch .Client (MEILISEARCH_URL , api_key = MEILISEARCH_API_KEY )
261
302
262
303
@@ -332,7 +373,7 @@ def get_search_params(
332
373
Return a dictionary of parameters that should be passed to the Meilisearch client
333
374
`.search()` method.
334
375
"""
335
- params = {"showRankingScore" : True }
376
+ params : dict [ str , t . Any ] = {"showRankingScore" : True }
336
377
337
378
# Aggregation
338
379
if aggregation_terms :
0 commit comments