diff --git a/Musicreater/_plugin_abc.py b/Musicreater/_plugin_abc.py index a121281..00ae230 100644 --- a/Musicreater/_plugin_abc.py +++ b/Musicreater/_plugin_abc.py @@ -93,24 +93,76 @@ from .data import SingleMusic, SingleTrack # ] +# ======================== +# 枚举类 +# ======================== + +class PluginTypes(str, Enum): + """插件类型枚举""" + + FUNCTION_MUSIC_IMPORT = "import_music_data" + FUNCTION_TRACK_IMPORT = "import_track_data" + FUNCTION_MUSIC_OPERATE = "music_data_operating" + FUNCTION_TRACK_OPERATE = "track_data_operating" + FUNCTION_MUSIC_EXPORT = "export_music_data" + FUNCTION_TRACK_EXPORT = "export_track_data" + SERVICE = "service" + LIBRARY = "library" + + + +# ======================== +# 数据类 +# ======================== + + @dataclass class PluginConfig(ABC): """插件配置基类""" def to_dict(self) -> Dict[str, Any]: - """字典化配置文件""" + """将配置内容转换为字典 + + 返回 + ==== + Dict[str, Any] + 配置项的字典表示,不包含以下划线开头的私有属性 + """ return {k: v for k, v in self.__dict__.items() if not k.startswith("_")} @classmethod def from_dict(cls, data: Dict[str, Any]) -> "PluginConfig": - """从字典创建配置实例""" + """从字典创建配置实例 + + 参数 + ==== + data: Dict[str, Any] + 包含配置字段的字典 + + 返回 + ==== + PluginConfig + 配置类的实例 + """ + # 只保留类中定义的字段 field_names = {f.name for f in cls.__dataclass_fields__.values()} filtered_data = {k: v for k, v in data.items() if k in field_names} return cls(**filtered_data) def save_to_file(self, file_path: Path) -> None: - """保存配置到文件""" + """保存配置到 TOML 文件 + + 参数 + ==== + file_path: Path + 目标文件路径;必须以 .toml 为后缀 + + 异常 + ==== + PluginConfigDumpError + 当文件后缀不是 .toml 或写入失败时抛出 + """ if file_path.suffix.upper() == ".TOML": file_path.parent.mkdir(parents=True, exist_ok=True) else: @@ -126,7 +178,23 @@ class PluginConfig(ABC): @classmethod def load_from_file(cls, file_path: Path) -> "PluginConfig": - """从文件加载配置""" + """从 TOML 文件加载配置 + + 参数 + ==== + file_path: Path + 源文件路径 + + 返回 + ==== + PluginConfig + 加载后的配置实例 + + 异常 + ==== + PluginConfigLoadError + 当读取或解析失败时抛出 + """ try: with file_path.open("rb") as f: return cls.from_dict(tomllib.load(f)) @@ -134,19 +202,6 @@ class PluginConfig(ABC): raise PluginConfigLoadError(e) -class PluginType(str, Enum): - """插件类型枚举""" - - FUNCTION_MUSIC_IMPORT = "import_music_data" - FUNCTION_TRACK_IMPORT = "import_track_data" - FUNCTION_MUSIC_OPERATE = "music_data_operating" - FUNCTION_TRACK_OPERATE = "track_data_operating" - FUNCTION_MUSIC_EXPORT = "export_music_data" - FUNCTION_TRACK_EXPORT = "export_track_data" - SERVICE = "service" - LIBRARY = "library" - - @dataclass class PluginMetaInformation(ABC): """插件元信息""" @@ -159,7 +214,7 @@ class PluginMetaInformation(ABC): """插件简介""" version: Tuple[int, ...] """插件版本号""" - type: PluginType + type: PluginTypes """插件类型""" license: str = "MIT License" """插件发布时采用的许可协议""" @@ -167,6 +222,11 @@ class PluginMetaInformation(ABC): """插件是否对其他插件存在依赖""" +# ======================== +# 抽象基类 +# ======================== + + class TopPluginBase(ABC): """所有插件的抽象基类""" @@ -195,7 +255,7 @@ class TopInOutPluginBase(TopPluginBase, ABC): """导入导出用抽象基类""" supported_formats: Tuple[str, ...] = tuple() - """支持的格式""" + """支持的格式(定义后会自动转大写)""" def __init_subclass__(cls) -> None: super().__init_subclass__() @@ -214,11 +274,33 @@ class TopInOutPluginBase(TopPluginBase, ABC): ) def can_handle_file(self, file_path: Path) -> bool: - """判断是否可处理某个文件""" + """判断是否可处理某个文件 + + 参数 + ==== + file_path: Path + 待检测的文件路径 + + 返回 + ==== + bool + 若文件后缀已在本类中定义,则返回 True + """ return file_path.suffix.upper().endswith(self.supported_formats) def can_handle_format(self, format_name: str) -> bool: - """判断是否可处理某个格式""" + """判断是否可处理某个格式 + + 参数 + ==== + format_name: str + 格式名称(如 'MIDI', 'WAV') + + 返回 + ==== + bool + 若格式名本类中已经定义,则返回 True + """ return format_name.upper().endswith(self.supported_formats) @@ -228,7 +310,7 @@ class MusicInputPluginBase(TopInOutPluginBase, ABC): def __init_subclass__(cls) -> None: super().__init_subclass__() - if cls.metainfo.type != PluginType.FUNCTION_MUSIC_IMPORT: + if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_IMPORT: raise PluginMetainfoValueError( "插件类`{cls_name}`是从`MusicInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_IMPORT`类型的插件,而不是`PluginType.{cls_type}`".format( cls_name=cls.__name__, @@ -240,11 +322,38 @@ class MusicInputPluginBase(TopInOutPluginBase, ABC): def loadbytes( self, bytes_buffer_in: BinaryIO, config: Optional[PluginConfig] ) -> "SingleMusic": - """从字节流加载数据到完整曲目""" + """从字节流加载数据到完整曲目 + + 参数 + ==== + bytes_buffer_in: BinaryIO + 输入的二进制字节流 + config: Optional[PluginConfig] + 插件配置;**可选** + + 返回 + ==== + SingleMusic + 解析得到的完整曲目对象 + """ + pass def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleMusic": - """从文件加载数据到完整曲目""" + """从文件加载数据到完整曲目 + + 参数 + ==== + file_path: Path + 输入文件路径 + config: Optional[PluginConfig] + 插件配置;**可选** + + 返回 + ==== + SingleMusic + 解析得到的完整曲目对象 + """ with file_path.open("rb") as f: return self.loadbytes(f, config) @@ -255,7 +364,7 @@ class TrackInputPluginBase(TopInOutPluginBase, ABC): def __init_subclass__(cls) -> None: super().__init_subclass__() - if cls.metainfo.type != PluginType.FUNCTION_TRACK_IMPORT: + if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_IMPORT: raise PluginMetainfoValueError( "插件类`{cls_name}`是从`TrackInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_IMPORT`类型的插件,而不是`PluginType.{cls_type}`".format( cls_name=cls.__name__, @@ -267,11 +376,37 @@ class TrackInputPluginBase(TopInOutPluginBase, ABC): def loadbytes( self, bytes_buffer_in: BinaryIO, config: Optional[PluginConfig] ) -> "SingleTrack": - """从字节流加载音符数据到单个音轨""" + """从字节流加载音符数据到单个音轨 + + 参数 + ==== + bytes_buffer_in: BinaryIO + 输入的二进制字节流 + config: Optional[PluginConfig] + 插件配置;**可选** + + 返回 + ==== + SingleTrack + 解析得到的单个音轨对象 + """ pass def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleTrack": - """从文件加载音符数据到单个音轨""" + """从文件加载音符数据到单个音轨 + + 参数 + ==== + file_path: Path + 输入文件路径 + config: Optional[PluginConfig] + 插件配置;**可选** + + 返回 + ==== + SingleTrack + 解析得到的单个音轨对象 + """ with file_path.open("rb") as f: return self.loadbytes(f, config) @@ -282,7 +417,7 @@ class MusicOperatePluginBase(TopPluginBase, ABC): def __init_subclass__(cls) -> None: super().__init_subclass__() - if cls.metainfo.type != PluginType.FUNCTION_MUSIC_OPERATE: + if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_OPERATE: raise PluginMetainfoValueError( "插件类`{cls_name}`是从`MusicOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_OPERATE`类型的插件,而不是`PluginType.{cls_type}`".format( cls_name=cls.__name__, @@ -294,7 +429,20 @@ class MusicOperatePluginBase(TopPluginBase, ABC): def process( self, data: "SingleMusic", config: Optional[PluginConfig] ) -> "SingleMusic": - """处理完整曲目的数据""" + """处理完整曲目的数据 + + 参数 + ==== + data: SingleMusic + 待处理的完整曲目 + config: Optional[PluginConfig] + 插件配置;**可选** + + 返回 + ==== + SingleMusic + 处理后的完整曲目 + """ pass @@ -304,7 +452,7 @@ class TrackOperatePluginBase(TopPluginBase, ABC): def __init_subclass__(cls) -> None: super().__init_subclass__() - if cls.metainfo.type != PluginType.FUNCTION_TRACK_OPERATE: + if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_OPERATE: raise PluginMetainfoValueError( "插件类`{cls_name}`是从`TrackOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_OPERATE`类型的插件,而不是`PluginType.{cls_type}`".format( cls_name=cls.__name__, @@ -316,7 +464,20 @@ class TrackOperatePluginBase(TopPluginBase, ABC): def process( self, data: "SingleTrack", config: Optional[PluginConfig] ) -> "SingleTrack": - """处理单个音轨的音符数据""" + """处理单个音轨的音符数据 + + 参数 + ==== + data: SingleTrack + 待处理的单个音轨 + config: Optional[PluginConfig] + 插件配置;**可选** + + 返回 + ==== + SingleTrack + 处理后的单个音轨 + """ pass @@ -326,7 +487,7 @@ class MusicOutputPluginBase(TopInOutPluginBase, ABC): def __init_subclass__(cls) -> None: super().__init_subclass__() - if cls.metainfo.type != PluginType.FUNCTION_MUSIC_EXPORT: + if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_EXPORT: raise PluginMetainfoValueError( "插件类`{cls_name}`是从`MusicOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format( cls_name=cls.__name__, @@ -338,14 +499,38 @@ class MusicOutputPluginBase(TopInOutPluginBase, ABC): def dumpbytes( self, data: "SingleMusic", config: Optional[PluginConfig] ) -> BinaryIO: - """将完整曲目导出为对应格式的字节流""" + """将完整曲目导出为对应格式的字节流 + + 参数 + ==== + data: SingleMusic + 待导出的完整曲目 + config: Optional[PluginConfig] + 插件配置;**可选** + + 返回 + ==== + BinaryIO + 导出后的二进制字节流 + """ pass @abstractmethod def dump( self, data: "SingleMusic", file_path: Path, config: Optional[PluginConfig] ): - """将完整曲目导出为对应格式的文件""" + """将完整曲目导出为对应格式的文件 + + 参数 + ==== + data: SingleMusic + 待导出的完整曲目 + file_path: Path + 输出文件路径 + config: Optional[PluginConfig] + 插件配置;**可选** + """ + pass @@ -355,7 +540,7 @@ class TrackOutputPluginBase(TopInOutPluginBase, ABC): def __init_subclass__(cls) -> None: super().__init_subclass__() - if cls.metainfo.type != PluginType.FUNCTION_TRACK_EXPORT: + if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_EXPORT: raise PluginMetainfoValueError( "插件类`{cls_name}`是从`TrackOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format( cls_name=cls.__name__, @@ -367,14 +552,37 @@ class TrackOutputPluginBase(TopInOutPluginBase, ABC): def dumpbytes( self, data: "SingleTrack", config: Optional[PluginConfig] ) -> BinaryIO: - """将单个音轨导出为对应格式的字节流""" + """将单个音轨导出为对应格式的字节流 + + 参数 + ==== + data: SingleTrack + 待导出的单个音轨 + config: Optional[PluginConfig] + 插件配置;**可选** + + 返回 + ==== + BinaryIO + 导出后的二进制字节流 + """ pass @abstractmethod def dump( self, data: "SingleTrack", file_path: Path, config: Optional[PluginConfig] ): - """将单个音轨导出为对应格式的文件""" + """将单个音轨导出为对应格式的文件 + + 参数 + ==== + data: SingleTrack + 待导出的单个音轨 + file_path: Path + 输出文件路径 + config: Optional[PluginConfig] + 插件配置;**可选** + """ pass @@ -384,7 +592,7 @@ class ServicePluginBase(TopPluginBase, ABC): def __init_subclass__(cls) -> None: super().__init_subclass__() - if cls.metainfo.type != PluginType.SERVICE: + if cls.metainfo.type != PluginTypes.SERVICE: raise PluginMetainfoValueError( "插件类`{cls_name}`是从`ServicePlugin`继承的,该类的子类应当为一个`PluginType.SERVICE`类型的插件,而不是`PluginType.{cls_type}`".format( cls_name=cls.__name__, @@ -394,7 +602,15 @@ class ServicePluginBase(TopPluginBase, ABC): @abstractmethod def serve(self, config: Optional[PluginConfig], *args) -> None: - """服务插件的运行逻辑""" + """服务插件的运行逻辑 + + 参数 + ==== + config: Optional[PluginConfig] + 插件配置;**可选** + *args: Any + 其他运行时参数 + """ pass @@ -404,7 +620,7 @@ class LibraryPluginBase(TopPluginBase, ABC): def __init_subclass__(cls) -> None: super().__init_subclass__() - if cls.metainfo.type != PluginType.LIBRARY: + if cls.metainfo.type != PluginTypes.LIBRARY: raise PluginMetainfoValueError( "插件类`{cls_name}`是从`LibraryPlugin`继承的,该类的子类应当为一个`PluginType.LIBRARY`类型的插件,而不是`PluginType.{cls_type}`".format( cls_name=cls.__name__, diff --git a/Musicreater/builtin_plugins/midi_read.py b/Musicreater/builtin_plugins/midi_read.py index ead0134..687d35b 100644 --- a/Musicreater/builtin_plugins/midi_read.py +++ b/Musicreater/builtin_plugins/midi_read.py @@ -27,7 +27,7 @@ from Musicreater.plugins import ( music_input_plugin, PluginConfig, PluginMetaInformation, - PluginType, + PluginTypes, MusicInputPluginBase, ) @@ -37,11 +37,11 @@ class MidiImport2MusicPlugin(MusicInputPluginBase): """Midi 音乐数据导入插件""" metainfo = PluginMetaInformation( - name="midi_2_music_plugin", + name="Midi 导入插件", author="金羿、玉衡Alioth", description="从 Midi 文件导入音乐数据", version=(0, 0, 1), - type=PluginType.FUNCTION_MUSIC_IMPORT, + type=PluginTypes.FUNCTION_MUSIC_IMPORT, license="Same as Musicreater", ) diff --git a/Musicreater/data.py b/Musicreater/data.py index ed71940..d027f71 100644 --- a/Musicreater/data.py +++ b/Musicreater/data.py @@ -476,14 +476,7 @@ class SingleTrack(List[SingleNote]): self.extra_info = extra_information if extra_information else {} - self.argument_curves = { - CurvableParam.PITCH: None, - CurvableParam.VELOCITY: None, - CurvableParam.VOLUME: None, - CurvableParam.DISTANCE: None, - CurvableParam.LR_PANNING: None, - CurvableParam.UD_PANNING: None, - } + self.argument_curves = {item: None for item in CurvableParam} super().__init__(*args) super().sort() @@ -572,9 +565,9 @@ class SingleTrack(List[SingleNote]): is_percussive_note=self.is_percussive, sound_position=self.sound_position, **{ - item.value: self.argument_curves[item].value_at(_note.start_time) # type: ignore + item.value: argcrv.value_at(_note.start_time) for item in CurvableParam - if self.argument_curves[item] + if (argcrv := self.argument_curves[item]) }, ) diff --git a/Musicreater/exceptions.py b/Musicreater/exceptions.py index fa96b02..eefb026 100644 --- a/Musicreater/exceptions.py +++ b/Musicreater/exceptions.py @@ -225,6 +225,23 @@ class PluginLoadError(MusicreaterOuterlyError): super().__init__("插件加载错误 - ", *args) +class PluginNotFoundError(PluginLoadError): + """插件未找到""" + + def __init__(self, *args): + """插件未找到""" + super().__init__("插件未找到:", *args) + + +class PluginRegisteredError(PluginLoadError): + """插件重复注册""" + + def __init__(self, *args): + """插件已被注册注册""" + super().__init__("插件重复注册:", *args) + + + class PluginConfigRelatedError(MusicreaterOuterlyError): """插件配置相关错误""" diff --git a/Musicreater/main.py b/Musicreater/main.py index ed88499..3445503 100644 --- a/Musicreater/main.py +++ b/Musicreater/main.py @@ -47,19 +47,25 @@ import re from difflib import get_close_matches -from typing import Dict, Generator, List, Optional, Tuple, Union +from typing import Dict, Generator, List, Optional, Tuple, Union, Mapping, Callable from pathlib import Path from .data import SingleMusic, SingleTrack -from .exceptions import FileFormatNotSupportedError, PluginNotSpecifiedError +from .exceptions import ( + FileFormatNotSupportedError, + PluginNotSpecifiedError, + PluginNotFoundError, +) from ._plugin_abc import TopPluginBase from .plugins import ( _global_plugin_registry, PluginRegistry, PluginConfig, - PluginType, + PluginTypes, load_plugin_module, + T_IOPlugin, + T_Plugin, ) @@ -72,7 +78,7 @@ class MusiCreater: __plugin_registry: PluginRegistry """插件注册表实例""" _plugin_cache: Dict[str, TopPluginBase] - """插件缓存字典,插件名为键、插件实例为值""" + """插件缓存字典,插件id为键、插件实例为值""" music: SingleMusic """当前曲目实例""" @@ -86,149 +92,119 @@ class MusiCreater: self.music = whole_music + @staticmethod + def _get_plugin_within_iousage( + get_func: Callable[[Union[Path, str]], Generator[T_IOPlugin, None, None]], + fpath: Path, + plg_regdict: Dict[str, T_IOPlugin], + plg_id: Optional[str], + ) -> T_IOPlugin: + + __plugin: Optional[T_IOPlugin] = None + if plg_id: + __plugin = plg_regdict.get(plg_id) + + else: + for __plg in get_func(fpath): + if __plugin: + raise PluginNotSpecifiedError( + "文件类型`{}`可被多个插件处理,请在调用函数的参数中指定插件名称".format( + fpath.suffix.upper() + ) + ) + __plugin = __plg + if __plugin: + return __plugin + else: + raise FileFormatNotSupportedError( + "无法找到处理`{}`类型文件的插件".format(fpath.suffix.upper()) + ) + @classmethod def import_music( cls, file_path: Path, - plugin_name: Optional[str] = None, + plugin_id: Optional[str] = None, plugin_config: Optional[PluginConfig] = None, - ): - __music = None - if plugin_name: - __music = _global_plugin_registry.get_music_input_plugin(plugin_name).load( - file_path, plugin_config - ) - else: - for plugin in _global_plugin_registry.get_music_input_plugin_by_format( - file_path - ): - if __music is not None: - raise PluginNotSpecifiedError( - "文件类型`{}`可被多个插件处理,请在导入函数的参数中指定插件名称".format( - file_path.suffix.upper() - ) - ) - __music = plugin.load(file_path, plugin_config) - if __music is None: - raise FileFormatNotSupportedError( - "无法找到处理`{}`类型文件的插件".format(file_path.suffix.upper()) - ) - return cls(whole_music=__music) + ) -> "MusiCreater": + return cls( + whole_music=cls._get_plugin_within_iousage( + _global_plugin_registry.get_music_input_plugin_by_format, + file_path, + _global_plugin_registry._music_input_plugins, + plugin_id, + ).load(file_path, plugin_config) + ) def import_track( self, file_path: Path, - plugin_name: Optional[str] = None, + plugin_id: Optional[str] = None, plugin_config: Optional[PluginConfig] = None, ) -> SingleTrack: - __track = None - if plugin_name: - __track = self.get_plugin_by_name( - plugin_name - ).load( # pyright: ignore[reportAttributeAccessIssue] - file_path, plugin_config - ) - else: - for plugin in self.__plugin_registry.get_track_input_plugin_by_format( - file_path - ): - if __track: - raise PluginNotSpecifiedError( - "文件类型`{}`可被多个插件处理,请在导入函数的参数中指定插件名称".format( - file_path.suffix.upper() - ) - ) - __track = plugin.load(file_path, plugin_config) - if __track: - self.music.append(__track) - return __track - raise FileFormatNotSupportedError( - "无法找到处理`{}`类型文件的插件".format(file_path.suffix.upper()) + self.music.append( + self._get_plugin_within_iousage( + self.__plugin_registry.get_track_input_plugin_by_format, + file_path, + self.__plugin_registry._track_input_plugins, + plugin_id, + ).load(file_path, plugin_config) ) + return self.music[-1] def export_music( self, file_path: Path, - plugin_name: Optional[str] = None, + plugin_id: Optional[str] = None, plugin_config: Optional[PluginConfig] = None, ) -> None: - __plugin = None - if plugin_name: - __plugin = self.get_plugin_by_name(plugin_name) - else: - for plugin in self.__plugin_registry.get_music_output_plugin_by_format( - file_path - ): - if __plugin: - raise PluginNotSpecifiedError( - "文件类型`{}`可被多个插件处理,请在导出函数的参数中指定插件名称".format( - file_path.suffix.upper() - ) - ) - __plugin = plugin - - if __plugin: - __plugin.dump( # pyright: ignore[reportAttributeAccessIssue] - self.music, file_path, plugin_config - ) - else: - raise FileFormatNotSupportedError( - "无法找到处理`{}`类型文件的插件".format(file_path.suffix.upper()) - ) + self._get_plugin_within_iousage( + self.__plugin_registry.get_music_output_plugin_by_format, + file_path, + self.__plugin_registry._music_output_plugins, + plugin_id, + ).dump(self.music, file_path, plugin_config) def export_track( self, track_index: int, file_path: Path, - plugin_name: Optional[str] = None, + plugin_id: Optional[str] = None, plugin_config: Optional[PluginConfig] = None, ) -> None: - __plugin = None - if plugin_name: - __plugin = self.get_plugin_by_name(plugin_name) - else: - for plugin in self.__plugin_registry.get_track_output_plugin_by_format( - file_path - ): - if __plugin: - raise PluginNotSpecifiedError( - "文件类型`{}`可被多个插件处理,请在导出函数的参数中指定插件名称".format( - file_path.suffix.upper() - ) - ) - __plugin = plugin - - if __plugin: - __plugin.dump( # pyright: ignore[reportAttributeAccessIssue] - self.music[track_index], file_path, plugin_config - ) - else: - raise FileFormatNotSupportedError( - "无法找到处理`{}`类型文件的插件".format(file_path.suffix.upper()) - ) + self._get_plugin_within_iousage( + self.__plugin_registry.get_track_output_plugin_by_format, + file_path, + self.__plugin_registry._track_output_plugins, + plugin_id, + ).dump(self.music[track_index], file_path, plugin_config) def perform_operation_on_music( - self, plugin_name: str, plugin_config: Optional[PluginConfig] = None + self, plugin_id: str, plugin_config: Optional[PluginConfig] = None ): - # 这样做是为了兼容以后的*撤回/重做*功能 - self.music = self.get_plugin_by_name( - plugin_name - ).process( # pyright: ignore[reportAttributeAccessIssue] - self.music, plugin_config - ) + if __plugin := self.__plugin_registry._music_operate_plugins.get(plugin_id): + # 这样做是为了兼容以后的*撤回/重做*功能 + self.music = __plugin.process(self.music, plugin_config) + else: + raise PluginNotFoundError( + "无法找到惟一识别码为`{}`的插件".format(plugin_id) + ) def perform_operation_on_track( self, track_index: int, - plugin_name: str, + plugin_id: str, plugin_config: Optional[PluginConfig] = None, ): - # 这样做是为了兼容以后的*撤回/重做*功能 - self.music[track_index] = self.get_plugin_by_name( - plugin_name - ).process( # pyright: ignore[reportAttributeAccessIssue] - self.music[track_index], plugin_config - ) + if __plugin := self.__plugin_registry._track_operate_plugins.get(plugin_id): + # 这样做是为了兼容以后的*撤回/重做*功能 + self.music[track_index] = __plugin.process( + self.music[track_index], plugin_config + ) + else: + raise PluginNotFoundError( + "无法找到惟一识别码为`{}`的插件".format(plugin_id) + ) @staticmethod def _camel_to_snake(name: str) -> str: @@ -240,8 +216,8 @@ class MusiCreater: "([a-z0-9])([A-Z])", r"\1_\2", re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name) ).lower() - def _parse_plugin_name(self, attr_name: str) -> Optional[str]: - """解析属性名称为插件名称""" + def _parse_plugin_id(self, attr_name: str) -> Optional[str]: + """解析属性名称为插件惟一识别码""" # 尝试去除 _plugin 后缀 if attr_name.endswith("_plugin"): @@ -256,35 +232,35 @@ class MusiCreater: if snake_case_name in self._plugin_cache: # 尝试转换后的插件名 return snake_case_name else: - return self._parse_plugin_name(snake_case_name) + return self._parse_plugin_id(snake_case_name) return None - def _get_closest_plugin_name(self, requested_name: str) -> Optional[str]: - """找到最接近的插件名称(用于更好的错误提示)""" + def _get_closest_plugin_id(self, requested_id: str) -> Optional[str]: + """找到最接近的插件识别码(用于更好的错误提示)""" matches = get_close_matches( - requested_name, self._plugin_cache.keys(), n=1, cutoff=0.6 + requested_id, self._plugin_cache.keys(), n=1, cutoff=0.6 ) return matches[0] if matches else None - def get_plugin_by_name(self, name: str) -> TopPluginBase: + def get_plugin_by_id(self, plg_id: str): """获取插件实例,并缓存起来,提高性能""" - if name.startswith("_"): - raise AttributeError("属性`{}`不存在,不应访问类的私有属性".format(name)) + if plg_id.startswith("_"): + raise AttributeError("属性`{}`不存在,不应访问类的私有属性".format(plg_id)) - if name in self._plugin_cache: - return self._plugin_cache[name] + if plg_id in self._plugin_cache: + return self._plugin_cache[plg_id] else: - plugin_name = self._parse_plugin_name(name) + plugin_name = self._parse_plugin_id(plg_id) if plugin_name: - self._plugin_cache[name] = self._plugin_cache[plugin_name] - return self._plugin_cache[name] + self._plugin_cache[plg_id] = self._plugin_cache[plugin_name] + return self._plugin_cache[plg_id] - closest = self._get_closest_plugin_name(name) + closest = self._get_closest_plugin_id(plg_id) raise AttributeError( - "插件`{}`不存在,请检查插件名称是否正确".format(name) + "插件`{}`不存在,请检查插件的惟一识别码是否正确".format(plg_id) + ( ";或者阁下可能想要使用的是`{}`插件?".format(closest) if closest @@ -292,18 +268,18 @@ class MusiCreater: ) ) - def __getattr__(self, name: str): + def __getattr__(self, plugin_id: str): """动态属性访问,允许直接 实例.插件名 来访问插件""" - return self.get_plugin_by_name(name) + return self.get_plugin_by_id(plugin_id) def _cache_all_plugins(self): """获取所有已注册插件的名称""" - for __plugin_type, __plugins_set in self.__plugin_registry: - for __plugin in __plugins_set: - if __plugin.metainfo.name in self._plugin_cache: # 避免重复缓存 + for __plugin_type, __plugins_map in self.__plugin_registry: + for __plugin_id, __plugin in __plugins_map.items(): + if __plugin_id in self._plugin_cache: # 避免重复缓存 if ( __plugin.metainfo.version - <= self._plugin_cache[__plugin.metainfo.name].metainfo.version + <= self._plugin_cache[__plugin_id].metainfo.version ): # 优先使用版本号最大的插件 continue - self._plugin_cache[__plugin.metainfo.name] = __plugin + self._plugin_cache[__plugin_id] = __plugin diff --git a/Musicreater/plugins.py b/Musicreater/plugins.py index 14bb224..b6bb34a 100644 --- a/Musicreater/plugins.py +++ b/Musicreater/plugins.py @@ -30,13 +30,16 @@ from typing import ( Set, Iterable, Iterator, + TypeVar, + Mapping, + Callable, ) from itertools import chain from ._plugin_abc import ( # 枚举类 - PluginType, + PluginTypes, # 抽象基类/数据类(插件参数定义) PluginConfig, PluginMetaInformation, @@ -56,12 +59,13 @@ from .exceptions import ( PluginMetainfoNotFoundError, ParameterTypeError, PluginInstanceNotFoundError, + PluginRegisteredError, ) __all__ = [ # 枚举类 - "PluginType", + "PluginTypes", # 抽象基类/数据类(插件参数定义) "PluginConfig", "PluginMetaInformation", @@ -86,6 +90,26 @@ __all__ = [ ] +T_IOPlugin = TypeVar( + "T_IOPlugin", + MusicInputPluginBase, + TrackInputPluginBase, + MusicOutputPluginBase, + TrackOutputPluginBase, +) +T_Plugin = TypeVar( + "T_Plugin", + MusicInputPluginBase, + TrackInputPluginBase, + MusicOperatePluginBase, + TrackOperatePluginBase, + MusicOutputPluginBase, + TrackOutputPluginBase, + ServicePluginBase, + LibraryPluginBase, +) + + def load_plugin_module(package: Union[Path, str]): """自动发现并加载插件包中的插件 @@ -111,290 +135,199 @@ class PluginRegistry: """插件注册管理器(注册表)""" def __init__(self): - # 实际上在纵容那些有着同样名称的插件…… - # (不用 Dict[str`plugin name`, PluginClass`] 的形式) - # 啊,我真的很高尚 - # 你真的不会把插件注册两遍吧……对吧? + self._music_input_plugins: Dict[str, MusicInputPluginBase] = {} + self._track_input_plugins: Dict[str, TrackInputPluginBase] = {} + self._music_operate_plugins: Dict[str, MusicOperatePluginBase] = {} + self._track_operate_plugins: Dict[str, TrackOperatePluginBase] = {} + self._music_output_plugins: Dict[str, MusicOutputPluginBase] = {} + self._track_output_plugins: Dict[str, TrackOutputPluginBase] = {} + self._service_plugins: Dict[str, ServicePluginBase] = {} + self._library_plugins: Dict[str, LibraryPluginBase] = {} - # EMERGENCY TODO ================================================ CRITICAL 紧急更改 - # 改成 Dict[str`plugin id`, PluginClass`]] 的形式吧 - # 刚刚才想起来,这个 name 是显示名称啊!草 - # 现在测试情况下就将错就错吧,先把 name 当成 id 来写吧 - self._music_input_plugins: Set[MusicInputPluginBase] = set() - self._track_input_plugins: Set[TrackInputPluginBase] = set() - self._music_operate_plugins: Set[MusicOperatePluginBase] = set() - self._track_operate_plugins: Set[TrackOperatePluginBase] = set() - self._music_output_plugins: Set[MusicOutputPluginBase] = set() - self._track_output_plugins: Set[TrackOutputPluginBase] = set() - self._service_plugins: Set[ServicePluginBase] = set() - self._library_plugins: Set[LibraryPluginBase] = set() - - def __iter__(self) -> Iterator[Tuple[PluginType, Set[TopPluginBase]]]: + def __iter__(self) -> Iterator[Tuple[PluginTypes, Mapping[str, TopPluginBase]]]: """迭代器,返回所有插件""" return iter( ( - (PluginType.FUNCTION_MUSIC_IMPORT, self._music_input_plugins), - (PluginType.FUNCTION_TRACK_IMPORT, self._track_input_plugins), - (PluginType.FUNCTION_MUSIC_OPERATE, self._music_operate_plugins), - (PluginType.FUNCTION_TRACK_OPERATE, self._track_operate_plugins), - (PluginType.FUNCTION_MUSIC_EXPORT, self._music_output_plugins), - (PluginType.FUNCTION_TRACK_EXPORT, self._track_output_plugins), - (PluginType.SERVICE, self._service_plugins), - (PluginType.LIBRARY, self._library_plugins), + (PluginTypes.FUNCTION_MUSIC_IMPORT, self._music_input_plugins), + (PluginTypes.FUNCTION_TRACK_IMPORT, self._track_input_plugins), + (PluginTypes.FUNCTION_MUSIC_OPERATE, self._music_operate_plugins), + (PluginTypes.FUNCTION_TRACK_OPERATE, self._track_operate_plugins), + (PluginTypes.FUNCTION_MUSIC_EXPORT, self._music_output_plugins), + (PluginTypes.FUNCTION_TRACK_EXPORT, self._track_output_plugins), + (PluginTypes.SERVICE, self._service_plugins), + (PluginTypes.LIBRARY, self._library_plugins), ) - ) # pyright: ignore[reportReturnType] + ) - def register_music_input_plugin(self, plugin_class: type) -> None: + @staticmethod + def _register_plugin(cls_dict: dict, plg_class: type, plg_id: str) -> None: + """注册插件""" + if plg_id in cls_dict: + if cls_dict[plg_id].metainfo.version >= plg_class.metainfo.version: + raise PluginRegisteredError( + "插件惟一识别码`{}`所对应的插件已存在更高版本`{}`,请勿重复注册同一插件!".format( + plg_id, plg_class.metainfo + ) + ) + cls_dict[plg_id] = plg_class() + + def register_music_input_plugin( + self, + plugin_class: type, + plugin_id: str, + ) -> None: """注册输入插件-整首曲目""" - self._music_input_plugins.add(plugin_class()) + self._register_plugin(self._music_input_plugins, plugin_class, plugin_id) - def register_track_input_plugin(self, plugin_class: type) -> None: + def register_track_input_plugin(self, plugin_class: type, plugin_id: str) -> None: """注册输入插件-单个音轨""" - self._track_input_plugins.add(plugin_class()) + self._register_plugin(self._track_input_plugins, plugin_class, plugin_id) - def register_music_operate_plugin(self, plugin_class: type) -> None: + def register_music_operate_plugin(self, plugin_class: type, plugin_id: str) -> None: """注册曲目处理插件""" - self._music_operate_plugins.add(plugin_class()) + self._register_plugin(self._music_operate_plugins, plugin_class, plugin_id) - def register_track_operate_plugin(self, plugin_class: type) -> None: + def register_track_operate_plugin(self, plugin_class: type, plugin_id: str) -> None: """注册音轨处理插件""" - self._track_operate_plugins.add(plugin_class()) + self._register_plugin(self._track_operate_plugins, plugin_class, plugin_id) - def register_music_output_plugin(self, plugin_class: type) -> None: + def register_music_output_plugin(self, plugin_class: type, plugin_id: str) -> None: """注册输出插件-整首曲目""" - self._music_output_plugins.add(plugin_class()) + self._register_plugin(self._music_output_plugins, plugin_class, plugin_id) - def register_track_output_plugin(self, plugin_class: type) -> None: + def register_track_output_plugin(self, plugin_class: type, plugin_id: str) -> None: """注册输出插件-单个音轨""" - self._track_output_plugins.add(plugin_class()) + self._register_plugin(self._track_output_plugins, plugin_class, plugin_id) - def register_service_plugin(self, plugin_class: type) -> None: + def register_service_plugin(self, plugin_class: type, plugin_id: str) -> None: """注册服务插件""" - self._service_plugins.add(plugin_class()) + self._register_plugin(self._service_plugins, plugin_class, plugin_id) - def register_library_plugin(self, plugin_class: type) -> None: + def register_library_plugin(self, plugin_class: type, plugin_id: str) -> None: """注册支持库插件""" - self._library_plugins.add(plugin_class()) + self._register_plugin(self._library_plugins, plugin_class, plugin_id) + + @staticmethod + def _get_io_plugin_by_format( + plugin_regdict: Dict[str, T_IOPlugin], fpath_or_format: Union[Path, str] + ) -> Generator[T_IOPlugin, None, None]: + if isinstance(fpath_or_format, str): + return ( + plugin + for plugin in plugin_regdict.values() + if plugin.can_handle_format(fpath_or_format) + ) + elif isinstance(fpath_or_format, Path): + return ( + plugin + for plugin in plugin_regdict.values() + if plugin.can_handle_file(fpath_or_format) + ) + else: + raise ParameterTypeError( + "用于指定“导入全曲的数据之类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format( + type(fpath_or_format), fpath_or_format + ) + ) def get_music_input_plugin_by_format( self, filepath_or_format: Union[Path, str] ) -> Generator[MusicInputPluginBase, None, None]: """通过指定输入的文件或格式,以获取对应的全曲导入用插件""" - if isinstance(filepath_or_format, str): - return ( - plugin - for plugin in self._music_input_plugins - if plugin.can_handle_format(filepath_or_format) - ) - elif isinstance(filepath_or_format, Path): - return ( - plugin - for plugin in self._music_input_plugins - if plugin.can_handle_file(filepath_or_format) - ) - else: - raise ParameterTypeError( - "用于指定“导入全曲的数据之类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format( - type(filepath_or_format), filepath_or_format - ) - ) + return self._get_io_plugin_by_format( + self._music_input_plugins, filepath_or_format + ) def get_track_input_plugin_by_format( self, filepath_or_format: Union[Path, str] ) -> Generator[TrackInputPluginBase, None, None]: """通过指定输入的文件或格式,以获取对应的单音轨导入用插件""" - if isinstance(filepath_or_format, str): - return ( - plugin - for plugin in self._track_input_plugins - if plugin.can_handle_format(filepath_or_format) - ) - elif isinstance(filepath_or_format, Path): - return ( - plugin - for plugin in self._track_input_plugins - if plugin.can_handle_file(filepath_or_format) - ) - else: - raise ParameterTypeError( - "用于指定“导入单个音轨的数据之类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format( - type(filepath_or_format), filepath_or_format - ) - ) + return self._get_io_plugin_by_format( + self._track_input_plugins, filepath_or_format + ) def get_music_output_plugin_by_format( self, filepath_or_format: Union[Path, str] ) -> Generator[MusicOutputPluginBase, None, None]: """通过指定输出的文件或格式,以获取对应的导出全曲用插件""" - if isinstance(filepath_or_format, str): - return ( - plugin - for plugin in self._music_output_plugins - if plugin.can_handle_format(filepath_or_format) - ) - elif isinstance(filepath_or_format, Path): - return ( - plugin - for plugin in self._music_output_plugins - if plugin.can_handle_file(filepath_or_format) - ) - else: - raise ParameterTypeError( - "用于指定“全曲数据导出的类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format( - type(filepath_or_format), filepath_or_format - ) - ) + return self._get_io_plugin_by_format( + self._music_output_plugins, filepath_or_format + ) def get_track_output_plugin_by_format( self, filepath_or_format: Union[Path, str] ) -> Generator[TrackOutputPluginBase, None, None]: """通过指定输出的文件或格式,以获取对应的导出单个音轨用插件""" - if isinstance(filepath_or_format, str): - return ( - plugin - for plugin in self._track_output_plugins - if plugin.can_handle_format(filepath_or_format) + return self._get_io_plugin_by_format( + self._track_output_plugins, filepath_or_format + ) + + def _get_plugin_by_name( + self, + plugin_regdict: Mapping[str, T_Plugin], + plugin_name: str, + plugin_usage: str = "", + ) -> T_Plugin: + """通过指定名称,以获取对应的插件,当名称重叠时,取版本号最大的""" + try: + return max( + [ + plugin + for plugin in plugin_regdict.values() + if plugin.metainfo.name == plugin_name + ], + key=lambda plugin: plugin.metainfo.version, ) - elif isinstance(filepath_or_format, Path): - return ( - plugin - for plugin in self._track_output_plugins - if plugin.can_handle_file(filepath_or_format) - ) - else: - raise ParameterTypeError( - "用于指定“单音轨数据导出的类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format( - type(filepath_or_format), filepath_or_format - ) + except ValueError: + raise PluginInstanceNotFoundError( + "未找到“用于{}、名为`{}`”的插件".format(plugin_usage, plugin_name) ) def get_music_input_plugin(self, plugin_name: str) -> MusicInputPluginBase: """获取指定名称的全曲导入用插件,当名称重叠时,取版本号最大的""" - try: - return max( - [ - plugin - for plugin in self._music_input_plugins - if plugin.metainfo.name == plugin_name - ], - key=lambda plugin: plugin.metainfo.version, - ) - except ValueError: - raise PluginInstanceNotFoundError( - "未找到“用于导入曲目、名为`{}`”的插件".format(plugin_name) - ) + return self._get_plugin_by_name( + self._music_input_plugins, plugin_name, "导入全曲" + ) def get_track_input_plugin(self, plugin_name: str) -> TrackInputPluginBase: """获取指定名称的单音轨导入用插件,当名称重叠时,取版本号最大的""" - try: - return max( - [ - plugin - for plugin in self._track_input_plugins - if plugin.metainfo.name == plugin_name - ], - key=lambda plugin: plugin.metainfo.version, - ) - except ValueError: - raise PluginInstanceNotFoundError( - "未找到“用于导入单个音轨、名为`{}`”的插件".format(plugin_name) - ) + return self._get_plugin_by_name( + self._track_input_plugins, plugin_name, "导入单轨" + ) def get_music_operate_plugin(self, plugin_name: str) -> MusicOperatePluginBase: """获取指定名称的全曲处理用插件,当名称重叠时,取版本号最大的""" - try: - return max( - [ - plugin - for plugin in self._music_operate_plugins - if plugin.metainfo.name == plugin_name - ], - key=lambda plugin: plugin.metainfo.version, - ) - except ValueError: - raise PluginInstanceNotFoundError( - "未找到“用于处理整个曲目、名为`{}`”的插件".format(plugin_name) - ) + return self._get_plugin_by_name( + self._music_operate_plugins, plugin_name, "处理整个曲目" + ) def get_track_operate_plugin(self, plugin_name: str) -> TrackOperatePluginBase: """获取指定名称的单音轨处理用插件,当名称重叠时,取版本号最大的""" - try: - return max( - [ - plugin - for plugin in self._track_operate_plugins - if plugin.metainfo.name == plugin_name - ], - key=lambda plugin: plugin.metainfo.version, - ) - except ValueError: - raise PluginInstanceNotFoundError( - "未找到“用于处理单个音轨、名为`{}`”的插件".format(plugin_name) - ) + return self._get_plugin_by_name( + self._track_operate_plugins, plugin_name, "处理单个音轨" + ) def get_music_output_plugin(self, plugin_name: str) -> MusicOutputPluginBase: """获取指定名称的导出全曲用插件,当名称重叠时,取版本号最大的""" - try: - return max( - [ - plugin - for plugin in self._music_output_plugins - if plugin.metainfo.name == plugin_name - ], - key=lambda plugin: plugin.metainfo.version, - ) - except ValueError: - raise PluginMetainfoNotFoundError( - "未找到“用于导出完整曲目、名为`{}`”的插件".format(plugin_name) - ) + return self._get_plugin_by_name( + self._music_output_plugins, plugin_name, "导出完整曲目" + ) def get_track_output_plugin(self, plugin_name: str) -> TrackOutputPluginBase: """获取指定名称的导出单音轨用插件,当名称重叠时,取版本号最大的""" - try: - return max( - [ - plugin - for plugin in self._track_output_plugins - if plugin.metainfo.name == plugin_name - ], - key=lambda plugin: plugin.metainfo.version, - ) - except ValueError: - raise PluginMetainfoNotFoundError( - "未找到“用于导出单个音轨、名为`{}`”的插件".format(plugin_name) - ) + return self._get_plugin_by_name( + self._track_output_plugins, plugin_name, "导出单个音轨" + ) def get_service_plugin(self, plugin_name: str) -> ServicePluginBase: """获取服务用插件,当名称重叠时,取版本号最大的""" - try: - return max( - [ - plugin - for plugin in self._service_plugins - if plugin.metainfo.name == plugin_name - ], - key=lambda plugin: plugin.metainfo.version, - ) - except ValueError: - raise PluginInstanceNotFoundError( - "未找到名为`{}`的服务用插件".format(plugin_name) - ) + return self._get_plugin_by_name(self._service_plugins, plugin_name, "提供服务") def get_library_plugin(self, plugin_name: str) -> LibraryPluginBase: """获取依赖库类插件,当名称重叠时,取版本号最高的""" - try: - return max( - [ - plugin - for plugin in self._library_plugins - if plugin.metainfo.name == plugin_name - ], - key=lambda plugin: plugin.metainfo.version, - ) - except ValueError: - raise PluginInstanceNotFoundError( - "未找到名为`{}`的依赖库插件".format(plugin_name) - ) + return self._get_plugin_by_name( + self._library_plugins, plugin_name, "作为依赖库" + ) def supported_input_formats(self) -> Set[str]: """所有支持的导入格式""" @@ -402,7 +335,8 @@ class PluginRegistry: chain.from_iterable( plugin.supported_formats for plugin in chain( - self._music_input_plugins, self._track_input_plugins + self._music_input_plugins.values(), + self._track_input_plugins.values(), ) ) ) @@ -413,7 +347,8 @@ class PluginRegistry: chain.from_iterable( plugin.supported_formats for plugin in chain( - self._music_output_plugins, self._track_output_plugins + self._music_output_plugins.values(), + self._track_output_plugins.values(), ) ) ) @@ -423,97 +358,67 @@ _global_plugin_registry = PluginRegistry() """全局插件注册表实例""" -def music_input_plugin(plugin_id: str): - """全曲输入用插件装饰器""" +def __plugin_regist_decorator(plg_id: str, rgst_func: Callable[[type, str], None]): def decorator(cls): global _global_plugin_registry - cls.id = plugin_id - _global_plugin_registry.register_music_input_plugin(cls) + cls.id = plg_id + rgst_func(cls, plg_id) return cls return decorator +def music_input_plugin(plugin_id: str): + """全曲输入用插件装饰器""" + return __plugin_regist_decorator( + plugin_id, _global_plugin_registry.register_music_input_plugin + ) + + def track_input_plugin(plugin_id: str): """单轨输入用插件装饰器""" - - def decorator(cls): - global _global_plugin_registry - cls.id = plugin_id - _global_plugin_registry.register_track_input_plugin(cls) - return cls - - return decorator + return __plugin_regist_decorator( + plugin_id, _global_plugin_registry.register_track_input_plugin + ) def music_operate_plugin(plugin_id: str): """全曲处理用插件装饰器""" - - def decorator(cls): - global _global_plugin_registry - cls.id = plugin_id - _global_plugin_registry.register_music_operate_plugin(cls) - return cls - - return decorator - + return __plugin_regist_decorator( + plugin_id, _global_plugin_registry.register_music_operate_plugin + ) def track_operate_plugin(plugin_id: str): """音轨处理插件装饰器""" - - def decorator(cls): - global _global_plugin_registry - cls.id = plugin_id - _global_plugin_registry.register_track_operate_plugin(cls) - return cls - - return decorator + return __plugin_regist_decorator( + plugin_id, _global_plugin_registry.register_track_operate_plugin + ) def music_output_plugin(plugin_id: str): """乐曲输出用插件装饰器""" - - def decorator(cls): - global _global_plugin_registry - cls.id = plugin_id - _global_plugin_registry.register_music_output_plugin(cls) - return cls - - return decorator + return __plugin_regist_decorator( + plugin_id, _global_plugin_registry.register_music_output_plugin + ) def track_output_plugin(plugin_id: str): """音轨输出用插件装饰器""" - - def decorator(cls): - global _global_plugin_registry - cls.id = plugin_id - _global_plugin_registry.register_track_output_plugin(cls) - return cls - - return decorator + return __plugin_regist_decorator( + plugin_id, _global_plugin_registry.register_track_output_plugin + ) def service_plugin(plugin_id: str): """服务插件装饰器""" - - def decorator(cls): - global _global_plugin_registry - cls.id = plugin_id - _global_plugin_registry.register_service_plugin(cls) - return cls - - return decorator - + return __plugin_regist_decorator( + plugin_id, _global_plugin_registry.register_service_plugin + ) def library_plugin(plugin_id: str): """支持库插件装饰器""" - def decorator(cls): - global _global_plugin_registry - cls.id = plugin_id - _global_plugin_registry.register_library_plugin(cls) - return cls - - return decorator + return __plugin_regist_decorator( + plugin_id, _global_plugin_registry.register_library_plugin + ) diff --git a/test_read.py b/test_read.py index 298bf30..71d34eb 100644 --- a/test_read.py +++ b/test_read.py @@ -14,5 +14,11 @@ print(msct:=MusiCreater.import_music(Path("./resources/测试片段.mid"))) print(msct.music) +# 为了让类型检查器满意,以下方法不建议使用,因为这本质上是越过了 MusiCreater 类而直接执行插件的函数 +print(t := msct.midi_2_music_plugin.load(Path("./resources/测试片段.mid"), None)) +# 我们建议用这种方式来代替 +t = _global_plugin_registry._music_input_plugins["midi_2_music_plugin"].load(Path("./resources/测试片段.mid"), None) + +print(_global_plugin_registry) +print(msct._plugin_cache) -print(msct.midi_2_music_plugin.load(Path("./resources/测试片段.mid"), None)) \ No newline at end of file