From 3adac382f4203930597c1426da36bb542fd05cdc Mon Sep 17 00:00:00 2001
From: Stefano Borini <sborini@enthought.com>
Date: Thu, 26 Apr 2018 13:59:32 +0100
Subject: [PATCH] Changed output_slot_names to output_slot_info and fixed tests

---
 force_bdss/core/output_slot_info.py           | 19 ++++++++++++
 force_bdss/core_evaluation_driver.py          |  8 ++---
 force_bdss/core_mco_driver.py                 |  4 +--
 .../data_sources/base_data_source_model.py    | 11 +++++--
 .../tests/test_base_data_source_model.py      | 21 ++++++++++++--
 force_bdss/io/workflow_reader.py              |  8 +++++
 force_bdss/kpi/base_kpi_calculator_model.py   |  7 +++--
 .../tests/test_base_kpi_calculator_model.py   | 29 +++++++++++++------
 force_bdss/tests/fixtures/test_null.json      |  4 +--
 .../tests/test_core_evaluation_driver.py      | 27 +++++++++++++----
 10 files changed, 107 insertions(+), 31 deletions(-)
 create mode 100644 force_bdss/core/output_slot_info.py

diff --git a/force_bdss/core/output_slot_info.py b/force_bdss/core/output_slot_info.py
new file mode 100644
index 0000000..f294c53
--- /dev/null
+++ b/force_bdss/core/output_slot_info.py
@@ -0,0 +1,19 @@
+from traits.api import HasStrictTraits, Bool
+
+from ..local_traits import Identifier
+
+
+class OutputSlotInfo(HasStrictTraits):
+    """
+    Class that specifies the name and characteristics of the output slots
+    of a data source.
+    This entity will go in the model object, and associates the positional
+    order in the containing list with the variable name that refers to the
+    value that should be taken.
+    """
+    #: The user defined name of the variable containing the value.
+    name = Identifier()
+
+    #: True if the value associated to this output slot must be exported as
+    #: a KPI.
+    kpi = Bool(False)
diff --git a/force_bdss/core_evaluation_driver.py b/force_bdss/core_evaluation_driver.py
index 8f75105..6ff50dd 100644
--- a/force_bdss/core_evaluation_driver.py
+++ b/force_bdss/core_evaluation_driver.py
@@ -143,7 +143,7 @@ def _compute_layer_results(environment_data_values,
             log.error(error_txt)
             raise RuntimeError(error_txt)
 
-        if len(res) != len(model.output_slot_names):
+        if len(res) != len(model.output_slot_info):
             error_txt = (
                 "The number of data values ({} values) returned"
                 " by '{}' does not match the number"
@@ -152,7 +152,7 @@ def _compute_layer_results(environment_data_values,
                 " error.").format(
                 len(res),
                 factory.name,
-                len(model.output_slot_names)
+                len(model.output_slot_info)
             )
 
             log.error(error_txt)
@@ -160,8 +160,8 @@ def _compute_layer_results(environment_data_values,
 
         # At this point, the returned data values are unnamed.
         # Add the names as specified by the user.
-        for dv, output_slot_name in zip(res, model.output_slot_names):
-            dv.name = output_slot_name
+        for dv, output_slot_info in zip(res, model.output_slot_info):
+            dv.name = output_slot_info.name
 
         # If the name was not specified, simply discard the value,
         # because apparently the user is not interested in it.
diff --git a/force_bdss/core_mco_driver.py b/force_bdss/core_mco_driver.py
index ea2cbfa..79c6844 100644
--- a/force_bdss/core_mco_driver.py
+++ b/force_bdss/core_mco_driver.py
@@ -57,11 +57,11 @@ class CoreMCODriver(BaseCoreDriver):
     def _deliver_start_event(self):
         output_names = []
         for kpi in self.workflow.kpi_calculators:
-            output_names.extend(kpi.output_slot_names)
+            output_names.extend(kpi.output_slot_info)
 
         self._deliver_event(MCOStartEvent(
             input_names=tuple(p.name for p in self.workflow.mco.parameters),
-            output_names=tuple(output_names)
+            output_names=tuple([on.name for on in output_names])
         ))
 
     @on_trait_change("mco:finished")
diff --git a/force_bdss/data_sources/base_data_source_model.py b/force_bdss/data_sources/base_data_source_model.py
index 04381be..ef9ccc1 100644
--- a/force_bdss/data_sources/base_data_source_model.py
+++ b/force_bdss/data_sources/base_data_source_model.py
@@ -1,6 +1,7 @@
 from traits.api import ABCHasStrictTraits, Instance, List, Event
 
 from force_bdss.core.input_slot_map import InputSlotInfo
+from force_bdss.core.output_slot_info import OutputSlotInfo
 from force_bdss.local_traits import Identifier
 from .i_data_source_factory import IDataSourceFactory
 
@@ -22,12 +23,13 @@ class BaseDataSourceModel(ABCHasStrictTraits):
     #: slots.
     input_slot_info = List(Instance(InputSlotInfo), visible=False)
 
-    #: Allows to assign names to the output slots, so that they can be
-    #: referenced somewhere else (e.g. the KPICalculators).
+    #: Allows to assign names and KPI status to the output slots, so that
+    #: they can be referenced somewhere else (e.g. another layer's
+    #: DataSources).
     #: If the name is the empty string, it means that the user is not
     #: interested in preserving the information, and should therefore be
     #: discarded and not propagated further.
-    output_slot_names = List(Identifier(), visible=False)
+    output_slot_info = List(Instance(OutputSlotInfo), visible=False)
 
     #: This event claims that a change in the model influences the slots
     #: (either input or output). It must be triggered every time a specific
@@ -44,4 +46,7 @@ class BaseDataSourceModel(ABCHasStrictTraits):
         state["input_slot_info"] = [
             x.__getstate__() for x in self.input_slot_info
         ]
+        state["output_slot_info"] = [
+            x.__getstate__() for x in self.output_slot_info
+        ]
         return state
diff --git a/force_bdss/data_sources/tests/test_base_data_source_model.py b/force_bdss/data_sources/tests/test_base_data_source_model.py
index 76d898d..cf189f9 100644
--- a/force_bdss/data_sources/tests/test_base_data_source_model.py
+++ b/force_bdss/data_sources/tests/test_base_data_source_model.py
@@ -1,6 +1,7 @@
 import unittest
 
 from force_bdss.core.input_slot_map import InputSlotInfo
+from force_bdss.core.output_slot_info import OutputSlotInfo
 
 try:
     import mock
@@ -24,7 +25,7 @@ class TestBaseDataSourceModel(unittest.TestCase):
             {
                 "__traits_version__": "4.6.0",
                 "input_slot_info": [],
-                "output_slot_names": []
+                "output_slot_info": []
             })
 
         model.input_slot_info = [
@@ -35,7 +36,10 @@ class TestBaseDataSourceModel(unittest.TestCase):
                 name="bar"
             )
         ]
-        model.output_slot_names = ["baz", "quux"]
+        model.output_slot_info = [
+            OutputSlotInfo(name="baz"),
+            OutputSlotInfo(name="quux")
+        ]
 
         self.assertEqual(
             model.__getstate__(),
@@ -53,5 +57,16 @@ class TestBaseDataSourceModel(unittest.TestCase):
                         "name": "bar"
                     }
                 ],
-                "output_slot_names": ["baz", "quux"]
+                "output_slot_info": [
+                    {
+                        "__traits_version__": "4.6.0",
+                        "name": "baz",
+                        "kpi": False
+                    },
+                    {
+                        "__traits_version__": "4.6.0",
+                        "name": "quux",
+                        "kpi": False
+                    }
+                ]
             })
diff --git a/force_bdss/io/workflow_reader.py b/force_bdss/io/workflow_reader.py
index 8241196..c2d78ab 100644
--- a/force_bdss/io/workflow_reader.py
+++ b/force_bdss/io/workflow_reader.py
@@ -4,6 +4,7 @@ import logging
 from traits.api import HasStrictTraits, Instance
 
 from force_bdss.core.input_slot_map import InputSlotInfo
+from force_bdss.core.output_slot_info import OutputSlotInfo
 from force_bdss.core.workflow import Workflow
 from ..factory_registry_plugin import IFactoryRegistryPlugin
 
@@ -157,6 +158,10 @@ class WorkflowReader(HasStrictTraits):
                 model_data["input_slot_info"] = self._extract_input_slot_info(
                     model_data["input_slot_info"]
                 )
+                model_data["output_slot_info"] = \
+                    self._extract_output_slot_info(
+                        model_data["output_slot_info"]
+                    )
                 layer.append(ds_factory.create_model(model_data))
             layers.append(layer)
 
@@ -220,6 +225,9 @@ class WorkflowReader(HasStrictTraits):
     def _extract_input_slot_info(self, info):
         return [InputSlotInfo(**d) for d in info]
 
+    def _extract_output_slot_info(self, info):
+        return [OutputSlotInfo(**d) for d in info]
+
     def _extract_notification_listeners(self, wf_data):
         registry = self.factory_registry
         listeners = []
diff --git a/force_bdss/kpi/base_kpi_calculator_model.py b/force_bdss/kpi/base_kpi_calculator_model.py
index bff66d0..bdbde3f 100644
--- a/force_bdss/kpi/base_kpi_calculator_model.py
+++ b/force_bdss/kpi/base_kpi_calculator_model.py
@@ -1,6 +1,6 @@
 from traits.api import ABCHasStrictTraits, Instance, List, Event
 
-from force_bdss.local_traits import Identifier
+from force_bdss.core.output_slot_info import OutputSlotInfo
 from ..core.input_slot_map import InputSlotInfo
 from .i_kpi_calculator_factory import IKPICalculatorFactory
 
@@ -27,7 +27,7 @@ class BaseKPICalculatorModel(ABCHasStrictTraits):
     #: If the name is the empty string, it means that the user is not
     #: interested in preserving the information, and should therefore be
     #: discarded and not propagated further.
-    output_slot_names = List(Identifier(), visible=False)
+    output_slot_info = List(Instance(OutputSlotInfo), visible=False)
 
     #: This event claims that a change in the model influences the slots
     #: (either input or output). It must be triggered every time a specific
@@ -44,4 +44,7 @@ class BaseKPICalculatorModel(ABCHasStrictTraits):
         state["input_slot_info"] = [
             x.__getstate__() for x in self.input_slot_info
             ]
+        state["output_slot_info"] = [
+            x.__getstate__() for x in self.output_slot_info
+        ]
         return state
diff --git a/force_bdss/kpi/tests/test_base_kpi_calculator_model.py b/force_bdss/kpi/tests/test_base_kpi_calculator_model.py
index 6bc7bfb..391b2f1 100644
--- a/force_bdss/kpi/tests/test_base_kpi_calculator_model.py
+++ b/force_bdss/kpi/tests/test_base_kpi_calculator_model.py
@@ -1,6 +1,7 @@
 import unittest
 
 from force_bdss.core.input_slot_map import InputSlotInfo
+from force_bdss.core.output_slot_info import OutputSlotInfo
 from force_bdss.kpi.base_kpi_calculator_factory import BaseKPICalculatorFactory
 from force_bdss.kpi.base_kpi_calculator_model import BaseKPICalculatorModel
 
@@ -23,18 +24,17 @@ class TestBaseKPICalculatorModel(unittest.TestCase):
             {
                 "__traits_version__": "4.6.0",
                 "input_slot_info": [],
-                "output_slot_names": []
+                "output_slot_info": []
             })
 
         model.input_slot_info = [
-            InputSlotInfo(
-                name="foo"
-            ),
-            InputSlotInfo(
-                name="bar"
-            )
+            InputSlotInfo(name="foo"),
+            InputSlotInfo(name="bar")
+        ]
+        model.output_slot_info = [
+            OutputSlotInfo(name="baz"),
+            OutputSlotInfo(name="quux")
         ]
-        model.output_slot_names = ["baz", "quux"]
 
         self.assertEqual(
             model.__getstate__(),
@@ -52,5 +52,16 @@ class TestBaseKPICalculatorModel(unittest.TestCase):
                         "name": "bar"
                     }
                 ],
-                "output_slot_names": ["baz", "quux"]
+                "output_slot_info": [
+                    {
+                        "__traits_version__": "4.6.0",
+                        "name": "baz",
+                        "kpi": False,
+                    },
+                    {
+                        "__traits_version__": "4.6.0",
+                        "name": "quux",
+                        "kpi": False,
+                    }
+                ]
             })
diff --git a/force_bdss/tests/fixtures/test_null.json b/force_bdss/tests/fixtures/test_null.json
index 042261e..7101131 100644
--- a/force_bdss/tests/fixtures/test_null.json
+++ b/force_bdss/tests/fixtures/test_null.json
@@ -15,7 +15,7 @@
           "model_data": {
             "input_slot_info": [
             ],
-            "output_slot_names": [
+            "output_slot_info": [
             ]
           }
         }
@@ -27,7 +27,7 @@
         "model_data": {
           "input_slot_info": [
           ],
-          "output_slot_names": [
+          "output_slot_info": [
           ]
         }
       }
diff --git a/force_bdss/tests/test_core_evaluation_driver.py b/force_bdss/tests/test_core_evaluation_driver.py
index 0e54df5..c0e76a1 100644
--- a/force_bdss/tests/test_core_evaluation_driver.py
+++ b/force_bdss/tests/test_core_evaluation_driver.py
@@ -3,6 +3,7 @@ import unittest
 import testfixtures
 import six
 
+from force_bdss.core.output_slot_info import OutputSlotInfo
 from force_bdss.core.workflow import Workflow
 from force_bdss.tests.probe_classes.factory_registry_plugin import \
     ProbeFactoryRegistryPlugin
@@ -217,7 +218,11 @@ class TestCoreEvaluationDriver(unittest.TestCase):
             InputSlotInfo(name="foo"),
             InputSlotInfo(name="quux")
         ]
-        evaluator_model.output_slot_names = ["one", "", "three"]
+        evaluator_model.output_slot_info = [
+            OutputSlotInfo(name="one"),
+            OutputSlotInfo(name=""),
+            OutputSlotInfo(name="three")
+        ]
 
         res = _compute_layer_results(
             data_values,
@@ -292,7 +297,9 @@ class TestCoreEvaluationDriver(unittest.TestCase):
             InputSlotInfo(name="in1"),
             InputSlotInfo(name="in2")
         ]
-        model.output_slot_names = ["res1"]
+        model.output_slot_info = [
+            OutputSlotInfo(name="res1")
+        ]
         wf.execution_layers[0].append(model)
 
         model = adder_factory.create_model()
@@ -300,7 +307,9 @@ class TestCoreEvaluationDriver(unittest.TestCase):
             InputSlotInfo(name="in3"),
             InputSlotInfo(name="in4")
         ]
-        model.output_slot_names = ["res2"]
+        model.output_slot_info = [
+            OutputSlotInfo(name="res2")
+        ]
         wf.execution_layers[0].append(model)
 
         # layer 1
@@ -309,7 +318,9 @@ class TestCoreEvaluationDriver(unittest.TestCase):
             InputSlotInfo(name="res1"),
             InputSlotInfo(name="res2")
         ]
-        model.output_slot_names = ["res3"]
+        model.output_slot_info = [
+            OutputSlotInfo(name="res3")
+        ]
         wf.execution_layers[1].append(model)
 
         # layer 2
@@ -318,7 +329,9 @@ class TestCoreEvaluationDriver(unittest.TestCase):
             InputSlotInfo(name="res3"),
             InputSlotInfo(name="res1")
         ]
-        model.output_slot_names = ["res4"]
+        model.output_slot_info = [
+            OutputSlotInfo(name="res4")
+        ]
         wf.execution_layers[2].append(model)
 
         # KPI layer
@@ -327,7 +340,9 @@ class TestCoreEvaluationDriver(unittest.TestCase):
             InputSlotInfo(name="res4"),
             InputSlotInfo(name="res2")
         ]
-        model.output_slot_names = ["out1"]
+        model.output_slot_info = [
+            OutputSlotInfo(name="out1")
+        ]
         wf.kpi_calculators.append(model)
 
         kpi_results = execute_workflow(wf, data_values)
-- 
GitLab