From 8ad0a7de795bd67d5f73d0b446e06e9cc3cb9364 Mon Sep 17 00:00:00 2001 From: Stefano Borini <sborini@enthought.com> Date: Thu, 17 May 2018 13:58:59 +0100 Subject: [PATCH] Changed infrastructure for plugin and factories --- force_bdss/api.py | 2 +- force_bdss/base_extension_plugin.py | 43 +------------- .../data_sources/base_data_source_factory.py | 59 ++++++++++++++++++- force_bdss/ids.py | 58 ++++++++++-------- force_bdss/mco/base_mco_factory.py | 39 +++++++++++- .../parameters/base_mco_parameter_factory.py | 36 +++++++++-- .../base_notification_listener_factory.py | 35 +++++++++-- force_bdss/ui_hooks/base_ui_hooks_factory.py | 22 +++++++ 8 files changed, 214 insertions(+), 80 deletions(-) diff --git a/force_bdss/api.py b/force_bdss/api.py index 4e3950b..2f83fe3 100644 --- a/force_bdss/api.py +++ b/force_bdss/api.py @@ -1,5 +1,5 @@ from .base_extension_plugin import BaseExtensionPlugin # noqa -from .ids import factory_id, mco_parameter_id # noqa +from .ids import plugin_id # noqa from .core.data_value import DataValue # noqa from .core.workflow import Workflow # noqa diff --git a/force_bdss/base_extension_plugin.py b/force_bdss/base_extension_plugin.py index f4ee680..1f1a0c1 100644 --- a/force_bdss/base_extension_plugin.py +++ b/force_bdss/base_extension_plugin.py @@ -28,11 +28,7 @@ class BaseExtensionPlugin(Plugin): in your plugin, and reimplement the methods as from example:: class MyPlugin(BaseExtensionPlugin): - def get_producer(self): - return "enthought" - - def get_identifier(self): - return "myplugin" + id = plugin_id("enthought", "plugin_name", 0) def get_factory_classes(self): return [ @@ -86,26 +82,8 @@ class BaseExtensionPlugin(Plugin): _logger = Instance(logging.Logger) def __init__(self, *args, **kwargs): - broken = False - error = "" - - if "id" not in kwargs: - try: - id_ = plugin_id(self.get_producer(), self.get_identifier()) - except Exception as e: - self._logger.exception(e) - error = traceback.format_exc() - broken = True - else: - kwargs["id"] = id_ - super(BaseExtensionPlugin, self).__init__(*args, **kwargs) - if broken: - self.broken = True - self.error = error - return - try: self.factory_classes = self.get_factory_classes() self.mco_factories[:] = [ @@ -133,25 +111,6 @@ class BaseExtensionPlugin(Plugin): self.notification_listener_factories[:] = [] self.ui_hooks_factories[:] = [] - def get_producer(self): - """Must be reimplemented to return a string with the name of the - company producing this plugin. Examples are "enthought", "itwm" etc. - """ - - raise NotImplementedError( - "get_producer was not implemented in plugin {}".format( - self.__class__)) - - def get_identifier(self): - """Must return a string with the name of the plugin the producer - is releasing. The name must be unique and is responsibility of - the producer to guarantee this name is not conflicting with - another already existing plugin - """ - raise NotImplementedError( - "get_identifier was not implemented in plugin {}".format( - self.__class__)) - def get_factory_classes(self): """Must return a list of factory classes that this plugin exports. """ diff --git a/force_bdss/data_sources/base_data_source_factory.py b/force_bdss/data_sources/base_data_source_factory.py index 7452c49..698cdb5 100644 --- a/force_bdss/data_sources/base_data_source_factory.py +++ b/force_bdss/data_sources/base_data_source_factory.py @@ -1,10 +1,11 @@ import logging -from traits.api import ABCHasStrictTraits, provides, String, Instance, Type +from traits.api import ABCHasStrictTraits, provides, Str, Instance, Type from envisage.plugin import Plugin from force_bdss.data_sources.base_data_source import BaseDataSource from force_bdss.data_sources.base_data_source_model import BaseDataSourceModel from force_bdss.data_sources.i_data_source_factory import IDataSourceFactory +from force_bdss.ids import factory_id log = logging.getLogger(__name__) @@ -13,15 +14,30 @@ log = logging.getLogger(__name__) class BaseDataSourceFactory(ABCHasStrictTraits): """Base class for DataSource factories. Reimplement this class to create your own DataSource. + + You must reimplement the following methods as from example:: + + class MyDataSourceFactory(BaseDataSourceFactory) + def get_data_source_class(self): + return MyDataSource + + def get_data_source_model(self): + return MyDataSourceModel + + def get_name(self): + return "My data source" + + def get_identifier(self): + return "my_data_source" """ # NOTE: changes to this class must be ported also to the IDataSourceFactory #: Unique identifier that identifies the factory uniquely in the #: universe of factories. Create one with the function factory_id() - id = String() + id = Str() #: A human readable name of the factory. Spaces allowed - name = String() + name = Str() #: The data source to be instantiated. Define this to your DataSource data_source_class = Type(BaseDataSource) @@ -39,6 +55,43 @@ class BaseDataSourceFactory(ABCHasStrictTraits): self.plugin = plugin super(BaseDataSourceFactory, self).__init__(*args, **kwargs) + self.data_source_class = self.get_data_source_class() + self.model_class = self.get_model_class() + self.name = self.get_name() + identifier = self.get_identifier() + self.id = factory_id(self.plugin.id, identifier) + + def get_data_source_class(self): + """Must be reimplemented to return the DataSource class. + """ + raise NotImplementedError( + "get_data_source_class was not implemented in factory {}".format( + self.__class__)) + + def get_model_class(self): + """Must be reimplemented to return the DataSourceModel class. + """ + raise NotImplementedError( + "get_model_class was not implemented in factory {}".format( + self.__class__)) + + def get_name(self): + """Must be reimplemented to return a user-visible name of the + data source. + """ + raise NotImplementedError( + "get_name was not implemented in factory {}".format( + self.__class__)) + + def get_identifier(self): + """Must be reimplemented to return a unique string identifying + the factory. The provider is responsible to guarantee this identifier + to be unique across the plugin data sources. + """ + raise NotImplementedError( + "get_name was not implemented in factory {}".format( + self.__class__)) + def create_data_source(self): """Factory method. Must return the factory-specific BaseDataSource instance. diff --git a/force_bdss/ids.py b/force_bdss/ids.py index c62d52b..f4bf5e3 100644 --- a/force_bdss/ids.py +++ b/force_bdss/ids.py @@ -17,57 +17,69 @@ class ExtensionPointID: UI_HOOKS_FACTORIES = 'force.bdss.ui_hooks.factories' -def factory_id(producer, identifier): +def factory_id(plugin_id, identifier): """Creates an id for the factory. Parameters ---------- - producer: str - the company or research institute unique identifier (e.g. "enthought") + plugin_id: str + the id of the plugin that contains this factory identifier: str - A unique identifier for the factory. The producer has authority and + A unique identifier for the factory. The identifier should be unique control over the uniqueness of this identifier. Returns ------- str: an identifier to be used in the factory. """ - return _string_id(producer, "factory", identifier) + return _string_id(plugin_id, "factory", identifier) -def mco_parameter_id(producer, mco_identifier, parameter_identifier): +def mco_parameter_id(mco_factory_id, parameter_identifier): """Creates an ID for an MCO parameter, so that it can be identified uniquely.""" - return _string_id(producer, - "factory", - mco_identifier, - "parameter", - parameter_identifier) + return _string_id(mco_factory_id, "parameter", parameter_identifier) -def plugin_id(producer, identifier): +def plugin_id(producer, identifier, version): """Creates an ID for the plugins. These must be defined, otherwise the envisage system will complain (but not break) + + Parameters + ---------- + producer: str + A unique string identifying the producer (company/research institute) + of the plugin (e.g. "enthought", "itwm") + identifier: str + A string identifying the plugin. It must be unique within the context + of the producer, who is responsible to guarantee that plugin names + are unique + version: int + A version number for the plugin. """ - return _string_id(producer, "plugin", identifier) + if not isinstance(version, int) or version < 0: + raise ValueError("version must be a non negative integer") + + return _string_id("force", + "bdss", + producer, + "plugin", + identifier, + "v{}".format(version)) def _string_id(*args): - """Creates an id for a generic entity. + """Creates an id for a generic entity, by concatenating the given args + with dots. Parameters ---------- - entity_namespace: str - A namespace for the entity we want to address (e.g. "factory") - producer: str - the company or research institute unique identifier (e.g. "enthought") - identifier: str - A unique identifier for the factory. The producer has authority and - control over the uniqueness of this identifier. + *args: str + The strings to concatenate Returns ------- - str: an identifier to be used in the factory. + str: an identifier to be used. """ def is_valid(entry): return ( @@ -79,4 +91,4 @@ def _string_id(*args): raise ValueError("One or more of the specified parameters was " "invalid: {}".format(str(args))) - return ".".join(["force", "bdss"]+list(args)) + return ".".join(list(args)) diff --git a/force_bdss/mco/base_mco_factory.py b/force_bdss/mco/base_mco_factory.py index 281351e..44ebde9 100644 --- a/force_bdss/mco/base_mco_factory.py +++ b/force_bdss/mco/base_mco_factory.py @@ -1,7 +1,8 @@ import logging -from traits.api import ABCHasStrictTraits, String, provides, Instance, Type +from traits.api import ABCHasStrictTraits, Str, provides, Instance, Type from envisage.plugin import Plugin +from force_bdss.ids import factory_id from force_bdss.mco.base_mco import BaseMCO from force_bdss.mco.base_mco_communicator import BaseMCOCommunicator from force_bdss.mco.base_mco_model import BaseMCOModel @@ -18,10 +19,10 @@ class BaseMCOFactory(ABCHasStrictTraits): # in the IMultiCriteriaOptimizerFactory interface class. #: A unique ID produced with the factory_id() routine. - id = String() + id = Str() #: A user friendly name of the factory. Spaces allowed. - name = String() + name = Str() #: The optimizer class to instantiate. Define this to your MCO class. optimizer_class = Type(BaseMCO) @@ -39,6 +40,38 @@ class BaseMCOFactory(ABCHasStrictTraits): self.plugin = plugin super(BaseMCOFactory, self).__init__(*args, **kwargs) + self.name = self.get_name() + self.optimizer_class = self.get_optimizer_class() + self.model_class = self.get_model_class() + self.communicator_class = self.get_communicator_class() + identifier = self.get_identifier() + self.id = factory_id(self.plugin.id, identifier) + + def get_optimizer_class(self): + raise NotImplementedError( + "get_optimizer_class was not implemented in factory {}".format( + self.__class__)) + + def get_model_class(self): + raise NotImplementedError( + "get_model_class was not implemented in factory {}".format( + self.__class__)) + + def get_communicator_class(self): + raise NotImplementedError( + "get_communicator_class was not implemented in factory {}".format( + self.__class__)) + + def get_identifier(self): + raise NotImplementedError( + "get_identifier was not implemented in factory {}".format( + self.__class__)) + + def get_name(self): + raise NotImplementedError( + "get_name was not implemented in factory {}".format( + self.__class__)) + def create_optimizer(self): """Factory method. Creates the optimizer with the given application diff --git a/force_bdss/mco/parameters/base_mco_parameter_factory.py b/force_bdss/mco/parameters/base_mco_parameter_factory.py index cd8751b..c6c16f9 100644 --- a/force_bdss/mco/parameters/base_mco_parameter_factory.py +++ b/force_bdss/mco/parameters/base_mco_parameter_factory.py @@ -1,4 +1,6 @@ -from traits.api import HasStrictTraits, String, Type, Instance +from traits.api import HasStrictTraits, Str, Type, Instance + +from force_bdss.ids import mco_parameter_id class BaseMCOParameterFactory(HasStrictTraits): @@ -14,23 +16,49 @@ class BaseMCOParameterFactory(HasStrictTraits): mco_factory = Instance('force_bdss.mco.base_mco_factory.BaseMCOFactory') #: A unique string identifying the parameter - id = String() + id = Str() #: A user friendly name (for the UI) - name = String("Undefined parameter") + name = Str("Undefined parameter") #: A long description of the parameter - description = String("Undefined parameter") + description = Str("Undefined parameter") # The model class to instantiate when create_model is called. model_class = Type( "force_bdss.mco.parameters.base_mco_parameter.BaseMCOParameter" ) + def get_identifier(self): + raise NotImplementedError( + "get_identifier was not implemented in factory {}".format( + self.__class__)) + + def get_name(self): + raise NotImplementedError( + "get_name was not implemented in factory {}".format( + self.__class__)) + + def get_description(self): + raise NotImplementedError( + "get_description was not implemented in factory {}".format( + self.__class__)) + + def get_model_class(self): + raise NotImplementedError( + "get_model_class was not implemented in factory {}".format( + self.__class__)) + def __init__(self, mco_factory, *args, **kwargs): self.mco_factory = mco_factory super(BaseMCOParameterFactory, self).__init__(*args, **kwargs) + self.name = self.get_name() + self.description = self.get_description() + self.model_class = self.get_model_class() + identifier = self.get_identifier() + self.id = mco_parameter_id(self.mco_factory.id, identifier) + def create_model(self, data_values=None): """Creates the instance of the model class and returns it. You should not reimplement this, as the default is generally ok. diff --git a/force_bdss/notification_listeners/base_notification_listener_factory.py b/force_bdss/notification_listeners/base_notification_listener_factory.py index f892d9c..7e7d30b 100644 --- a/force_bdss/notification_listeners/base_notification_listener_factory.py +++ b/force_bdss/notification_listeners/base_notification_listener_factory.py @@ -1,9 +1,10 @@ import logging from traits.api import ( - ABCHasStrictTraits, Instance, String, provides, Type, Bool + HasStrictTraits, Instance, Str, provides, Type, Bool ) from envisage.plugin import Plugin +from force_bdss.ids import factory_id from force_bdss.notification_listeners.base_notification_listener import \ BaseNotificationListener from force_bdss.notification_listeners.base_notification_listener_model \ @@ -15,16 +16,16 @@ log = logging.getLogger(__name__) @provides(INotificationListenerFactory) -class BaseNotificationListenerFactory(ABCHasStrictTraits): +class BaseNotificationListenerFactory(HasStrictTraits): """Base class for notification listeners. Notification listeners are extensions that receive event notifications from the MCO and perform an associated action. """ #: identifier of the factory - id = String() + id = Str() #: Name of the factory. User friendly for UI - name = String() + name = Str() #: If the factor should be visible in the UI. Set to false to make it #: invisible. This is normally useful for notification systems that are @@ -53,6 +54,32 @@ class BaseNotificationListenerFactory(ABCHasStrictTraits): self.plugin = plugin super(BaseNotificationListenerFactory, self).__init__(*args, **kwargs) + self.listener_class = self.get_listener_class() + self.model_class = self.get_model_class() + self.name = self.get_name() + identifier = self.get_identifier() + self.id = factory_id(self.plugin.id, identifier) + + def get_listener_class(self): + raise NotImplementedError( + "get_listener_class was not implemented in factory {}".format( + self.__class__)) + + def get_model_class(self): + raise NotImplementedError( + "get_model_class was not implemented in factory {}".format( + self.__class__)) + + def get_identifier(self): + raise NotImplementedError( + "get_identifier was not implemented in factory {}".format( + self.__class__)) + + def get_name(self): + raise NotImplementedError( + "get_name was not implemented in factory {}".format( + self.__class__)) + def create_listener(self): """ Creates an instance of the listener. diff --git a/force_bdss/ui_hooks/base_ui_hooks_factory.py b/force_bdss/ui_hooks/base_ui_hooks_factory.py index adb07b9..111a858 100644 --- a/force_bdss/ui_hooks/base_ui_hooks_factory.py +++ b/force_bdss/ui_hooks/base_ui_hooks_factory.py @@ -2,6 +2,7 @@ import logging from traits.api import ABCHasStrictTraits, Instance, String, provides, Type from envisage.plugin import Plugin +from force_bdss.ids import factory_id from force_bdss.ui_hooks.base_ui_hooks_manager import BaseUIHooksManager from .i_ui_hooks_factory import IUIHooksFactory @@ -38,6 +39,27 @@ class BaseUIHooksFactory(ABCHasStrictTraits): self.plugin = plugin super(BaseUIHooksFactory, self).__init__(*args, **kwargs) + self.ui_hooks_manager_class = self.get_ui_hooks_manager_class() + self.name = self.get_name() + identifier = self.get_identifier() + self.id = factory_id(self.plugin.id, identifier) + + def get_ui_hooks_manager_class(self): + raise NotImplementedError( + "get_ui_hooks_manager_class was not implemented " + "in factory {}".format( + self.__class__)) + + def get_name(self): + raise NotImplementedError( + "get_name was not implemented in factory {}".format( + self.__class__)) + + def get_identifier(self): + raise NotImplementedError( + "get_identifier was not implemented in factory {}".format( + self.__class__)) + def create_ui_hooks_manager(self): """Creates an instance of the hook manager. The hooks manager contains a set of methods that are applicable in -- GitLab