From 22498ceaaf49024b78df05087072f9cd336ca32f Mon Sep 17 00:00:00 2001
From: lukas leufen <l.leufen@fz-juelich.de>
Date: Fri, 22 Nov 2019 16:42:51 +0100
Subject: [PATCH] first implementation of data store with 2 different designs
 but same functionality, not decided yet which representation is more suitable

---
 src/datastore.py       |  92 +++++++++++++++++++++++++++++++++++
 test/test_datastore.py | 107 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 199 insertions(+)
 create mode 100644 src/datastore.py
 create mode 100644 test/test_datastore.py

diff --git a/src/datastore.py b/src/datastore.py
new file mode 100644
index 00000000..4c71e67f
--- /dev/null
+++ b/src/datastore.py
@@ -0,0 +1,92 @@
+__author__ = 'Lukas Leufen'
+__date__ = '2019-11-22'
+
+
+from typing import Any
+from abc import ABC
+
+
+class NameNotFoundInDataStore(Exception):
+
+    pass
+
+
+class NameNotFoundInScope(Exception):
+
+    pass
+
+
+class AbstractDataStore(ABC):
+
+    """
+    Data store for all settings for the experiment workflow to save experiment parameters for the proceeding modules
+    and predefine parameters loaded during the experiment setup phase. The data store is hierarchically structured, so
+    that global settings can be overwritten by local adjustments.
+    """
+    def __init__(self):
+        # empty initialise the data-store variables
+        self._store = {}
+
+    def put(self, name: str, obj: Any, scope: str) -> None:
+        pass
+
+    def get(self, name: str, scope: str) -> None:
+        pass
+
+
+class DataStoreByVariable(AbstractDataStore):
+
+    def put(self, name, obj, scope):
+        # open new variable related store with `name` as key if not existing
+        if name not in self._store.keys():
+            self._store[name] = {}
+        self._store[name][scope] = obj
+
+    def get(self, name, scope, depth=None):
+        if depth is None:
+            depth = scope.count(".")
+        if depth >= 0:
+            try:
+                return self._store[name][scope]
+            except KeyError:
+                return self.get(name, scope.rsplit(".", maxsplit=depth)[0], depth-1)
+        else:
+            if name in self._store.keys():
+                raise NameNotFoundInScope(f"Couldn't find {name} in scope {scope}. {name} is only defined in "
+                                          f"{self.search_name(name)}")
+            else:
+                raise NameNotFoundInDataStore(f"Couldn't find {name} in data store")
+
+    def search_name(self, name):
+        return sorted(self._store[name])
+
+
+class DataStoreByScope(AbstractDataStore):
+
+    def put(self, name, obj, scope):
+        if scope not in self._store.keys():
+            self._store[scope] = {}
+        self._store[scope][name] = obj
+
+    def get(self, name, scope, depth=None):
+        if depth is None:
+            depth = scope.count(".")
+        if depth >= 0:
+            try:
+                return self._store[scope][name]
+            except KeyError:
+                return self.get(name, scope.rsplit(".", maxsplit=1)[0], depth-1)
+        else:
+            occurrences = self.search_name(name)
+            if len(occurrences) == 0:
+                raise NameNotFoundInDataStore(f"Couldn't find {name} in data store")
+            else:
+                raise NameNotFoundInScope(f"Couldn't find {name} in scope {scope}. {name} is only defined in "
+                                          f"{occurrences}")
+
+    def search_name(self, name):
+        keys = []
+        for (key, val) in self._store.items():
+            if name in val.keys():
+                keys.append(key)
+        return sorted(keys)
diff --git a/test/test_datastore.py b/test/test_datastore.py
new file mode 100644
index 00000000..1c41b18c
--- /dev/null
+++ b/test/test_datastore.py
@@ -0,0 +1,107 @@
+__author__ = 'Lukas Leufen'
+__date__ = '2019-11-22'
+
+
+from src.datastore import AbstractDataStore, DataStoreByVariable, DataStoreByScope
+from src.datastore import NameNotFoundInDataStore, NameNotFoundInScope
+import pytest
+
+
+class TestAbstractDataStore:
+
+    @pytest.fixture
+    def ds(self):
+        return AbstractDataStore()
+
+    def test_init(self, ds):
+        assert ds._store == {}
+
+
+class TestDataStoreByVariable:
+
+    @pytest.fixture
+    def ds(self):
+        return DataStoreByVariable()
+
+    def test_put(self, ds):
+        ds.put("number", 3, "general.subscope")
+        assert ds._store["number"]["general.subscope"] == 3
+
+    def test_get(self, ds):
+        ds.put("number", 3, "general.subscope")
+        assert ds.get("number", "general.subscope") == 3
+
+    def test_get_with_sub_scope(self, ds):
+        ds.put("number", 3, "general")
+        ds.put("number", 10, "general.subscope")
+        assert ds.get("number", "general.subscope") == 10
+        assert ds.get("number", "general") == 3
+
+    def test_get_with_not_existing_sub_scope(self, ds):
+        ds.put("number", 3, "general")
+        ds.put("number2", 10, "general.subscope")
+        ds.put("number2", 1, "general")
+        assert ds.get("number", "general.subscope") == 3
+
+    def test_raise_not_in_data_store(self, ds):
+        ds.put("number", 22, "general")
+        with pytest.raises(NameNotFoundInDataStore) as e:
+            ds.get("number3", "general")
+        assert "Couldn't find number3 in data store" in e.value.args[0]
+
+    def test_search(self, ds):
+        ds.put("number", 22, "general")
+        ds.put("number", 22, "general2")
+        ds.put("number", 22, "general.sub")
+        assert ds.search_name("number") == ["general", "general.sub", "general2"]
+
+    def test_raise_not_in_scope(self, ds):
+        ds.put("number", 11, "general.sub")
+        with pytest.raises(NameNotFoundInScope) as e:
+            ds.get("number", "general")
+        assert "Couldn't find number in scope general. number is only defined in ['general.sub']" in e.value.args[0]
+
+
+class TestDataStoreByScope:
+
+    @pytest.fixture
+    def ds(self):
+        return DataStoreByScope()
+
+    def test_put_with_scope(self, ds):
+        ds.put("number", 3, "general.subscope")
+        assert ds._store["general.subscope"]["number"] == 3
+
+    def test_get(self, ds):
+        ds.put("number", 3, "general.subscope")
+        assert ds.get("number", "general.subscope") == 3
+
+    def test_get_with_sub_scope(self, ds):
+        ds.put("number", 3, "general")
+        ds.put("number", 10, "general.subscope")
+        assert ds.get("number", "general.subscope") == 10
+        assert ds.get("number", "general") == 3
+
+    def test_get_with_not_existing_sub_scope(self, ds):
+        ds.put("number", 3, "general")
+        ds.put("number2", 10, "general.subscope")
+        ds.put("number2", 1, "general")
+        assert ds.get("number", "general.subscope") == 3
+
+    def test_raise_not_in_data_store(self, ds):
+        ds.put("number", 22, "general")
+        with pytest.raises(NameNotFoundInDataStore) as e:
+            ds.get("number3", "general")
+        assert "Couldn't find number3 in data store" in e.value.args[0]
+
+    def test_search(self, ds):
+        ds.put("number", 22, "general")
+        ds.put("number", 22, "general2")
+        ds.put("number", 22, "general.sub")
+        assert ds.search_name("number") == ["general", "general.sub", "general2"]
+
+    def test_raise_not_in_scope(self, ds):
+        ds.put("number", 11, "general.sub")
+        with pytest.raises(NameNotFoundInScope) as e:
+            ds.get("number", "general")
+        assert "Couldn't find number in scope general. number is only defined in ['general.sub']" in e.value.args[0]
-- 
GitLab