fix: Fix plugin import path for installed plugins

The issue was that installed plugins (calculator, clock_widget, etc.)
could not import 'plugins.base_plugin' because the plugins package
wasn't properly in Python's module path when loading via
spec_from_file_location.

FIX:
1. Added project root to sys.path so 'plugins' package is findable
2. Added _ensure_plugins_package() method to preload plugins module
3. Modified discover_plugins() to try normal import first
4. Added proper error logging with traceback for debugging

This ensures installed plugins can properly import BasePlugin
from plugins.base_plugin as expected.
This commit is contained in:
LemonNexus 2026-02-15 16:06:45 +00:00
parent 2dd9392694
commit 7a3aa9b7f1
1 changed files with 44 additions and 10 deletions

View File

@ -29,12 +29,23 @@ class PluginManager:
self.plugin_classes: Dict[str, Type[BasePlugin]] = {} self.plugin_classes: Dict[str, Type[BasePlugin]] = {}
self.config = self._load_config() self.config = self._load_config()
# Add plugin dirs to path # Add plugin dirs and project root to path
# Project root needed for 'plugins.base_plugin' imports
project_root = Path(__file__).parent.parent.absolute()
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))
for plugin_dir in self.PLUGIN_DIRS: for plugin_dir in self.PLUGIN_DIRS:
path = Path(plugin_dir).absolute() path = Path(plugin_dir).absolute()
if path.exists() and str(path) not in sys.path: if path.exists() and str(path) not in sys.path:
sys.path.insert(0, str(path)) sys.path.insert(0, str(path))
# Ensure plugins package is loaded
try:
import plugins
except ImportError:
pass # plugins package will be loaded when needed
def _load_config(self) -> dict: def _load_config(self) -> dict:
"""Load plugin configuration.""" """Load plugin configuration."""
config_path = Path("config/plugins.json") config_path = Path("config/plugins.json")
@ -90,6 +101,9 @@ class PluginManager:
"""Discover all available plugin classes with error handling.""" """Discover all available plugin classes with error handling."""
discovered = [] discovered = []
# First, ensure plugins package is loaded
self._ensure_plugins_package()
for plugin_dir in self.PLUGIN_DIRS: for plugin_dir in self.PLUGIN_DIRS:
base_path = Path(plugin_dir) base_path = Path(plugin_dir)
if not base_path.exists(): if not base_path.exists():
@ -110,20 +124,26 @@ class PluginManager:
continue continue
try: try:
# Load the plugin module # Load the plugin module using normal import
# This ensures proper package resolution
module_name = f"{plugin_dir}.{item.name}.plugin" module_name = f"{plugin_dir}.{item.name}.plugin"
if module_name in sys.modules: if module_name in sys.modules:
module = sys.modules[module_name] module = sys.modules[module_name]
else: else:
spec = importlib.util.spec_from_file_location( # Try normal import first
module_name, plugin_file try:
) module = importlib.import_module(module_name)
if not spec or not spec.loader: except ImportError:
continue # Fall back to spec loading
module = importlib.util.module_from_spec(spec) spec = importlib.util.spec_from_file_location(
sys.modules[module_name] = module module_name, plugin_file
spec.loader.exec_module(module) )
if not spec or not spec.loader:
continue
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
# Find plugin class # Find plugin class
for attr_name in dir(module): for attr_name in dir(module):
@ -137,10 +157,24 @@ class PluginManager:
except Exception as e: except Exception as e:
print(f"[PluginManager] Failed to discover {item.name}: {e}") print(f"[PluginManager] Failed to discover {item.name}: {e}")
import traceback
traceback.print_exc()
continue continue
return discovered return discovered
def _ensure_plugins_package(self):
"""Ensure the plugins package is loaded in sys.modules."""
if 'plugins' not in sys.modules:
try:
import plugins
except ImportError:
# Create a minimal plugins module if it doesn't exist
import types
plugins_module = types.ModuleType('plugins')
plugins_module.__path__ = [str(Path('plugins').absolute())]
sys.modules['plugins'] = plugins_module
def load_plugin(self, plugin_class: Type[BasePlugin]) -> bool: def load_plugin(self, plugin_class: Type[BasePlugin]) -> bool:
"""Instantiate and initialize a plugin with error handling.""" """Instantiate and initialize a plugin with error handling."""
try: try: