@@ -344,16 +344,21 @@ def _load_single_plugin(self, plugin_dir: Path):
344344 with open (manifest_path , 'r' ) as f :
345345 manifest_data = yaml .safe_load (f )
346346 manifest = PluginManifest (** manifest_data )
347-
347+
348+ # Check dependencies before loading
349+ if not self ._check_dependencies (plugin_dir ):
350+ logger .warning (f"Skipping plugin { manifest .id } : dependencies not installed" )
351+ return
352+
348353 # Add plugin src to sys.path so it can import its own modules
349354 # We add the root of the plugin directory
350355 if str (plugin_dir ) not in sys .path :
351356 sys .path .insert (0 , str (plugin_dir ))
352-
357+
353358 # Dynamic Import
354359 entry_path_dir = plugin_dir / manifest .entry_point .replace ("." , "/" ) / "__init__.py"
355360 entry_path_file = plugin_dir / f"{ manifest .entry_point .replace ('.' , '/' )} .py"
356-
361+
357362 spec = None
358363 if entry_path_dir .exists ():
359364 spec = importlib .util .spec_from_file_location (
@@ -365,15 +370,15 @@ def _load_single_plugin(self, plugin_dir: Path):
365370 f"plugins.{ manifest .id } " ,
366371 entry_path_file
367372 )
368-
373+
369374 if spec and spec .loader :
370375 module = importlib .util .module_from_spec (spec )
371376 sys .modules [f"plugins.{ manifest .id } " ] = module
372377 spec .loader .exec_module (module )
373-
378+
374379 self .loaded_plugins [manifest .id ] = module
375380 self .plugin_manifests [manifest .id ] = manifest
376-
381+
377382 # Register Extensions
378383 self ._register_extensions (manifest , module )
379384 logger .info (f"Loaded plugin: { manifest .name } ({ manifest .version } )" )
@@ -390,14 +395,50 @@ def _register_extensions(self, manifest: PluginManifest, module: Any):
390395 for ext_point , items in manifest .extensions .items ():
391396 if ext_point not in self .extensions :
392397 self .extensions [ext_point ] = {}
393-
398+
394399 for key , class_name in items .items ():
395400 if hasattr (module , class_name ):
396401 cls = getattr (module , class_name )
397402 self .extensions [ext_point ][key ] = cls
398403 else :
399404 logger .warning (f"Plugin { manifest .id } declares { class_name } for { ext_point } , but class not found in module." )
400405
406+ def _check_dependencies (self , plugin_dir : Path ) -> bool :
407+ """
408+ Checks if the plugin's dependencies are installed by attempting to import key packages.
409+ """
410+ requirements_path = plugin_dir / "requirements.txt"
411+ if not requirements_path .exists ():
412+ return True # No requirements file, assume dependencies are satisfied
413+
414+ try :
415+ with open (requirements_path , 'r' ) as f :
416+ requirements = f .read ().splitlines ()
417+
418+ # Extract package names (before any version specifiers)
419+ packages_to_check = []
420+ for req in requirements :
421+ req = req .strip ()
422+ if req and not req .startswith ('#' ):
423+ # Extract package name (e.g., 'scrapy==2.11.1' -> 'scrapy')
424+ package_name = req .split ('==' )[0 ].split ('>=' )[0 ].split ('>' )[0 ].split ('<' )[0 ].split ('<=' )[0 ].split ('!=' )[0 ].strip ()
425+ if package_name :
426+ packages_to_check .append (package_name )
427+
428+ # Try to import each package
429+ for package in packages_to_check :
430+ try :
431+ importlib .import_module (package )
432+ except ImportError :
433+ logger .debug (f"Dependency check failed for { package } in plugin { plugin_dir .name } " )
434+ return False
435+
436+ return True
437+
438+ except Exception as e :
439+ logger .warning (f"Error checking dependencies for plugin { plugin_dir .name } : { e } " )
440+ return False
441+
401442 def get_extensions (self , extension_point : str ) -> Dict [str , Any ]:
402443 """
403444 Returns a dictionary of {key: ImplementationClass} for a given extension point.
0 commit comments