88
99from __future__ import annotations
1010
11+ import functools
1112import logging
1213import queue
1314import threading
15+ from importlib .metadata import entry_points
1416from pathlib import Path
1517from typing import Type
1618
1719from murfey .client .context import Context
18- from murfey .client .contexts .atlas import AtlasContext
19- from murfey .client .contexts .clem import CLEMContext
20- from murfey .client .contexts .fib import FIBContext
21- from murfey .client .contexts .spa import SPAContext
22- from murfey .client .contexts .spa_metadata import SPAMetadataContext
23- from murfey .client .contexts .sxt import SXTContext
24- from murfey .client .contexts .tomo import TomographyContext
25- from murfey .client .contexts .tomo_metadata import TomographyMetadataContext
2620from murfey .client .destinations import find_longest_data_directory
2721from murfey .client .instance_environment import MurfeyInstanceEnvironment
2822from murfey .client .rsync import RSyncerUpdate , TransferResult
3327logger = logging .getLogger ("murfey.client.analyser" )
3428
3529
30+ # Load the Context entry points as a list upon initialisation
31+ context_eps = list (entry_points (group = "murfey.contexts" ))
32+
33+
34+ @functools .lru_cache (maxsize = 1 )
35+ def _get_context (name : str ):
36+ """
37+ Load the desired context from the configured list of entry points.
38+ Returns None if the entry point is not found
39+ """
40+ if context := [ep for ep in context_eps if ep .name == name ]:
41+ return context [0 ]
42+ else :
43+ logger .warning (f"Could not find entry point for { name !r} " )
44+ return None
45+
46+
3647class Analyser (Observer ):
3748 def __init__ (
3849 self ,
@@ -145,7 +156,9 @@ def _find_context(self, file_path: Path) -> bool:
145156 )
146157 )
147158 ):
148- self ._context = CLEMContext (
159+ if (context := _get_context ("CLEMContext" )) is None :
160+ return False
161+ self ._context = context .load ()(
149162 "leica" ,
150163 self ._basepath ,
151164 self ._murfey_config ,
@@ -166,7 +179,9 @@ def _find_context(self, file_path: Path) -> bool:
166179 and "Sites" in file_path .parts
167180 )
168181 ):
169- self ._context = FIBContext (
182+ if (context := _get_context ("FIBContext" )) is None :
183+ return False
184+ self ._context = context .load ()(
170185 "autotem" ,
171186 self ._basepath ,
172187 self ._murfey_config ,
@@ -183,7 +198,9 @@ def _find_context(self, file_path: Path) -> bool:
183198 all (path in file_path .parts for path in ("LayersData" , "Layer" ))
184199 )
185200 ):
186- self ._context = FIBContext (
201+ if (context := _get_context ("FIBContext" )) is None :
202+ return False
203+ self ._context = context .load ()(
187204 "maps" ,
188205 self ._basepath ,
189206 self ._murfey_config ,
@@ -196,7 +213,9 @@ def _find_context(self, file_path: Path) -> bool:
196213 # Image metadata stored in "features.json" file
197214 file_path .name == "features.json" or ()
198215 ):
199- self ._context = FIBContext (
216+ if (context := _get_context ("FIBContext" )) is None :
217+ return False
218+ self ._context = context .load ()(
200219 "meteor" ,
201220 self ._basepath ,
202221 self ._murfey_config ,
@@ -208,7 +227,9 @@ def _find_context(self, file_path: Path) -> bool:
208227 # SXT workflow checks
209228 # -----------------------------------------------------------------------------
210229 if file_path .suffix in (".txrm" , ".xrm" ):
211- self ._context = SXTContext (
230+ if (context := _get_context ("SXTContext" )) is None :
231+ return False
232+ self ._context = context .load ()(
212233 "zeiss" ,
213234 self ._basepath ,
214235 self ._murfey_config ,
@@ -220,7 +241,9 @@ def _find_context(self, file_path: Path) -> bool:
220241 # Tomography and SPA workflow checks
221242 # -----------------------------------------------------------------------------
222243 if "atlas" in file_path .parts :
223- self ._context = AtlasContext (
244+ if (context := _get_context ("AtlasContext" )) is None :
245+ return False
246+ self ._context = context .load ()(
224247 "serialem" if self ._serialem else "epu" ,
225248 self ._basepath ,
226249 self ._murfey_config ,
@@ -229,7 +252,9 @@ def _find_context(self, file_path: Path) -> bool:
229252 return True
230253
231254 if "Metadata" in file_path .parts or file_path .name == "EpuSession.dm" :
232- self ._context = SPAMetadataContext (
255+ if (context := _get_context ("SPAMetadataContext" )) is None :
256+ return False
257+ self ._context = context .load ()(
233258 "epu" ,
234259 self ._basepath ,
235260 self ._murfey_config ,
@@ -242,7 +267,9 @@ def _find_context(self, file_path: Path) -> bool:
242267 or "Thumbnails" in file_path .parts
243268 or file_path .name == "Session.dm"
244269 ):
245- self ._context = TomographyMetadataContext (
270+ if (context := _get_context ("TomographyMetadataContext" )) is None :
271+ return False
272+ self ._context = context .load ()(
246273 "tomo" ,
247274 self ._basepath ,
248275 self ._murfey_config ,
@@ -263,7 +290,9 @@ def _find_context(self, file_path: Path) -> bool:
263290 ]:
264291 if not self ._context :
265292 logger .info ("Acquisition software: EPU" )
266- self ._context = SPAContext (
293+ if (context := _get_context ("SPAContext" )) is None :
294+ return False
295+ self ._context = context .load ()(
267296 "epu" ,
268297 self ._basepath ,
269298 self ._murfey_config ,
@@ -282,7 +311,9 @@ def _find_context(self, file_path: Path) -> bool:
282311 ):
283312 if not self ._context :
284313 logger .info ("Acquisition software: tomo" )
285- self ._context = TomographyContext (
314+ if (context := _get_context ("TomographyContext" )) is None :
315+ return False
316+ self ._context = context .load ()(
286317 "tomo" ,
287318 self ._basepath ,
288319 self ._murfey_config ,
@@ -322,24 +353,26 @@ def _analyse(self):
322353 or transferred_file .name == "EpuSession.dm"
323354 and not self ._context
324355 ):
325- self ._context = SPAMetadataContext (
326- "epu" ,
327- self ._basepath ,
328- self ._murfey_config ,
329- self ._token ,
330- )
356+ if context := _get_context ("SPAMetadataContext" ):
357+ self ._context = context .load ()(
358+ "epu" ,
359+ self ._basepath ,
360+ self ._murfey_config ,
361+ self ._token ,
362+ )
331363 elif (
332364 "Batch" in transferred_file .parts
333365 or "SearchMaps" in transferred_file .parts
334366 or transferred_file .name == "Session.dm"
335367 and not self ._context
336368 ):
337- self ._context = TomographyMetadataContext (
338- "tomo" ,
339- self ._basepath ,
340- self ._murfey_config ,
341- self ._token ,
342- )
369+ if context := _get_context ("TomographyMetadataContext" ):
370+ self ._context = context .load ()(
371+ "tomo" ,
372+ self ._basepath ,
373+ self ._murfey_config ,
374+ self ._token ,
375+ )
343376 self .post_transfer (transferred_file )
344377 else :
345378 dc_metadata = {}
@@ -386,7 +419,7 @@ def _analyse(self):
386419 )
387420 except Exception as e :
388421 logger .error (f"Exception encountered: { e } " )
389- if not isinstance (self ._context , AtlasContext ):
422+ if "AtlasContext" not in str (self ._context ):
390423 if not dc_metadata :
391424 try :
392425 dc_metadata = self ._context .gather_metadata (
@@ -417,24 +450,21 @@ def _analyse(self):
417450 )
418451 self .notify (dc_metadata )
419452
420- # If a file with a CLEM context is identified, immediately post it
421- elif isinstance (self ._context , CLEMContext ):
453+ # Ccontexts that can be immediately posted without additional work
454+ elif "CLEMContext" not in str (self ._context ):
422455 logger .debug (
423- f"File { transferred_file .name !r} will be processed as part of CLEM workflow"
456+ f"File { transferred_file .name !r} is part of CLEM workflow"
424457 )
425458 self .post_transfer (transferred_file )
426-
427- elif isinstance (self ._context , FIBContext ):
459+ elif "FIBContext" not in str (self ._context ):
428460 logger .debug (
429- f"File { transferred_file .name !r} will be processed as part of the FIB workflow"
461+ f"File { transferred_file .name !r} is part of the FIB workflow"
430462 )
431463 self .post_transfer (transferred_file )
432-
433- elif isinstance (self ._context , SXTContext ):
464+ elif "SXTContext" not in str (self ._context ):
434465 logger .debug (f"File { transferred_file .name !r} is an SXT file" )
435466 self .post_transfer (transferred_file )
436-
437- elif isinstance (self ._context , AtlasContext ):
467+ elif "AtlasContext" not in str (self ._context ):
438468 logger .debug (f"File { transferred_file .name !r} is part of the atlas" )
439469 self .post_transfer (transferred_file )
440470
@@ -480,14 +510,14 @@ def _analyse(self):
480510 self ._context ._acquisition_software
481511 )
482512 self .notify (dc_metadata )
483- elif isinstance (
484- self ._context ,
485- (
486- SPAContext ,
487- SPAMetadataContext ,
488- TomographyContext ,
489- TomographyMetadataContext ,
490- ),
513+ elif any (
514+ context in str ( self ._context )
515+ for context in (
516+ " SPAContext" ,
517+ " SPAMetadataContext" ,
518+ " TomographyContext" ,
519+ " TomographyMetadataContext" ,
520+ )
491521 ):
492522 context = str (self ._context ).split (" " )[0 ].split ("." )[- 1 ]
493523 logger .debug (
0 commit comments