From 666d6d417fdb32ade1324690c10a37c301c2afc9 Mon Sep 17 00:00:00 2001
From: Alexander Schlemmer <alexander@mail-schlemmer.de>
Date: Fri, 7 Jul 2023 10:24:47 +0200
Subject: [PATCH] FIX: problems with cyclic references in high level api

---
 src/caosdb/high_level_api.py | 30 +++++++++++++++++++++++-------
 1 file changed, 23 insertions(+), 7 deletions(-)

diff --git a/src/caosdb/high_level_api.py b/src/caosdb/high_level_api.py
index 005a20bb..ce65915e 100644
--- a/src/caosdb/high_level_api.py
+++ b/src/caosdb/high_level_api.py
@@ -561,8 +561,8 @@ class CaosDBPythonEntity(object):
         return propval
 
     def resolve_references(self, deep: bool, references: db.Container,
-                           visited: Dict[Union[str, int],
-                                         "CaosDBPythonEntity"] = None):
+                           visited: Optional[Dict[Union[str, int],
+                                                  "CaosDBPythonEntity"]] = None):
         """
         Resolve this entity's references. This affects unresolved properties as well
         as unresolved parents.
@@ -807,7 +807,9 @@ BASE_ATTRIBUTES = (
 
 def _single_convert_to_python_object(robj: CaosDBPythonEntity,
                                      entity: db.Entity,
-                                     references: Optional[db.Container] = None):
+                                     references: Optional[db.Container] = None,
+                                     visited: Optional[Dict[int,
+                                                            "CaosDBPythonEntity"]] = None):
     """
     Convert a db.Entity from the standard API to a (previously created)
     CaosDBPythonEntity from the high level API.
@@ -822,6 +824,17 @@ def _single_convert_to_python_object(robj: CaosDBPythonEntity,
 
     Returns the input object robj.
     """
+    
+    # This parameter is used in the recursion to keep track of already visited
+    # entites (in order to detect cycles).
+    if visited is None:
+        visited = dict()
+
+    if id(entity) in visited:
+        return visited[id(entity)]
+    else:
+        visited[id(entity)] = robj
+        
     for base_attribute in BASE_ATTRIBUTES:
         val = entity.__getattribute__(base_attribute)
         if val is not None:
@@ -924,7 +937,9 @@ def convert_to_entity(python_object):
 
 
 def convert_to_python_object(entity: Union[db.Container, db.Entity],
-                             references: Optional[db.Container] = None):
+                             references: Optional[db.Container] = None,
+                             visited: Optional[Dict[int,
+                                                    "CaosDBPythonEntity"]] = None):
     """
     Convert either a container of CaosDB entities or a single CaosDB entity
     into the high level representation.
@@ -936,15 +951,16 @@ def convert_to_python_object(entity: Union[db.Container, db.Entity],
     """
     if isinstance(entity, db.Container):
         # Create a list of objects:
-        return [convert_to_python_object(i, references) for i in entity]
+        return [convert_to_python_object(i, references, visited) for i in entity]
 
+    # TODO: recursion problems?
     return _single_convert_to_python_object(
-        high_level_type_for_standard_type(entity)(), entity, references)
+        high_level_type_for_standard_type(entity)(), entity, references, visited)
 
 
 def new_high_level_entity(entity: db.RecordType,
                           importance_level: str,
-                          name: str = None):
+                          name: Optional[str] = None):
     """
     Create an new record in high level format based on a record type in standard format.
 
-- 
GitLab