Repository: nismod/smif

Issue:
'DataHandle' object has no attribute 'read_coefficients'
Adaptors expect data_handle to have method `read_coefficients`.  The store has read and write methods for coefficients, but this isn't exposed at the data_handle level.

```python
Traceback (most recent call last):
  File "/vagrant/smif/src/smif/controller/scheduler.py", line 178, in add
    self._run(job_graph, job_graph_id)
  File "/vagrant/smif/src/smif/controller/scheduler.py", line 249, in _run
    model.simulate(data_handle)
  File "/vagrant/smif/src/smif/convert/adaptor.py", line 27, in simulate
    coefficients = self.get_coefficients(data, from_spec, to_spec)
  File "/vagrant/smif/src/smif/convert/adaptor.py", line 46, in get_coefficients
    coefficients = data_handle.read_coefficients(from_spec, to_spec)
AttributeError: 'DataHandle' object has no attribute 'read_coefficients'
```

Discussion:

Patch:
diff --git a/src/smif/data_layer/data_handle.py b/src/smif/data_layer/data_handle.py
index 40401cc8..def4b820 100644
--- a/src/smif/data_layer/data_handle.py
+++ b/src/smif/data_layer/data_handle.py
@@ -555,6 +555,14 @@ class DataHandle(object):
             decision_iteration
         )
 
+    def read_coefficients(self, source_spec, destination_spec):
+        data = self._store.read_coefficients(source_spec, destination_spec)
+        return data
+
+    def write_coefficients(self, source_spec, destination_spec, data):
+        data = self._store.write_coefficients(source_spec, destination_spec, data)
+        return data
+
 
 class ResultsHandle(object):
     """Results access for decision modules
diff --git a/src/smif/data_layer/memory_interface.py b/src/smif/data_layer/memory_interface.py
index 14d52cbe..ff7616c9 100644
--- a/src/smif/data_layer/memory_interface.py
+++ b/src/smif/data_layer/memory_interface.py
@@ -363,10 +363,15 @@ class MemoryDataStore(DataStore):
 
     # region Conversion coefficients
     def read_coefficients(self, source_spec, destination_spec):
-        return self._coefficients[(source_spec, destination_spec)]
+        spec = (source_spec.name, destination_spec.name)
+        try:
+            return self._coefficients[(source_spec.name, destination_spec.name)]
+        except KeyError:
+            msg = "Could not find coefficients for spec pair {}.{}"
+            raise SmifDataNotFoundError(msg.format(spec[0], spec[1]))
 
     def write_coefficients(self, source_spec, destination_spec, data):
-        self._coefficients[(source_spec, destination_spec)] = data
+        self._coefficients[(source_spec.name, destination_spec.name)] = data
     # endregion
 
     # region Results


Test patch:
diff --git a/tests/data_layer/test_data_handle.py b/tests/data_layer/test_data_handle.py
index cb858427..a6556afe 100644
--- a/tests/data_layer/test_data_handle.py
+++ b/tests/data_layer/test_data_handle.py
@@ -9,7 +9,7 @@ from smif.data_layer import DataHandle
 from smif.data_layer.data_array import DataArray
 from smif.data_layer.data_handle import ResultsHandle
 from smif.exception import (SmifDataError, SmifDataMismatchError,
-                            SmifTimestepResolutionError)
+                            SmifDataNotFoundError, SmifTimestepResolutionError)
 from smif.metadata import Spec
 from smif.model import SectorModel, SosModel
 
@@ -88,7 +88,7 @@ def mock_store(sample_dimensions, get_sector_model, empty_store):
 
     # convertor model
     # - test input matches test output from test_source
-    # - test ouput matches population input to energy demand
+    # - test output matches population input to energy demand
     store.write_model({
         'name': 'test_convertor',
         'description': '',
@@ -716,6 +716,49 @@ class TestDataHandleGetParameters:
         assert actual == expected
 
 
+class TestDataHandleCoefficients:
+    """Tests the interface for reading and writing coefficients
+    """
+    def test_read_coefficient_raises(self, mock_store, mock_model):
+        """
+        """
+        store = mock_store
+        source_spec = mock_model.outputs['gas_demand']
+        sink_spec = mock_model.outputs['gas_demand']
+
+        dh = DataHandle(store, 1, 2010, [2010], mock_model)
+        with raises(SmifDataNotFoundError):
+            dh.read_coefficients(source_spec, sink_spec)
+
+    def test_write_coefficients(self, mock_store, mock_model):
+        """
+        """
+
+        store = mock_store
+        source_spec = mock_model.outputs['gas_demand']
+        sink_spec = mock_model.outputs['gas_demand']
+        data = np.zeros(4)
+
+        dh = DataHandle(store, 1, 2010, [2010], mock_model)
+        dh.write_coefficients(source_spec, sink_spec, data)
+
+    def test_read_coefficient_(self, mock_store, mock_model):
+        """
+        """
+        store = mock_store
+        source_spec = mock_model.outputs['gas_demand']
+        sink_spec = mock_model.outputs['gas_demand']
+
+        dh = DataHandle(store, 1, 2010, [2010], mock_model)
+
+        data = np.zeros(4)
+        dh.write_coefficients(source_spec, sink_spec, data)
+
+        actual = dh.read_coefficients(source_spec, sink_spec)
+
+        np.testing.assert_equal(actual, np.array([0, 0, 0, 0]))
+
+
 class TestResultsHandle:
     """Get results from any model
     """
diff --git a/tests/data_layer/test_data_store.py b/tests/data_layer/test_data_store.py
index 212e4680..5ce339fe 100644
--- a/tests/data_layer/test_data_store.py
+++ b/tests/data_layer/test_data_store.py
@@ -146,8 +146,13 @@ class TestCoefficients():
     """
     def test_read_write_coefficients(self, conversion_source_spec, conversion_sink_spec,
                                      handler):
+
+        with raises(SmifDataNotFoundError):
+            handler.read_coefficients(conversion_source_spec, conversion_sink_spec)
+
         expected = np.array([[2]])
         handler.write_coefficients(conversion_source_spec, conversion_sink_spec, expected)
+
         actual = handler.read_coefficients(conversion_source_spec, conversion_sink_spec)
         np.testing.assert_equal(actual, expected)