Coverage for aipyapp/aipy/plugins.py: 21%
96 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-11 12:02 +0200
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-11 12:02 +0200
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
4import os
5import importlib.util
6from typing import Dict, Any, List, Union, Optional, Type
7from pathlib import Path
9from loguru import logger
11from .. import __pkgpath__, Plugin, PluginType
13class PluginManager:
14 """Plugin manager class."""
15 FILE_PATTERN = "p_*.py"
17 def __init__(self):
18 self.sys_plugin_dir = os.path.join(__pkgpath__, 'plugins')
19 self.plugin_directories: List[Path] = []
20 self._plugins: Dict[str, Any] = {}
21 self.logger = logger.bind(src=self.__class__.__name__)
22 self.add_plugin_directory(self.sys_plugin_dir)
24 def __iter__(self):
25 return iter(self._plugins.values())
27 def __getitem__(self, name: str) -> Plugin:
28 return self._plugins[name]
30 def __len__(self) -> int:
31 return len(self._plugins)
33 def add_plugin_directory(self, directory: Union[str, Path]):
34 """Add a plugin directory to the plugin manager."""
35 dir_path = Path(directory)
36 if not dir_path.is_dir():
37 self.logger.warning(f"Plugin directory {directory} does not exist")
38 return False
39 if dir_path in self.plugin_directories:
40 self.logger.warning(f"Plugin directory {directory} already added")
41 return False
42 self.plugin_directories.append(dir_path)
43 self.logger.info(f"Added plugin directory {directory}")
44 return True
46 def _discover_plugins(self) -> List[Path]:
47 """Discover plugins in the plugin directories."""
48 plugin_files = []
49 for plugin_dir in self.plugin_directories:
50 if not os.path.exists(plugin_dir):
51 self.logger.warning(f"Plugin directory {plugin_dir} does not exist")
52 continue
53 for fname in plugin_dir.glob(self.FILE_PATTERN):
54 plugin_files.append(fname)
55 return plugin_files
57 def _load_plugins(self, filepath: Path) -> List[Plugin]:
58 """Load plugins from a file."""
59 import sys
61 name = filepath.stem
62 plugin_dir = filepath.parent
64 # 临时添加插件目录到 sys.path
65 original_path = sys.path.copy()
66 if str(plugin_dir) not in sys.path:
67 sys.path.insert(0, str(plugin_dir))
69 try:
70 spec = importlib.util.spec_from_file_location(name, filepath)
71 module = importlib.util.module_from_spec(spec)
72 spec.loader.exec_module(module)
74 plugins = []
75 for cls in module.__dict__.values():
76 if isinstance(cls, type) and issubclass(cls, Plugin) and cls.name:
77 plugins.append(cls)
78 return plugins
79 finally:
80 # 恢复原始 sys.path
81 sys.path = original_path
83 def _register_plugin(self, plugin: Plugin) -> bool:
84 """Register a plugin."""
85 plugin_name = plugin.name
86 if not plugin_name:
87 self.logger.warning(f"Plugin {plugin.__class__.__name__} has no name")
88 return False
89 if plugin_name in self._plugins:
90 self.logger.warning(f"Plugin {plugin_name} already registered")
91 return False
92 self._plugins[plugin_name] = plugin
93 return True
95 def load_all_plugins(self):
96 """Load all plugins."""
97 success_count = 0
98 plugin_files = self._discover_plugins()
99 for plugin_file in plugin_files:
100 plugins = self._load_plugins(plugin_file)
101 for plugin in plugins:
102 if self._register_plugin(plugin):
103 success_count += 1
104 self.logger.info(f"Loaded plugin {plugin.name} from {plugin_file}")
105 self.logger.info(f"Loaded {success_count} plugins")
107 def create_task_plugin(self, name: str, plugin_config: Dict[str, Any] = None) -> Optional[Plugin]:
108 """Create a plugin by name."""
109 plugin_cls = self._plugins.get(name)
110 if not plugin_cls:
111 self.logger.warning(f"Plugin {name} not found")
112 return
114 if plugin_cls.get_type() != PluginType.TASK:
115 self.logger.warning(f"Plugin {name} is not a task plugin")
116 return None
118 plugin = plugin_cls(plugin_config)
119 try:
120 plugin.init()
121 except Exception as e:
122 self.logger.error(f"Failed to initialize plugin {name}: {e}")
123 return None
124 return plugin
126 def get_task_plugins(self) -> List[Type[Plugin]]:
127 """Get all task plugins."""
128 return [plugin for plugin in self._plugins.values() if plugin.get_type() == PluginType.TASK]
130 def get_display_plugins(self) -> List[Type[Plugin]]:
131 """Get all display plugins."""
132 return [plugin for plugin in self._plugins.values() if plugin.get_type() == PluginType.DISPLAY]