# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file."""DataLab plugin system---------------------DataLab plugin system provides a way to extend the application with newfunctionalities.Plugins are Python modules that relies on two classes: - :class:`PluginInfo`, which stores information about the plugin - :class:`PluginBase`, which is the base class for all pluginsPlugins may also extends DataLab I/O features by providing new image orsignal formats. To do so, they must provide a subclass of :class:`ImageFormatBase`or :class:`SignalFormatBase`, in which format infos are defined using the:class:`FormatInfo` class."""from__future__importannotationsimportabcimportdataclassesimportimportlibimportosimportos.pathasospimportpkgutilimportsysfromtypingimportTYPE_CHECKINGfromqtpyimportQtWidgetsasQWfromcdl.configimportMOD_NAME,OTHER_PLUGINS_PATHLIST,Conf,_# pylint: disable=unused-importfromcdl.core.io.baseimportFormatInfo# noqa: F401fromcdl.core.io.image.baseimportImageFormatBase# noqa: F401fromcdl.core.io.image.formatsimportClassicsImageFormat# noqa: F401fromcdl.core.io.signal.baseimportSignalFormatBase# noqa: F401fromcdl.envimportexecenvfromcdl.proxyimportLocalProxyifTYPE_CHECKING:fromcdl.core.guiimportmainfromcdl.core.gui.panel.imageimportImagePanelfromcdl.core.gui.panel.signalimportSignalPanelfromcdl.core.model.imageimportNewImageParamfromcdl.core.model.signalimportNewSignalParamPLUGINS_DEFAULT_PATH=Conf.get_path("plugins")ifnotosp.isdir(PLUGINS_DEFAULT_PATH):os.makedirs(PLUGINS_DEFAULT_PATH)# pylint: disable=bad-mcs-classmethod-argument
[docs]classPluginRegistry(type):"""Metaclass for registering plugins"""_plugin_classes:list[type[PluginBase]]=[]_plugin_instances:list[PluginBase]=[]def__init__(cls,name,bases,attrs):super().__init__(name,bases,attrs)ifname!="PluginBase":cls._plugin_classes.append(cls)
[docs]@classmethoddefget_plugin_infos(cls,html:bool=True)->str:"""Return plugin infos (names, versions, descriptions) in html format Args: html: return html formatted text (default: True) """linesep="<br>"ifhtmlelseos.linesepbullet="• "ifhtmlelse" "*4defitalic(text:str)->str:"""Return italic text"""returnf"<i>{text}</i>"ifhtmlelsetextifConf.main.plugins_enabled.get():plugins=cls.get_plugins()ifplugins:text=italic(_("Registered plugins:"))text+=linesepforplugininplugins:text+=f"{bullet}{plugin.info.name} ({plugin.info.version})"ifplugin.info.description:text+=f": {plugin.info.description}"text+=linesepelse:text=italic(_("No plugins available"))else:text=italic(_("Plugins are disabled (see DataLab settings)"))returntext
[docs]classPluginBaseMeta(PluginRegistry,abc.ABCMeta):"""Mixed metaclass to avoid conflicts"""
[docs]classPluginBase(abc.ABC,metaclass=PluginBaseMeta):"""Plugin base class"""PLUGIN_INFO:PluginInfo=Nonedef__init__(self):self.main:main.CDLMainWindow=Noneself.proxy:LocalProxy=Noneself._is_registered=Falseself.info=self.PLUGIN_INFOifself.infoisNone:raiseValueError(f"Plugin info not set for {self.__class__.__name__}")@propertydefsignalpanel(self)->SignalPanel:"""Return signal panel"""returnself.main.signalpanel@propertydefimagepanel(self)->ImagePanel:"""Return image panel"""returnself.main.imagepanel
[docs]defedit_new_signal_parameters(self,title:str|None=None,size:int|None=None,hide_signal_type:bool=True,)->NewSignalParam:"""Create and edit new signal parameter dataset Args: title: title of the new signal size: size of the new signal (default: None, get from current signal) hide_signal_type: hide signal type parameter (default: True) Returns: New signal parameter dataset (or None if canceled) """newparam=self.signalpanel.get_newparam_from_current(title=title)ifsizeisnotNone:newparam.size=sizenewparam.hide_signal_type=hide_signal_typeifnewparam.edit(self.main):returnnewparamreturnNone
[docs]defedit_new_image_parameters(self,title:str|None=None,shape:tuple[int,int]|None=None,hide_image_type:bool=True,hide_image_dtype:bool=False,)->NewImageParam|None:"""Create and edit new image parameter dataset Args: title: title of the new image shape: shape of the new image (default: None, get from current image) hide_image_type: hide image type parameter (default: True) hide_image_dtype: hide image data type parameter (default: False) Returns: New image parameter dataset (or None if canceled) """newparam=self.imagepanel.get_newparam_from_current(title=title)ifshapeisnotNone:newparam.width,newparam.height=shapenewparam.hide_image_type=hide_image_typenewparam.hide_image_dtype=hide_image_dtypeifnewparam.edit(self.main):returnnewparamreturnNone
[docs]defis_registered(self):"""Return True if plugin is registered"""returnself._is_registered
[docs]defdiscover_plugins()->list[type[PluginBase]]:"""Discover plugins using naming convention Returns: List of discovered plugins (as classes) """ifConf.main.plugins_enabled.get():forpathin[Conf.main.plugins_path.get(),PLUGINS_DEFAULT_PATH,]+OTHER_PLUGINS_PATHLIST:rpath=osp.realpath(path)ifrpathnotinsys.path:sys.path.append(rpath)return[importlib.import_module(name)for_finder,name,_ispkginpkgutil.iter_modules()ifname.startswith(f"{MOD_NAME}_")]return[]
[docs]defget_available_plugins()->list[PluginBase]:"""Instantiate and get available plugins Returns: List of available plugins (as instances) """# Note: this function is not used by DataLab itself, but it is used by the# test suite to get a list of available pluginsdiscover_plugins()return[plugin_class()forplugin_classinPluginRegistry.get_plugin_classes()]