From 2e7f05817d05b07938f3ba585d0634cecc3ad4ed Mon Sep 17 00:00:00 2001
From: florian <f.spreckelsen@inidscale.com>
Date: Wed, 26 Oct 2022 13:59:49 +0200
Subject: [PATCH] ENH: Allow entity comparisons with inspection of referenced
 records

---
 src/caosdb/apiutils.py | 46 +++++++++++++++++++++++++++++++++++++-----
 1 file changed, 41 insertions(+), 5 deletions(-)

diff --git a/src/caosdb/apiutils.py b/src/caosdb/apiutils.py
index b1c74a59..0de24ca7 100644
--- a/src/caosdb/apiutils.py
+++ b/src/caosdb/apiutils.py
@@ -283,9 +283,28 @@ def compare_entities(old_entity: Entity, new_entity: Entity, compare_referenced_
                     matching[0].unit
 
             if (prop.value != matching[0].value):
-                olddiff["properties"][prop.name]["value"] = prop.value
-                newdiff["properties"][prop.name]["value"] = \
-                    matching[0].value
+                # basic comparison of value objects says they are different
+                same_value = False
+                if compare_referenced_records:
+                    # scalar reference
+                    if isinstance(prop.value, Entity) and isinstance(matching[0].value, Entity):
+                        # exlicitely not recursive to prevent infinite recursion
+                        same_value = empty_diff(
+                            prop.value, matching[0].value, compare_referenced_records=False)
+                    # list of references
+                    elif isinstance(prop.value, list) and isinstance(matching[0].value, list):
+                        # all elements in both lists actually are entity objects
+                        if all([isinstance(x, Entity) for x in prop.value]) and all([isinstance(x, Entity) for x in matching[0].value]):
+                            # can't be the same if the lengths are different
+                            if len(prop.value) == len(matching[0].value):
+                                # do a one-by-one comparison; the values are the same, if all diffs are empty
+                                same_value = all(
+                                    [empty_diff(x, y, False) for x, y in zip(prop.value, matching[0].value)])
+
+                if not same_value:
+                    olddiff["properties"][prop.name]["value"] = prop.value
+                    newdiff["properties"][prop.name]["value"] = \
+                        matching[0].value
 
             if (len(newdiff["properties"][prop.name]) == 0
                     and len(olddiff["properties"][prop.name]) == 0):
@@ -338,7 +357,7 @@ def empty_diff(old_entity: Entity, new_entity: Entity, compare_referenced_record
     return True
 
 
-def merge_entities(entity_a: Entity, entity_b: Entity):
+def merge_entities(entity_a: Entity, entity_b: Entity, merge_references_with_empty_diffs=True):
     """
     Merge entity_b into entity_a such that they have the same parents and properties.
 
@@ -352,13 +371,30 @@ def merge_entities(entity_a: Entity, entity_b: Entity):
     Returns entity_a.
 
     WARNING: This function is currently experimental and insufficiently tested. Use with care.
+
+    Parameters
+    ----------
+    entity_a, entity_b : Entity
+       The entities to be merged. entity_b will be merged into entity_a in place
+    merge_references_with_empty_diffs : bool, optional
+       Whether the merge is performed if entity_a and entity_b both reference
+       record(s) that may be different Python objects but have empty diffs. If
+       set to `False` a merge conflict will be raised in this case
+       instead. Default is True.
+
+    Returns
+    -------
+    entity_a : Entity
+       The initial entity_a after the in-place merge
+
     """
 
     logging.warning(
         "This function is currently experimental and insufficiently tested. Use with care.")
 
     # Compare both entities:
-    diff_r1, diff_r2 = compare_entities(entity_a, entity_b)
+    diff_r1, diff_r2 = compare_entities(
+        entity_a, entity_b, compare_referenced_records=merge_references_with_empty_diffs)
 
     # Go through the comparison and try to apply changes to entity_a:
     for key in diff_r2["parents"]:
-- 
GitLab