From 57719b8c0cf1f525e3329caa05269530301f9003 Mon Sep 17 00:00:00 2001
From: Stefano Borini <sborini@enthought.com>
Date: Tue, 8 Aug 2017 17:53:30 +0100
Subject: [PATCH] Working implementation

---
 force_bdss/api.py                             |  5 +++
 force_bdss/base_extension_plugin.py           |  8 +++--
 force_bdss/core_mco_driver.py                 | 34 ++++++++++++++++---
 .../dummy/dummy_dakota/dakota_optimizer.py    |  4 +++
 .../__init__.py                               |  0
 .../dummy_notification_listener.py            |  6 ++++
 .../dummy_notification_listener_factory.py    | 23 +++++++++++++
 .../dummy_notification_listener_model.py      |  7 ++++
 force_bdss/core_plugins/dummy/dummy_plugin.py |  5 ++-
 .../gui_notification_listener_factory.py      |  0
 .../base_notification_listener.py             | 29 ++++++++++++++++
 .../base_notification_listener_factory.py     | 20 +++++++++--
 .../base_notification_listener_model.py       | 22 ++++++++++++
 .../i_notification_listener_factory.py        |  9 +++--
 14 files changed, 160 insertions(+), 12 deletions(-)
 rename force_bdss/core_plugins/dummy/{ui_notification_listener => dummy_notification_listener}/__init__.py (100%)
 create mode 100644 force_bdss/core_plugins/dummy/dummy_notification_listener/dummy_notification_listener.py
 create mode 100644 force_bdss/core_plugins/dummy/dummy_notification_listener/dummy_notification_listener_factory.py
 create mode 100644 force_bdss/core_plugins/dummy/dummy_notification_listener/dummy_notification_listener_model.py
 delete mode 100644 force_bdss/core_plugins/dummy/gui_notification_listener/gui_notification_listener_factory.py
 create mode 100644 force_bdss/notification_listeners/base_notification_listener.py
 create mode 100644 force_bdss/notification_listeners/base_notification_listener_model.py

diff --git a/force_bdss/api.py b/force_bdss/api.py
index 129f5cc..1713d4f 100644
--- a/force_bdss/api.py
+++ b/force_bdss/api.py
@@ -20,3 +20,8 @@ from .mco.i_mco_factory import IMCOFactory  # noqa
 
 from .mco.parameters.base_mco_parameter_factory import BaseMCOParameterFactory  # noqa
 from .mco.parameters.base_mco_parameter import BaseMCOParameter  # noqa
+
+from .notification_listeners.i_notification_listener_factory import INotificationListenerFactory  # noqa
+from .notification_listeners.base_notification_listener import BaseNotificationListener  # noqa
+from .notification_listeners.base_notification_listener_factory import BaseNotificationListenerFactory  # noqa
+from .notification_listeners.base_notification_listener_model import BaseNotificationListenerModel  # noqa
diff --git a/force_bdss/base_extension_plugin.py b/force_bdss/base_extension_plugin.py
index 8954d00..692b9fa 100644
--- a/force_bdss/base_extension_plugin.py
+++ b/force_bdss/base_extension_plugin.py
@@ -1,6 +1,8 @@
 from envisage.plugin import Plugin
 from traits.trait_types import List
 
+from .notification_listeners.i_notification_listener_factory import \
+    INotificationListenerFactory
 from .ids import ExtensionPointID
 from .data_sources.i_data_source_factory import IDataSourceFactory
 from .kpi.i_kpi_calculator_factory import IKPICalculatorFactory
@@ -40,7 +42,7 @@ class BaseExtensionPlugin(Plugin):
         contributes_to=ExtensionPointID.KPI_CALCULATOR_FACTORIES
     )
 
-    notifier_factory = List(
-        INotifierFactory,
-        contributes_to=ExtensionPointID.NOTIFIER_FACTORIES
+    notification_listener_factories = List(
+        INotificationListenerFactory,
+        contributes_to=ExtensionPointID.NOTIFICATION_LISTENER_FACTORIES
     )
diff --git a/force_bdss/core_mco_driver.py b/force_bdss/core_mco_driver.py
index 729f569..d140f06 100644
--- a/force_bdss/core_mco_driver.py
+++ b/force_bdss/core_mco_driver.py
@@ -2,9 +2,11 @@ from __future__ import print_function
 
 import sys
 
-from traits.api import on_trait_change, Instance
+from traits.api import on_trait_change, Instance, List
 
 from force_bdss.mco.base_mco import BaseMCO
+from force_bdss.notification_listeners.base_notification_listener import \
+    BaseNotificationListener
 from .ids import plugin_id
 from .base_core_driver import BaseCoreDriver
 from .io.workflow_reader import (
@@ -23,10 +25,13 @@ class CoreMCODriver(BaseCoreDriver):
 
     mco = Instance(BaseMCO, allow_none=True)
 
-    listeners = Instance(BaseNotificationListener)
+    listeners = List(Instance(BaseNotificationListener))
 
     @on_trait_change("application:started")
     def application_started(self):
+        self.mco.run(self.workflow.mco)
+
+    def _mco_default(self):
         try:
             workflow = self.workflow
         except (InvalidVersionException, InvalidFileException) as e:
@@ -35,5 +40,26 @@ class CoreMCODriver(BaseCoreDriver):
 
         mco_model = workflow.mco
         mco_factory = mco_model.factory
-        mco = mco_factory.create_optimizer()
-        mco.run(mco_model)
+        return mco_factory.create_optimizer()
+
+    @on_trait_change("mco:started,mco:finished,mco:progress")
+    def _handle_mco_event(self, object, name, old, new):
+        if name == "started":
+            self._deliver_to_listeners("MCO_STARTED")
+        elif name == "finished":
+            self._deliver_to_listeners("MCO_FINISHED")
+        elif name == "progress":
+            self._deliver_to_listeners("MCO_PROGRESS")
+
+    def _deliver_to_listeners(self, message):
+        for listener in self.listeners:
+            listener.deliver(None, message)
+
+    def _listeners_default(self):
+        listeners = []
+
+        print(self.factory_registry.notification_listener_factories)
+        for factory in self.factory_registry.notification_listener_factories:
+            listeners.append(factory.create_listener())
+
+        return listeners
diff --git a/force_bdss/core_plugins/dummy/dummy_dakota/dakota_optimizer.py b/force_bdss/core_plugins/dummy/dummy_dakota/dakota_optimizer.py
index 5b1291a..d97c414 100644
--- a/force_bdss/core_plugins/dummy/dummy_dakota/dakota_optimizer.py
+++ b/force_bdss/core_plugins/dummy/dummy_dakota/dakota_optimizer.py
@@ -16,6 +16,7 @@ def rotated_range(start, stop, starting_value):
 
 class DummyDakotaOptimizer(BaseMCO):
     def run(self, model):
+        self.started = True
         parameters = model.parameters
 
         values = []
@@ -42,3 +43,6 @@ class DummyDakotaOptimizer(BaseMCO):
                 " ".join([str(v) for v in value]).encode("utf-8"))
             print("{}: {}".format(" ".join([str(v) for v in value]),
                                   out[0].decode("utf-8")))
+            self.progress = True
+
+        self.finished = True
diff --git a/force_bdss/core_plugins/dummy/ui_notification_listener/__init__.py b/force_bdss/core_plugins/dummy/dummy_notification_listener/__init__.py
similarity index 100%
rename from force_bdss/core_plugins/dummy/ui_notification_listener/__init__.py
rename to force_bdss/core_plugins/dummy/dummy_notification_listener/__init__.py
diff --git a/force_bdss/core_plugins/dummy/dummy_notification_listener/dummy_notification_listener.py b/force_bdss/core_plugins/dummy/dummy_notification_listener/dummy_notification_listener.py
new file mode 100644
index 0000000..bfa1a10
--- /dev/null
+++ b/force_bdss/core_plugins/dummy/dummy_notification_listener/dummy_notification_listener.py
@@ -0,0 +1,6 @@
+from force_bdss.api import BaseNotificationListener
+
+
+class DummyNotificationListener(BaseNotificationListener):
+    def deliver(self, model, message):
+        print(message)
diff --git a/force_bdss/core_plugins/dummy/dummy_notification_listener/dummy_notification_listener_factory.py b/force_bdss/core_plugins/dummy/dummy_notification_listener/dummy_notification_listener_factory.py
new file mode 100644
index 0000000..74d6b38
--- /dev/null
+++ b/force_bdss/core_plugins/dummy/dummy_notification_listener/dummy_notification_listener_factory.py
@@ -0,0 +1,23 @@
+from traits.api import String
+
+from force_bdss.ids import factory_id
+from force_bdss.notification_listeners.base_notification_listener_factory \
+    import \
+    BaseNotificationListenerFactory
+from .dummy_notification_listener import DummyNotificationListener
+from .dummy_notification_listener_model import DummyNotificationListenerModel
+
+
+class DummyNotificationListenerFactory(BaseNotificationListenerFactory):
+    id = String(factory_id("enthought", "dummy_notification_listener"))
+
+    name = String("Dummy Notification Listener")
+
+    def create_model(self, model_data=None):
+        if model_data is None:
+            model_data = {}
+
+        return DummyNotificationListenerModel(self, **model_data)
+
+    def create_listener(self):
+        return DummyNotificationListener(self)
diff --git a/force_bdss/core_plugins/dummy/dummy_notification_listener/dummy_notification_listener_model.py b/force_bdss/core_plugins/dummy/dummy_notification_listener/dummy_notification_listener_model.py
new file mode 100644
index 0000000..78443c4
--- /dev/null
+++ b/force_bdss/core_plugins/dummy/dummy_notification_listener/dummy_notification_listener_model.py
@@ -0,0 +1,7 @@
+from force_bdss.notification_listeners.base_notification_listener_model \
+    import \
+    BaseNotificationListenerModel
+
+
+class DummyNotificationListenerModel(BaseNotificationListenerModel):
+    pass
diff --git a/force_bdss/core_plugins/dummy/dummy_plugin.py b/force_bdss/core_plugins/dummy/dummy_plugin.py
index 1ff6590..bc64438 100644
--- a/force_bdss/core_plugins/dummy/dummy_plugin.py
+++ b/force_bdss/core_plugins/dummy/dummy_plugin.py
@@ -1,4 +1,7 @@
 from force_bdss.api import BaseExtensionPlugin, plugin_id
+from force_bdss.core_plugins.dummy.dummy_notification_listener\
+    .dummy_notification_listener_factory import \
+    DummyNotificationListenerFactory
 from .csv_extractor.csv_extractor_factory import CSVExtractorFactory
 from .kpi_adder.kpi_adder_factory import KPIAdderFactory
 from .dummy_dakota.dakota_factory import DummyDakotaFactory
@@ -23,4 +26,4 @@ class DummyPlugin(BaseExtensionPlugin):
                 KPIAdderFactory(self)]
 
     def _notification_listener_factories_default(self):
-        return [GUINotificationListenerFactory()]
+        return [DummyNotificationListenerFactory(self)]
diff --git a/force_bdss/core_plugins/dummy/gui_notification_listener/gui_notification_listener_factory.py b/force_bdss/core_plugins/dummy/gui_notification_listener/gui_notification_listener_factory.py
deleted file mode 100644
index e69de29..0000000
diff --git a/force_bdss/notification_listeners/base_notification_listener.py b/force_bdss/notification_listeners/base_notification_listener.py
new file mode 100644
index 0000000..32293b3
--- /dev/null
+++ b/force_bdss/notification_listeners/base_notification_listener.py
@@ -0,0 +1,29 @@
+import abc
+
+from traits.api import ABCHasStrictTraits, Instance
+
+from .i_notification_listener_factory import INotificationListenerFactory
+
+
+class BaseNotificationListener(ABCHasStrictTraits):
+    """Base class for the Multi Criteria Optimizer.
+
+    Inherit this class for your MCO implementation
+    """
+    #: A reference to the factory
+    factory = Instance(INotificationListenerFactory)
+
+    def __init__(self, factory, *args, **kwargs):
+        """Initializes the MCO.
+
+        Parameters
+        ----------
+        factory: BaseMCOFactory
+            The factory this BaseMCO belongs to
+        """
+        self.factory = factory
+        super(BaseNotificationListener, self).__init__(*args, **kwargs)
+
+    @abc.abstractmethod
+    def deliver(self, model, message):
+        pass
diff --git a/force_bdss/notification_listeners/base_notification_listener_factory.py b/force_bdss/notification_listeners/base_notification_listener_factory.py
index 58d4d08..18389a3 100644
--- a/force_bdss/notification_listeners/base_notification_listener_factory.py
+++ b/force_bdss/notification_listeners/base_notification_listener_factory.py
@@ -1,6 +1,6 @@
 import abc
 
-from traits.api import ABCHasStrictTraits, Instance, String, provides
+from traits.api import ABCHasStrictTraits, Instance, String, provides, Any
 from envisage.plugin import Plugin
 
 from .i_notification_listener_factory import INotificationListenerFactory
@@ -14,10 +14,26 @@ class BaseNotificationListenerFactory(ABCHasStrictTraits):
 
     plugin = Instance(Plugin)
 
+    persistent_state = Any()
+
+    def __init__(self, plugin, *args, **kwargs):
+        """Initializes the instance.
+
+        Parameters
+        ----------
+        plugin: Plugin
+            The plugin that holds this factory.
+        """
+        self.plugin = plugin
+        super(BaseNotificationListenerFactory, self).__init__(*args, **kwargs)
+
     @abc.abstractmethod
-    def create_object(self):
+    def create_listener(self):
         """"""
 
     @abc.abstractmethod
     def create_model(self, model_data=None):
         """"""
+
+    def init_persistent_state(self):
+        pass
diff --git a/force_bdss/notification_listeners/base_notification_listener_model.py b/force_bdss/notification_listeners/base_notification_listener_model.py
new file mode 100644
index 0000000..821a650
--- /dev/null
+++ b/force_bdss/notification_listeners/base_notification_listener_model.py
@@ -0,0 +1,22 @@
+from traits.api import ABCHasStrictTraits, Instance
+
+from force_bdss.notification_listeners.i_notification_listener_factory import \
+    INotificationListenerFactory
+
+
+class BaseNotificationListenerModel(ABCHasStrictTraits):
+    """Base class for the specific MCO models.
+    This model will also provide, through traits/traitsui magic the View
+    that will appear in the workflow manager UI.
+
+    In your definition, your specific model must reimplement this class.
+    """
+    #: A reference to the creating factory, so that we can
+    #: retrieve it as the originating factory.
+    factory = Instance(INotificationListenerFactory,
+                       visible=False,
+                       transient=True)
+
+    def __init__(self, factory, *args, **kwargs):
+        self.factory = factory
+        super(BaseNotificationListenerModel, self).__init__(*args, **kwargs)
diff --git a/force_bdss/notification_listeners/i_notification_listener_factory.py b/force_bdss/notification_listeners/i_notification_listener_factory.py
index 6a2c1e5..a269f8c 100644
--- a/force_bdss/notification_listeners/i_notification_listener_factory.py
+++ b/force_bdss/notification_listeners/i_notification_listener_factory.py
@@ -1,4 +1,4 @@
-from traits.api import Interface, String, Instance
+from traits.api import Interface, String, Instance, Any
 from envisage.plugin import Plugin
 
 
@@ -14,8 +14,13 @@ class INotificationListenerFactory(Interface):
 
     plugin = Instance(Plugin)
 
-    def create_object(self):
+    persistent_state = Any
+
+    def create_listener(self):
         """"""
 
     def create_model(self, model_data=None):
         """"""
+
+    def init_persistent_state(self):
+        pass
-- 
GitLab