From 18521a409e637b05ac11b813d272b13a08ea8b31 Mon Sep 17 00:00:00 2001 From: Alexander Schlemmer <alexander@mail-schlemmer.de> Date: Wed, 16 Mar 2022 09:38:47 +0100 Subject: [PATCH] FIX: fixed a problem with recursion in high level api --- src/caosdb/high_level_api.py | 25 +++++++++++++----- unittests/test_high_level_api.py | 45 +++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/src/caosdb/high_level_api.py b/src/caosdb/high_level_api.py index 5d1f1160..697ef2d3 100644 --- a/src/caosdb/high_level_api.py +++ b/src/caosdb/high_level_api.py @@ -674,7 +674,7 @@ class CaosDBPythonEntity(object): return entity - def serialize(self, without_metadata: bool = False): + def serialize(self, without_metadata: bool = False, visited: dict = None): """ Serialize necessary information into a dict. @@ -682,19 +682,27 @@ class CaosDBPythonEntity(object): If True don't set the metadata field in order to increase readability. Not recommended if deserialization is needed. """ + + if visited is None: + visited = dict() + + if self in visited: + return visited[self] + metadata: dict[str, Any] = dict() properties = dict() parents = list() # The full information to be returned: fulldict = dict() + visited[self] = fulldict # Add CaosDB role: fulldict["role"] = standard_type_for_high_level_type(self, True) for parent in self._parents: if isinstance(parent, CaosDBPythonEntity): - parents.append(parent.serialize()) + parents.append(parent.serialize(without_metadata, visited)) elif isinstance(parent, CaosDBPythonUnresolvedParent): parents.append({"name": parent.name, "id": parent.id, "unresolved": True}) @@ -722,7 +730,7 @@ class CaosDBPythonEntity(object): if isinstance(val, CaosDBPythonUnresolvedReference): properties[p] = {"id": val.id, "unresolved": True} elif isinstance(val, CaosDBPythonEntity): - properties[p] = val.serialize(without_metadata) + properties[p] = val.serialize(without_metadata, visited) elif isinstance(val, list): serializedelements = [] for element in val: @@ -732,7 +740,9 @@ class CaosDBPythonEntity(object): elm["unresolved"] = True serializedelements.append(elm) elif isinstance(element, CaosDBPythonEntity): - serializedelements.append(element.serialize(without_metadata)) + serializedelements.append( + element.serialize(without_metadata, + visited)) else: serializedelements.append(element) properties[p] = serializedelements @@ -749,8 +759,11 @@ class CaosDBPythonEntity(object): def __str__(self): return yaml.dump(self.serialize(False)) - def __repr__(self): - return yaml.dump(self.serialize(True)) + # This seemed like a good solution, but makes it difficult to + # compare python objects directly: + # + # def __repr__(self): + # return yaml.dump(self.serialize(True)) class CaosDBPythonRecord(CaosDBPythonEntity): diff --git a/unittests/test_high_level_api.py b/unittests/test_high_level_api.py index f00d3cc0..bca279cd 100644 --- a/unittests/test_high_level_api.py +++ b/unittests/test_high_level_api.py @@ -591,6 +591,45 @@ def test_deserialization(): obj_des = CaosDBPythonEntity.deserialize(serial) assert obj.serialize() == obj_des.serialize() - -def test_recursion(): - pass +@pytest.fixture +def get_record_container(): + record_xml = """ +<Entities> + <Record id="109"> + <Version id="da669fce50554b2835c3826cf717d6a4532f02de" head="true"> + <Predecessor id="68534369c5fd05e5bb1d37801a3dbc1532a8e094"/> + </Version> + <Parent id="103" name="Experiment" description="General type for all experiments in our lab"/> + <Property id="104" name="alpha" description="A fictitious measurement" datatype="DOUBLE" unit="km" importance="FIX" flag="inheritance:FIX">16.0</Property> + <Property id="107" name="date" datatype="DATETIME" importance="FIX" flag="inheritance:FIX">2022-03-16</Property> + <Property id="108" name="identifier" datatype="TEXT" importance="FIX" flag="inheritance:FIX">Demonstration</Property> + <Property id="111" name="sources" description="The elements of this lists are scientific activities that this scientific activity is based on." datatype="LIST<ScientificActivity>" importance="FIX" flag="inheritance:FIX"> + <Value>109</Value> + </Property> + </Record> +</Entities>""" + + c = db.Container.from_xml(record_xml) + return c + +def test_recursion(get_record_container): + r = convert_to_python_object(get_record_container[0]) + r.resolve_references(r, get_record_container) + assert r.id == 109 + assert r.sources[0].id == 109 + assert r.sources[0].sources[0].id == 109 + assert "&id001" in str(r) + assert "*id001" in str(r) + + d = r.serialize(True) + assert r.sources[0] == r.sources[0].sources[0] + +@pytest.mark.xfail +def test_recursion_advanced(get_record_container): + # TODO: + # This currently fails, because resolve is done in a second step + # and therefore a new python object is created for the reference. + r = convert_to_python_object(get_record_container[0]) + r.resolve_references(r, get_record_container) + d = r.serialize(True) + assert r == r.sources[0] -- GitLab