From 820a592df74bf4b5f9b7f7feb5caba4b325a2f7e Mon Sep 17 00:00:00 2001
From: Stefano Borini <sborini@enthought.com>
Date: Wed, 16 May 2018 15:15:41 +0100
Subject: [PATCH] Reports error in case of failed initialization of factory

---
 force_bdss/base_extension_plugin.py           | 76 +++++++++++++++++--
 .../probe_classes/probe_extension_plugin.py   | 17 +++++
 .../tests/test_base_extension_plugin.py       | 20 +++++
 3 files changed, 107 insertions(+), 6 deletions(-)
 create mode 100644 force_bdss/tests/probe_classes/probe_extension_plugin.py
 create mode 100644 force_bdss/tests/test_base_extension_plugin.py

diff --git a/force_bdss/base_extension_plugin.py b/force_bdss/base_extension_plugin.py
index baaeb42..7ba5490 100644
--- a/force_bdss/base_extension_plugin.py
+++ b/force_bdss/base_extension_plugin.py
@@ -1,6 +1,16 @@
+import logging
+import traceback
+
 from envisage.plugin import Plugin
-from traits.trait_types import List
+from traits.api import List, Unicode, Bool, Type, Either
 
+from force_bdss.data_sources.base_data_source_factory import \
+    BaseDataSourceFactory
+from force_bdss.mco.base_mco_factory import BaseMCOFactory
+from force_bdss.notification_listeners.base_notification_listener_factory \
+    import \
+    BaseNotificationListenerFactory
+from force_bdss.ui_hooks.base_ui_hooks_factory import BaseUIHooksFactory
 from .notification_listeners.i_notification_listener_factory import \
     INotificationListenerFactory
 from .ids import ExtensionPointID
@@ -9,20 +19,40 @@ from .mco.i_mco_factory import IMCOFactory
 from .ui_hooks.i_ui_hooks_factory import IUIHooksFactory
 
 
+logger = logging.getLogger(__name__)
+
+
 class BaseExtensionPlugin(Plugin):
     """Base class for extension plugins, that is, plugins that are
     provided by external contributors.
 
     It provides a set of slots to be populated that end up contributing
     to the application extension points. To use the class, simply inherit it
-    in your plugin, and then define the trait default initializer for the
-    specific trait you want to populate. For example::
+    in your plugin, and then fill the factory_classes trait with the classes
+    you want to export. For example::
 
         class MyPlugin(BaseExtensionPlugin):
-            def _data_source_factories_default(self):
-                return [MyDataSourceFactory1(),
-                        MyDataSourceFactory2()]
+            id = plugin_id("enthought", "myplugin")
+
+            factory_classes = [
+                MyDataSourceFactory1,
+                MyDataSourceFactory2
+                ]
     """
+    #: Reports if the plugin loaded its factories successfully or not.
+    broken = Bool(False)
+
+    #: The error that have been generated by the instantiations.
+    error = Unicode()
+
+    #: A list of all the factory classes to export.
+    factory_classes = List(
+        Either(Type(BaseDataSourceFactory),
+               Type(BaseMCOFactory),
+               Type(BaseNotificationListenerFactory),
+               Type(BaseUIHooksFactory)
+        )
+    )
 
     #: A list of available Multi Criteria Optimizers this plugin exports.
     mco_factories = List(
@@ -36,12 +66,46 @@ class BaseExtensionPlugin(Plugin):
         contributes_to=ExtensionPointID.DATA_SOURCE_FACTORIES
     )
 
+    #: A list of the available notification listeners this plugin exports
     notification_listener_factories = List(
         INotificationListenerFactory,
         contributes_to=ExtensionPointID.NOTIFICATION_LISTENER_FACTORIES
     )
 
+    #: A list of the available ui hooks this plugin exports
     ui_hooks_factories = List(
         IUIHooksFactory,
         contributes_to=ExtensionPointID.UI_HOOKS_FACTORIES
     )
+
+    def _data_source_factories_default(self):
+        return self._instantiate_factories(BaseDataSourceFactory)
+
+    def _mco_factories_default(self):
+        return self._instantiate_factories(BaseMCOFactory)
+
+    def _notification_listener_factories_default(self):
+        return self._instantiate_factories(BaseNotificationListenerFactory)
+
+    def _ui_hooks_factories_default(self):
+        return self._instantiate_factories(BaseUIHooksFactory)
+
+    def _instantiate_factories(self, type_):
+        if self.broken:
+            logger.error(
+                "Skipping instantiation of {} due to previous errors.".format(
+                    type_.__name__))
+            return []
+
+        try:
+            return [
+                factory(self)
+                for factory in self._factory_by_type(type_)
+            ]
+        except Exception as e:
+            self.broken = True
+            self.error = traceback.format_exc()
+            logger.exception(e)
+
+    def _factory_by_type(self, type_):
+        return [cls for cls in self.factory_classes if issubclass(cls, type_)]
diff --git a/force_bdss/tests/probe_classes/probe_extension_plugin.py b/force_bdss/tests/probe_classes/probe_extension_plugin.py
new file mode 100644
index 0000000..115e02a
--- /dev/null
+++ b/force_bdss/tests/probe_classes/probe_extension_plugin.py
@@ -0,0 +1,17 @@
+from force_bdss.base_extension_plugin import BaseExtensionPlugin
+from force_bdss.ids import plugin_id
+from force_bdss.tests.probe_classes.data_source import ProbeDataSourceFactory
+from force_bdss.tests.probe_classes.mco import ProbeMCOFactory
+from force_bdss.tests.probe_classes.notification_listener import \
+    ProbeNotificationListenerFactory
+from force_bdss.tests.probe_classes.ui_hooks import ProbeUIHooksFactory
+
+
+class ProbeExtensionPlugin(BaseExtensionPlugin):
+    id = plugin_id("enthought", "test")
+    factory_classes = [
+        ProbeDataSourceFactory,
+        ProbeMCOFactory,
+        ProbeNotificationListenerFactory,
+        ProbeUIHooksFactory,
+    ]
diff --git a/force_bdss/tests/test_base_extension_plugin.py b/force_bdss/tests/test_base_extension_plugin.py
new file mode 100644
index 0000000..5053499
--- /dev/null
+++ b/force_bdss/tests/test_base_extension_plugin.py
@@ -0,0 +1,20 @@
+import unittest
+
+from force_bdss.tests.probe_classes.probe_extension_plugin import \
+    ProbeExtensionPlugin
+
+try:
+    import mock
+except ImportError:
+    from unittest import mock
+
+
+class TestBaseExtensionPlugin(unittest.TestCase):
+    def test_basic_init(self):
+        plugin = ProbeExtensionPlugin()
+        self.assertEqual(len(plugin.data_source_factories), 1)
+        self.assertEqual(len(plugin.notification_listener_factories), 1)
+        self.assertEqual(len(plugin.mco_factories), 1)
+        self.assertEqual(len(plugin.ui_hooks_factories), 1)
+        self.assertFalse(plugin.broken)
+        self.assertEqual(plugin.error, "")
-- 
GitLab