python实现一个通用的插件类

 更新时间:2024年04月03日 09:41:21   作者:会编程的大白熊  
插件管理器用于注册、销毁、执行插件,本文主要介绍了python实现一个通用的插件类,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧

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

定义插件管理器

插件管理器用于注册、销毁、执行插件。

import abc
from functools import wraps
from typing import Callable, Dict

from pydantic import (
    BaseModel,
    validate_arguments,
    ValidationError as PydanticValidationError,
)

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

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

    module: ModuleType = import_module(module_path)

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


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

    @classmethod
    def register_invocation_cls(cls, invocation_cls: InvocationMeta, name=None) -> None:
        if not name:
            func_name = invocation_cls.Meta.func_name
        else:
            func_name = name
        if not isinstance(func_name, str):
            raise ValueError(f"func_name {func_name} should be string")
        existed_invocation_cls = cls.__hub.get(func_name)
        if existed_invocation_cls:
            raise RuntimeError(
                "func register error, {}'s func_name {} conflict with {}".format(
                    existed_invocation_cls, func_name, invocation_cls
                )
            )

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

    @classmethod
    def register_funcs(cls, func_dict) -> None:
        for func_name, func_obj in func_dict.items():
            if not isinstance(func_name, str):
                raise ValueError(f"func_name {func_name} should be string")
            if func_name in cls.__hub:
                raise ValueError(
                    "func register error, {}'s func_name {} conflict with {}".format(
                        func_obj, func_name, cls.__hub[func_name]
                    )
                )
            if isinstance(func_obj, str):
                func = import_string(func_obj)
            elif isinstance(func_obj, Callable):
                func = func_obj
            else:
                raise ValueError(
                    "func register error, {} is not be callable".format(
                        func_obj, func_name
                    )
                )

            cls.__hub[func_name] = func

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

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

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

    @classmethod
    def func_call(cls, func_name: str, *args, **kwargs):
        """根据函数名执行注册的函数 ."""
        func = cls.get_func(func_name)
        return func(*args, **kwargs)

定义元类

派生的类可自行注册到插件管理器。

class InvocationMeta(type):
    """
    Metaclass for function invocation
    """

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

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

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

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

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

        # register func
        FunctionsManager.register_invocation_cls(new_cls)

        return new_cls

定义元类的一个抽象派生类

支持参数验证。

class BaseInvocation(metaclass=InvocationMeta):
    """
    Base class for function invocation
    """

    class Inputs(BaseModel):
        """
        输入校验器
        """

        pass

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

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

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

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

    @abc.abstractmethod
    def invoke(self, *args, **kwargs):
        """自定义业务逻辑 ."""
        raise NotImplementedError()

定义装饰器

def register_class(name: str):
    def _register_class(cls: BaseInvocation):
        FunctionsManager.register_invocation_cls(cls, name=name)

        @wraps(cls)
        def wrapper():
            return cls()

        return wrapper

    return _register_class


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

        @wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)

        return wrapper

    return _register_func

单元测试

from pydantic import BaseModel

from .register import FunctionsManager, register_func, register_class, BaseInvocation


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


class Add(BaseInvocation):
    class Meta:
        func_name = "multiply"

    class Inputs(BaseModel):
        """
        输入校验器
        """
        x: int
        y: int

        class Meta:
            ordering = ["x", "y"]

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


@register_class("subtract")
class Subtract:
    class Inputs(BaseModel):
        """
        输入校验器
        """
        x: int
        y: int

        class Meta:
            ordering = ["x", "y"]

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


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

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

    def test_metaclass(self):
        func = FunctionsManager.get_func("multiply")
        assert func(2, 3) == 6

参考

https://github.com/TencentBlueKing/bkflow-feel/blob/main/bkflow_feel/utils.py

到此这篇关于python实现一个通用的插件类的文章就介绍到这了,更多相关python 通用插件类内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Python pandas修剪函数clip使用实例探究

    Python pandas修剪函数clip使用实例探究

    在数据处理和分析中,经常面临着需要限制数据范围的情况,而pandas库提供的clip函数就是一个强大的工具,可以方便地对数据进行修剪,本文将深入介绍clip函数的基本用法、常见参数以及实际场景中的应用,以帮助大家充分理解并灵活运用这一功能
    2024-01-01
  • Java中的各种单例模式优缺点解析

    Java中的各种单例模式优缺点解析

    这篇文章主要介绍了Java中的各种单例模式解析,单例模式是Java中最简单的设计模式之一,这种类型的设计模式属于创建者模式,它提供了一种访问对象的最佳方式,需要的朋友可以参考下
    2023-07-07
  • Python深度学习albumentations数据增强库

    Python深度学习albumentations数据增强库

    下面开始albumenations的正式介绍,在这里我强烈建议英语基础还好的读者去官方网站跟着教程一步步学习,而这里的内容主要是我自己的一个总结以及方便英语能力较弱的读者学习
    2021-09-09
  • Python批量查询域名是否被注册过

    Python批量查询域名是否被注册过

    本文给大家分享使用Python批量查询域名是否被注册过,非常不错,具有参考借鉴价值,需要的的朋友参考下吧
    2017-06-06
  • Python自动化操作Excel方法详解(xlrd,xlwt)

    Python自动化操作Excel方法详解(xlrd,xlwt)

    Excel是Windows环境下流行的、强大的电子表格应用。本文将详解用Python利用xlrd和xlwt实现自动化操作Excel的方法详细,需要的可以参考一下
    2022-06-06
  • python中get和post有什么区别

    python中get和post有什么区别

    在本篇内容里小编给大家分享的是关于python中get和post有什么区别的相关内容,需要的朋友们参考下吧。
    2020-06-06
  • Python中的线程操作模块(oncurrent)

    Python中的线程操作模块(oncurrent)

    这篇文章介绍了Python中的线程操作模块(oncurrent),文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-05-05
  • Python如何实现的二分查找算法

    Python如何实现的二分查找算法

    在本篇文章里小编给大家分享的是一篇关于Python实现的二分查找算法实例讲解内容,需要的朋友们可以学习下。
    2020-05-05
  • Python多线程编程(六):可重入锁RLock

    Python多线程编程(六):可重入锁RLock

    这篇文章主要介绍了Python多线程编程(六):可重入锁RLock,本文直接给出使用实例,然后讲解如何使用RLock避免死锁,需要的朋友可以参考下
    2015-04-04
  • python 检查是否为中文字符串的方法

    python 检查是否为中文字符串的方法

    今天小编就为大家分享一篇python 检查是否为中文字符串的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-12-12

最新评论