alkaid_release_platform/core/plugin.py
ekko.bao 0a0f6a6054 初次创建仓库提交代码
1. 已经构建好了架子了。
2. 添加了示例的插件
2025-04-21 06:37:06 +00:00

219 lines
8.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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