翼度科技»论坛 编程开发 python 查看内容

python实现一个通用的插件类

5

主题

5

帖子

15

积分

新手上路

Rank: 1

积分
15
本文提供了一种插件类的实现方案。

定义插件管理器

插件管理器用于注册、销毁、执行插件。
  1. import abc
  2. from functools import wraps
  3. from typing import Callable, Dict

  4. from pydantic import (
  5.     BaseModel,
  6.     validate_arguments,
  7.     ValidationError as PydanticValidationError,
  8. )

  9. def import_string(dotted_path: str) -> Callable:
  10.     """Import a dotted module path and return the attribute/class designated by the
  11.     last name in the path. Raise ImportError if the import failed.

  12.     Args:
  13.         dotted_path: 字符串表示的模块类,module.class
  14.     Returns:
  15.         返回加载的模块中的对象
  16.     """
  17.     try:
  18.         module_path, class_name = dotted_path.rsplit(".", 1)
  19.     except ValueError:
  20.         raise ImportError("{} doesn't look like a module path".format(dotted_path))

  21.     module: ModuleType = import_module(module_path)

  22.     try:
  23.         # 返回模块中的类
  24.         return getattr(module, class_name)
  25.     except AttributeError:
  26.         raise ImportError(
  27.             'Module "{}" does not define a "{}" attribute/class'.format(
  28.                 module_path, class_name
  29.             )
  30.         )


  31. class FunctionsManager:
  32.     """函数管理器 ."""
  33.     # 存放注册的可执行对象
  34.     __hub = {}  # type: ignore

  35.     @classmethod
  36.     def register_invocation_cls(cls, invocation_cls: InvocationMeta, name=None) -> None:
  37.         if not name:
  38.             func_name = invocation_cls.Meta.func_name
  39.         else:
  40.             func_name = name
  41.         if not isinstance(func_name, str):
  42.             raise ValueError(f"func_name {func_name} should be string")
  43.         existed_invocation_cls = cls.__hub.get(func_name)
  44.         if existed_invocation_cls:
  45.             raise RuntimeError(
  46.                 "func register error, {}'s func_name {} conflict with {}".format(
  47.                     existed_invocation_cls, func_name, invocation_cls
  48.                 )
  49.             )

  50.         # 存放类的实例
  51.         cls.__hub[func_name] = invocation_cls()

  52.     @classmethod
  53.     def register_funcs(cls, func_dict) -> None:
  54.         for func_name, func_obj in func_dict.items():
  55.             if not isinstance(func_name, str):
  56.                 raise ValueError(f"func_name {func_name} should be string")
  57.             if func_name in cls.__hub:
  58.                 raise ValueError(
  59.                     "func register error, {}'s func_name {} conflict with {}".format(
  60.                         func_obj, func_name, cls.__hub[func_name]
  61.                     )
  62.                 )
  63.             if isinstance(func_obj, str):
  64.                 func = import_string(func_obj)
  65.             elif isinstance(func_obj, Callable):
  66.                 func = func_obj
  67.             else:
  68.                 raise ValueError(
  69.                     "func register error, {} is not be callable".format(
  70.                         func_obj, func_name
  71.                     )
  72.                 )

  73.             cls.__hub[func_name] = func

  74.     @classmethod
  75.     def clear(cls) -> None:
  76.         """清空注册信息 ."""
  77.         cls.__hub = {}

  78.     @classmethod
  79.     def all_funcs(cls) -> Dict:
  80.         """获得所有的注册信息. """
  81.         return cls.__hub

  82.     @classmethod
  83.     def get_func(cls, func_name: str) -> Callable:
  84.         """获得注册的函数 ."""
  85.         func_obj = cls.__hub.get(func_name)
  86.         if not func_obj:
  87.             raise ValueError("func object {} not found".format(func_name))
  88.         return func_obj

  89.     @classmethod
  90.     def func_call(cls, func_name: str, *args, **kwargs):
  91.         """根据函数名执行注册的函数 ."""
  92.         func = cls.get_func(func_name)
  93.         return func(*args, **kwargs)
复制代码
定义元类

派生的类可自行注册到插件管理器。
  1. class InvocationMeta(type):
  2.     """
  3.     Metaclass for function invocation
  4.     """

  5.     def __new__(cls, name, bases, dct):
  6.         # ensure initialization is only performed for subclasses of Plugin
  7.         parents = [b for b in bases if isinstance(b, InvocationMeta)]
  8.         if not parents:
  9.             return super().__new__(cls, name, bases, dct)

  10.         new_cls = super().__new__(cls, name, bases, dct)

  11.         # meta validation
  12.         meta_obj = getattr(new_cls, "Meta", None)
  13.         if not meta_obj:
  14.             raise AttributeError("Meta class is required")

  15.         func_name = getattr(meta_obj, "func_name", None)
  16.         if not func_name:
  17.             raise AttributeError("func_name is required in Meta")

  18.         desc = getattr(meta_obj, "desc", None)
  19.         if desc is not None and not isinstance(desc, str):
  20.             raise AttributeError("desc in Meta should be str")

  21.         # register func
  22.         FunctionsManager.register_invocation_cls(new_cls)

  23.         return new_cls
复制代码
定义元类的一个抽象派生类

支持参数验证。
  1. class BaseInvocation(metaclass=InvocationMeta):
  2.     """
  3.     Base class for function invocation
  4.     """

  5.     class Inputs(BaseModel):
  6.         """
  7.         输入校验器
  8.         """

  9.         pass

  10.     @validate_arguments  # type: ignore
  11.     def __call__(self, *args, **kwargs):

  12.         # 输入参数校验, 仅可能是 args 或 kwargs 之一
  13.         try:
  14.             params = {}
  15.             if args:
  16.                 inputs_meta = getattr(self.Inputs, "Meta", None)
  17.                 inputs_ordering = getattr(inputs_meta, "ordering", None)
  18.                 if isinstance(inputs_ordering, list):
  19.                     if len(args) > len(inputs_ordering):
  20.                         raise Exception(f"Too many arguments for inputs: {args}")
  21.                     params = dict(zip(inputs_ordering, args))
  22.             elif kwargs:
  23.                 params = kwargs

  24.             # 参数校验
  25.             if params:
  26.                 self.Inputs(**params)
  27.         except PydanticValidationError as e:
  28.             raise Exception(e)

  29.         # 执行自定义业务逻辑
  30.         return self.invoke(*args, **kwargs)

  31.     @abc.abstractmethod
  32.     def invoke(self, *args, **kwargs):
  33.         """自定义业务逻辑 ."""
  34.         raise NotImplementedError()
复制代码
定义装饰器
  1. def register_class(name: str):
  2.     def _register_class(cls: BaseInvocation):
  3.         FunctionsManager.register_invocation_cls(cls, name=name)

  4.         @wraps(cls)
  5.         def wrapper():
  6.             return cls()

  7.         return wrapper

  8.     return _register_class


  9. def register_func(name: str):
  10.     def _register_func(func: Callable):
  11.         FunctionsManager.register_funcs({name: func})

  12.         @wraps(func)
  13.         def wrapper(*args, **kwargs):
  14.             return func(*args, **kwargs)

  15.         return wrapper

  16.     return _register_func
复制代码
单元测试
  1. from pydantic import BaseModel

  2. from .register import FunctionsManager, register_func, register_class, BaseInvocation


  3. @register_func("add")
  4. def add(x: int, y: int) -> int:
  5.     return x + y


  6. class Add(BaseInvocation):
  7.     class Meta:
  8.         func_name = "multiply"

  9.     class Inputs(BaseModel):
  10.         """
  11.         输入校验器
  12.         """
  13.         x: int
  14.         y: int

  15.         class Meta:
  16.             ordering = ["x", "y"]

  17.     def invoke(self, x: int, y: int) -> int:
  18.         return x * y


  19. @register_class("subtract")
  20. class Subtract:
  21.     class Inputs(BaseModel):
  22.         """
  23.         输入校验器
  24.         """
  25.         x: int
  26.         y: int

  27.         class Meta:
  28.             ordering = ["x", "y"]

  29.     def __call__(self, x: int, y: int) -> int:
  30.         return x - y


  31. class TestFunctionsManager:
  32.     def test_register_func(self):
  33.         func = FunctionsManager.get_func("add")
  34.         assert func(2, 3) == 5

  35.     def test_register_class(self):
  36.         func = FunctionsManager.get_func("subtract")
  37.         assert func(2, 3) == -1

  38.     def test_metaclass(self):
  39.         func = FunctionsManager.get_func("multiply")
  40.         assert func(2, 3) == 6
复制代码
参考

https://github.com/TencentBlueKing/bkflow-feel/blob/main/bkflow_feel/utils.py
到此这篇关于python实现一个通用的插件类的文章就介绍到这了,更多相关python 通用插件类内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

来源:https://www.jb51.net/python/319182d4u.htm
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具