alkaid_release_platform/core/plugin.py

219 lines
8.5 KiB
Python
Raw Normal View History

# core/plugin.py
from abc import ABC, abstractmethod
import importlib
from enum import Enum
import os,sys
import pkgutil
import inspect
from typing import Dict, List, Type
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from core.behavior_tree import ReleaseFlowAction
from core.defines import ReleasePhase, ProcessStep, Dict
import core.logger as logger
log = logger.get_logger()
"""
# format: PluginName.ReleaseStep.ProcessStep
ReleaseFlowCommon.StepEnv.Pre
"""
class PluginType(str, Enum):
"""
为插件分类目前仅有一种插件FLOW类型的插件用于执行Release Flow
"""
FLOW = 'flow'
class PluginInterface(ABC):
"""PluginInterface 插件基类"""
def __init__(self, type: PluginType):
self.type = type
def get_type(self) -> PluginType:
return self.type
@abstractmethod
def get_version(self) -> str:
pass
@abstractmethod
def get_description(self) -> str:
"""返回插件描述"""
pass
class ReleaseFlowPlugin(PluginInterface, ABC):
"""ReleaseFlowPlugin 插件基类"""
def __init__(self):
PluginInterface.__init__(self, PluginType.FLOW)
self.btrees:Dict = Dict()
@abstractmethod
def get_version(self) -> str:
"""返回插件版本"""
pass
@abstractmethod
def get_description(self) -> str:
"""返回插件描述"""
pass
# 支持使用.操作符访问btrees
def __getattr__(self, name: str) -> Dict:
return self.btrees[name]
def ReleaseFlowPluginDecorator(cls):
"""装饰器用于自动注册ReleaseFlow插件 每个 ReleaseFlowPlugin 插件上加上这个装饰器, 插件初始化时会自动搜索并注册行为树
Args:
cls: 插件类
Returns:
插件类
"""
original_init = cls.__init__
# 如果不是ReleaseFlowPlugin的子类则不进行装饰
if not issubclass(cls, ReleaseFlowPlugin):
return cls
def behavior_tree_register(self: ReleaseFlowPlugin):
"""
搜索当前目录下的所有行为树节点并根据命名注册到btrees中
"""
def _load_actions_from_package(self, package_name: str):
"""从包中加载插件"""
package = importlib.import_module(package_name)
for _, name, is_pkg in pkgutil.iter_modules(package.__path__, f"{package_name}."):
if not is_pkg:
_load_actions(self, name)
else:
_load_actions_from_package(self, f"{package_name}.{name}")
def _load_actions(self, module_name: str):
"""加载单个behavior action"""
# log.debug(f"Try to check action: {module_name}")
module = importlib.import_module(module_name)
# 遍历模块中的所有属性名称
for attribute_name in dir(module):
attribute = getattr(module, attribute_name)
if not isinstance(attribute, type): # 检查是否是类
continue
if not issubclass(attribute, ReleaseFlowAction): # 检查是否是插件类
continue
if inspect.isabstract(attribute): # 检查是否是抽象类
continue
log.info(f"Loaded action: {attribute.__name__}")
self.sub_actions[attribute.__name__] = attribute
def discover_actions(self:ReleaseFlowPlugin):
module = inspect.getmodule(self)
path = os.path.relpath(os.path.dirname(module.__file__)).replace('/', '.')
# log.debug(f"Try to search action in: {path}")
self.sub_actions = {}
for _, name, is_pkg in pkgutil.iter_modules([os.path.dirname(module.__file__)]):
# log.trace(f"Try to load action: {name}")
if is_pkg:
_load_actions_from_package(self, f"{path}.{name}")
else:
_load_actions(self, f"{path}.{name}")
if not isinstance(self, ReleaseFlowPlugin):
log.warning(f"Plugin class {self.__class__.__name__} is not a ReleaseFlowPlugin")
raise ReleaseErr("The plugin class decorator is not a ReleaseFlowPlugin")
discover_actions(self)
for phase in ReleasePhase:
phase = phase.value
self.btrees[phase] = Dict()
for step in ProcessStep:
step = step.value
action_class = f"_{phase}_{step}"
if not self.sub_actions.get(action_class):
log.trace(f"Action {action_class} not found")
continue
log.debug(f"Register ReleaseFlow behavior tree node: {action_class}")
if not self.btrees[phase].get(step):
self.btrees[phase][step] = self.sub_actions[action_class]
else:
log.warning(f"Behavior tree node {action_class} already exists: {self.btrees[phase][step]}")
def new_init(self, *args, **kwargs):
original_init(self, *args, **kwargs)
behavior_tree_register(self)
# 添加标记表示该类使用了flow action 装饰器,用于检查是否注册了行为树
cls._is_action_registered = True
cls.__init__ = new_init
return cls
class PluginManager:
"""插件管理器"""
def __init__(self, plugin_dir: str = "plugins"):
self.plugin_dir = plugin_dir
self.plugins: Dict[str, Type[PluginInterface]] = {}
self.loaded_plugins: Dict[str, PluginInterface] = {}
self.discover_plugins()
def discover_plugins(self):
"""发现并加载所有插件"""
for _, name, is_pkg in pkgutil.iter_modules([self.plugin_dir]):
plugin_dir = f"{self.plugin_dir}.{name}"
log.trace(f"Try to search plugin in: {plugin_dir}")
if is_pkg:
self._load_plugins_from_package(plugin_dir)
else:
self._load_plugin(plugin_dir)
def _load_plugins_from_package(self, package_name: str):
"""从包中加载插件"""
package = importlib.import_module(package_name)
for _, name, is_pkg in pkgutil.iter_modules(package.__path__, f"{package_name}."):
if not is_pkg:
self._load_plugin(name)
else:
self._load_plugins_from_package(f"{package_name}.{name}")
def _load_plugin(self, module_name: str):
"""加载单个插件"""
module = importlib.import_module(module_name)
for attribute_name in dir(module):
plugin_class = getattr(module, attribute_name)
if not isinstance(plugin_class, type): # 检查是否是类
continue
if not issubclass(plugin_class, PluginInterface): # 检查是否是插件类
continue
if inspect.isabstract(plugin_class): # 检查是否是抽象类
continue
# 检查插件类是否使用了 ReleaseFlowActionRegister 装饰器
if issubclass(plugin_class, ReleaseFlowPlugin) and not hasattr(plugin_class, '_is_action_registered'):
log.warning(f"Plugin class {plugin_class.__name__} does not have the ReleaseFlowActionRegister decorator.")
continue
log.info(f"Loaded plugin: {plugin_class.__name__}")
self.plugins[plugin_class.__name__] = plugin_class
def get_plugin(self, name: str) -> PluginInterface:
"""获取插件实例, 每个插件实例只初始化一次
Args:
name: 插件名称
Returns:
插件实例
"""
if name not in self.loaded_plugins:
if name in self.plugins:
self.loaded_plugins[name] = self.plugins[name]()
else:
raise ValueError(f"Plugin {name} not found")
return self.loaded_plugins[name]
def get_available_plugins(self) -> List[str]:
"""获取所有可用插件名称"""
return list(self.plugins.keys())
def __str__(self) -> str:
string = ""
string += f"Plugin directory: {self.plugin_dir}\n"
string += "Available plugins:"
string += "[" + " \n".join(self.plugins.keys()) + "]\n"
string += "Loaded plugins:"
string += "[" + " \n".join(self.loaded_plugins.keys()) + "]\n"
return string
def __repr__(self) -> str:
return str(self)
def __getattr__(self, name: str) -> PluginInterface:
# 获取插件实例
plugin = self.get_plugin(name)
return plugin