diff --git a/src/caosdb/common/models.py b/src/caosdb/common/models.py index 4a40e8cf0bbf28cf977419a5b683e2c846366b62..931065ec9a33371a21b59be09513e388b25d1766 100644 --- a/src/caosdb/common/models.py +++ b/src/caosdb/common/models.py @@ -48,10 +48,10 @@ from caosdb.connection.connection import get_connection from caosdb.connection.encode import MultipartParam, multipart_encode from caosdb.exceptions import (AmbiguityException, AuthorizationException, CaosDBException, ConnectionException, - ConsistencyError, - EntityDoesNotExistError, EntityError, - EntityHasNoDatatypeError, TransactionError, - UniqueNamesError, UnqualifiedParentsError, + ConsistencyError, EntityDoesNotExistError, + EntityError, EntityHasNoDatatypeError, + TransactionError, UniqueNamesError, + UnqualifiedParentsError, UnqualifiedPropertiesError, URITooLongException) from lxml import etree @@ -590,18 +590,75 @@ class Entity(object): return None - def get_property_values(self, *properties): - """ Return a list of tuples of the values of the given properties. + def _get_value_for_selector(self, selector): + """return the value described by the selector + + A selector is a list or a tuple of strings describing a path in an + entity tree with self as root. The last selector may be a special one + like unit or name. + + See also get_property_values() + """ + SPECIAL_SELECTORS = ["unit", "value", "description", "id", "name"] + + if not isinstance(selector, (tuple, list)): + selector = [selector] + + val = None + ref = self + + # there are some special selectors which can be applied to the + # final element; if such a special selector exists we split it + # from the list + + if selector[-1].lower() in SPECIAL_SELECTORS: + special_selector = selector[-1] + selector = selector[:-1] + else: + special_selector = None + + # iterating through the entity tree according to the selector + for subselector in selector: + # selector does not match the structure, we cannot get a + # property of non-entity + + if not isinstance(ref, Entity): + return None + + prop = ref.get_property(subselector) + + # selector does not match the structure, we did not get a + # property + if prop is None: + return None + + # if the property is a reference, we are interested in the + # corresponding entities attributes + if isinstance(prop.value, Entity): + ref = prop.value + + # otherwise in the attributes of the property + else: + ref = prop + + # if we saved a special selector before, apply it + if special_selector is None: + return prop.value + else: + return getattr(ref, special_selector.lower()) + + def get_property_values(self, *selectors): + """ Return a tuple with the values described by the given selectors. 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. + If the elements of the selectors parameter are tuples, they will return + the properties of the referenced entity, if present. E.g. ("window", + "height") will return the value of the height property of the + referenced window entity. - 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's values correspond to the order of selectors parameter. 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 @@ -609,51 +666,24 @@ class Entity(object): 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")`. + *selectors : str or tuple of str + Each selector is a list or tuple of property names, e.g. `"height", + "width"`. Returns ------- row : tuple A row-like representation of the entity's properties. """ - properties = properties row = tuple() - for selector in properties: - val = None - if isinstance(selector, (tuple, list)) and len(selector) > 0: - val = None - ref = self - - # the while loop walks through the re - while len(selector) > 1 and isinstance(ref, Entity): - prop = ref.get_property(selector[0]) - if prop is not None and isinstance(prop.value, Entity): - ref = prop.value - else: - # non-entity value - - ref = prop - selector = selector[1:] - if len(selector) == 1 and isinstance(ref, Entity): - if (hasattr(selector[0], "lower") - and hasattr(ref, selector[0].lower())): - val = getattr(ref, selector[0].lower()) - else: - prop = ref.get_property(selector[0]) - if prop is not None: - val = prop.value - else: - if hasattr(self, selector.lower()): - val = getattr(self, selector.lower()) - else: - prop = self.get_property(selector) - if prop is not None: - val = prop.value + + for selector in selectors: + val = self._get_value_for_selector(selector) + if isinstance(val, Entity): val = val.id if val.id is not None else val.name row += (val,) + return row def get_messages(self): @@ -893,6 +923,7 @@ class Entity(object): # add VALUE value = None + if len(vals): # The value[s] have been inside a <Value> tag. value = vals @@ -1052,12 +1083,16 @@ class Entity(object): def _parse_value(datatype, value): if value is None: return value + if datatype is None: return value + if datatype == DOUBLE: return float(value) + if datatype == INTEGER: return int(str(value)) + if datatype == BOOLEAN: if str(value).lower() == "true": return True @@ -1065,14 +1100,17 @@ def _parse_value(datatype, value): return False else: raise ValueError("Boolean value was {}.".format(value)) + if datatype in [DATETIME, TEXT]: if isinstance(value, str): return value # deal with collections + if isinstance(datatype, str): matcher = re.compile(r"^(?P<col>[^<]+)<(?P<dt>[^>]+)>$") m = matcher.match(datatype) + if m: col = m.group("col") dt = m.group("dt") @@ -1093,14 +1131,18 @@ def _parse_value(datatype, value): # This is for a special case, where the xml parser could not differentiate # between single values and lists with one element. As + if hasattr(value, "__len__") and len(value) == 1: return _parse_value(datatype, value[0]) # deal with references + if isinstance(value, Entity): return value + if isinstance(value, str) and "@" in value: # probably this is a versioned reference + return str(value) else: # for unversioned references @@ -1108,6 +1150,7 @@ def _parse_value(datatype, value): return int(value) except ValueError: # reference via name + return str(value) @@ -3173,15 +3216,17 @@ class Container(list): return self - def get_property_values(self, *properties): - """ Return a list of tuples of the values of the given properties. + def get_property_values(self, *selectors): + """ Return a list of tuples with values of the given selectors. 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. + If the elements of the selectors parameter are tuples, they will return + the properties of the referenced entity, if present. E.g. ("window", + "height") will return the value of the height property of the + referenced window entity. - All tuples of the returned list have the same length as the properties + All tuples of the returned list have the same length as the selectors parameter and the ordering of the tuple's values correspond to the order of the parameter as well. @@ -3191,10 +3236,9 @@ class Container(list): 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")`. + *selectors : str or tuple of str + Each selector is a list or tuple of property names, e.g. `"height", + "width"`. Returns ------- @@ -3202,8 +3246,10 @@ class Container(list): A tabular representation of the container's content. """ table = [] + for e in self: - table.append(e.get_property_values(*properties)) + table.append(e.get_property_values(*selectors)) + return table @@ -3629,6 +3675,7 @@ class Info(): http_response = c.retrieve(["Info"]) except ConnectionException as conn_e: print(conn_e) + return xml = etree.fromstring(http_response.read()) @@ -3743,6 +3790,7 @@ def _parse_single_xml_element(elem): return "" elif elem.text is None or elem.text.strip() == "": return None + return str(elem.text.strip()) elif elem.tag.lower() == "querytemplate": return QueryTemplate._from_xml(elem) diff --git a/unittests/test_container.py b/unittests/test_container.py index 195a2351d49954c77f5d3afa6cdbb8d9ea8541dc..b34055372fc83a5608ffcf54423a601001add12b 100644 --- a/unittests/test_container.py +++ b/unittests/test_container.py @@ -24,6 +24,7 @@ # """Tests for the Container class.""" from __future__ import absolute_import + import caosdb as c @@ -64,7 +65,8 @@ def test_get_property_values(): ("window", "heiGHT"), ("window", "heiGHT", "value"), ("window", "heiGHT", "unit"), - "owner") + "owner", + ) assert len(table) == 2 house_row = table[0] assert house_row == (house.name, 40.2, "ft", window.id, None, None, None, 20.5, 20.5, "m", owner.name)