From e9155a2db486b7159fd496eb77a37bb4afbbf49d Mon Sep 17 00:00:00 2001
From: Timm Fitschen <t.fitschen@indiscale.com>
Date: Thu, 25 Jun 2020 14:56:23 +0200
Subject: [PATCH] EHN: get_property_values

---
 src/caosdb/common/models.py | 125 ++++++++++++++++++++++++++++++++++--
 unittests/test_container.py |  69 ++++++++++++++++++++
 2 files changed, 190 insertions(+), 4 deletions(-)
 create mode 100644 unittests/test_container.py

diff --git a/src/caosdb/common/models.py b/src/caosdb/common/models.py
index 5ab31f48..184be277 100644
--- a/src/caosdb/common/models.py
+++ b/src/caosdb/common/models.py
@@ -368,7 +368,7 @@ class Entity(object):
 
         del copy_kwargs['importance']
         del copy_kwargs['inheritance']
-        new_property = Property(value=value, **copy_kwargs)
+        new_property = Property(**copy_kwargs)
 
         if abstract_property is not None:
             new_property._wrap(property)
@@ -378,6 +378,8 @@ class Entity(object):
             if new_property.datatype is None and isinstance(
                     property, (RecordType, Record, File)):
                 new_property.datatype = property
+        new_property.value = value
+            
         self.properties.append(
             property=new_property, importance=(
                 kwargs['importance'] if 'importance' in kwargs else None), inheritance=(
@@ -586,6 +588,72 @@ class Entity(object):
 
         return None
 
+    def get_property_values(self, *properties):
+        """ Return a list of tuples of the values of the given properties.
+
+        This represents an entity's properties as if it was a row of a table
+        with the given columns.
+
+        If the elements of the properties parameter are tuples, they will return
+        the properties of the referenced entity, if present.
+
+        All tuples of the returned list have the same length as the properties
+        parameter and the ordering of the tuple's values correspond to the
+        order of the parameter as well.
+
+        The tuple contains None for all values that are not available in the
+        entity. That does not necessarily mean, that the values are not stored
+        in the database.
+
+        Parameters
+        ----------
+        properties : str or tuple of str
+            A list of property names, e.g. `"height", "width"` or a list of
+            tuples of properties, e.g. `("window", "height"), ("window",
+            "width")`.
+
+        Raises
+        ------
+        TypeError :
+            If the properties parameter contains anything else than str or
+            tuple of str elements.
+
+        Returns
+        -------
+        row : tuple
+            A row-like representation of the entities properties.
+        """
+        selectors = properties
+        row = tuple()
+        for sel in selectors:
+            val = None
+            if isinstance(sel, str):
+                if hasattr(self, sel.lower()):
+                    val = getattr(self, sel.lower())
+                else:
+                    prop = self.get_property(sel)
+                    if prop is not None:
+                        val = prop.value
+
+            elif isinstance(sel, (tuple, list)) and len(sel) > 0:
+                ref = self
+                while len(sel) > 0 and isinstance(ref, Entity):
+                    prop = ref.get_property(sel[0])
+                    if prop is not None:
+                        ref = prop.value
+                    else:
+                        ref = None
+                    sel = sel[1:]
+                val = ref if len(sel) == 0 else None
+            else:
+                raise TypeError("The elements of the parameter `properties` "
+                                "must contain str or non-empty tuples of str."
+                                " Was {}.".format(sel))
+            if isinstance(val, Entity):
+                val = val.id if val.id is not None else val.name
+            row += (val,)
+        return row
+
     def get_messages(self):
         """Get all messages of this entity.
 
@@ -814,17 +882,26 @@ class Entity(object):
                 entity.add_message(child)
             elif child is None or hasattr(child, "encode"):
                 vals.append(child)
+            elif isinstance(child, Entity):
+                vals.append(child)
             else:
                 raise TypeError(
                     'Child was neither a Property, nor a Parent, nor a Message.\
                     Was ' + str(type(child)))
 
-        # parse VALUE
+        # add VALUE
+        value = None
         if len(vals):
             # The value[s] have been inside a <Value> tag.
-            entity.value = vals
+            value = vals
         elif elem.text is not None and elem.text.strip() != "":
-            entity.value = elem.text.strip()
+            value = elem.text.strip()
+
+        try:
+            entity.value = value
+        except ValueError:
+            # circumvent the parsing.
+            entity.__value = value
 
         return entity
 
@@ -3094,6 +3171,46 @@ class Container(list):
 
         return self
 
+    def get_property_values(self, *properties):
+        """ Return a list of tuples of the values of the given properties.
+
+        I.e. a tabular representation of the container's content.
+
+        If the elements of the properties parameter are tuples, they will return
+        the properties of the referenced entity, if present.
+
+        All tuples of the returned list have the same length as the properties
+        parameter and the ordering of the tuple's values correspond to the
+        order of the parameter as well.
+
+        The tuple contains None for all values that are not available in the
+        entity. That does not necessarily mean, that the values are not stored
+        in the database.
+
+        Parameters
+        ----------
+        properties : list
+            A list of property names, e.g. `["height", "width"]` or a list of
+            tuples of properties, e.g. `[("window", "height"), ("window",
+            "width")]`.
+
+        Raises
+        ------
+        TypeError :
+            If the properties parameter contains anything else than str or
+            tuple of str elements.
+
+        Returns
+        -------
+        table : list of tuples
+            A tabular representation of the container's content.
+        """
+        table = []
+        for e in self:
+            table.append(e.get_property_values(*properties))
+        return table
+
+
 
 def sync_global_acl():
     c = get_connection()
diff --git a/unittests/test_container.py b/unittests/test_container.py
new file mode 100644
index 00000000..f2b73b0a
--- /dev/null
+++ b/unittests/test_container.py
@@ -0,0 +1,69 @@
+
+# -*- encoding: utf-8 -*-
+#
+# ** header v3.0
+# This file is a part of the CaosDB Project.
+#
+# Copyright (C) 2018 Research Group Biomedical Physics,
+# Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+# ** end header
+#
+"""Tests for the Container class."""
+from __future__ import absolute_import
+import caosdb as c
+
+
+def test_get_property_values():
+    rt_house = c.RecordType("House")
+    rt_window = c.RecordType("Window")
+    rt_owner = c.RecordType("Owner")
+    p_height = c.Property("Height", datatype=c.DOUBLE)
+    window = c.Record().add_parent(rt_window)
+    window.id = 1001
+    window.add_property(p_height, 20.5)
+
+    owner = c.Record("The Queen").add_parent(rt_owner)
+
+    house = c.Record("Buckingham Palace")
+    house.add_parent(rt_house)
+    house.add_property(rt_owner, owner)
+    house.add_property(rt_window, window)
+    house.add_property(p_height, 40.2)
+
+    container = c.Container()
+    container.extend([
+        house,
+        owner
+    ])
+
+    table = container.get_property_values("naME",
+                                          "height",
+                                          "window",
+                                          ("window", "heiGHT"),
+                                          "owner")
+    assert len(table) == 2
+    house_row = table[0]
+    assert house_row == (house.name, 40.2, window.id, 20.5, owner.name)
+
+    owner_row = table[1]
+    assert owner_row == (owner.name, None, None, None, None)
+
+    assert container.get_property_values("sdfg") == [(None,), (None,)]
+    assert container.get_property_values("name") == [(house.name,),
+                                                     (owner.name,)]
+
+
-- 
GitLab