Skip to content
Snippets Groups Projects
Commit 9900131a authored by Florian Spreckelsen's avatar Florian Spreckelsen
Browse files

Merge branch 'f-force-merge' into 'dev'

F force merge

See merge request !74
parents 58677097 8fb519ee
No related branches found
No related tags found
2 merge requests!79Release 0.10.0,!74F force merge
Pipeline #30201 passed
......@@ -25,6 +25,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
th above `empty_diff` function). Formerly this would have caused a merge
conflict if the referenced record(s) were identical, but stored in different
Python objects.
* `apiutils.merge_entities` now has an optional `force` argument (defaults to
`False`, i.e., the old behavior) which determines whether in case of merge
conflicts errors will be raised or the properties and attributes of entity A
will be overwritten by entity B.
### Deprecated ###
......
......@@ -361,7 +361,7 @@ def empty_diff(old_entity: Entity, new_entity: Entity, compare_referenced_record
return True
def merge_entities(entity_a: Entity, entity_b: Entity, merge_references_with_empty_diffs=True):
def merge_entities(entity_a: Entity, entity_b: Entity, merge_references_with_empty_diffs=True, force=False):
"""
Merge entity_b into entity_a such that they have the same parents and properties.
......@@ -385,6 +385,10 @@ def merge_entities(entity_a: Entity, entity_b: Entity, merge_references_with_emp
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.
force : bool, optional
If True, in case `entity_a` and `entity_b` have the same properties, the
values of `entity_a` are replaced by those of `entity_b` in the merge.
If `False`, a RuntimeError is raised instead. Default is False.
Returns
-------
......@@ -421,6 +425,9 @@ def merge_entities(entity_a: Entity, entity_b: Entity, merge_references_with_emp
if (diff_r1["properties"][key][attribute] is None):
setattr(entity_a.get_property(key), attribute,
diff_r2["properties"][key][attribute])
elif force:
setattr(entity_a.get_property(key), attribute,
diff_r2["properties"][key][attribute])
else:
raise RuntimeError(
f"Merge conflict:\nEntity a ({entity_a.id}, {entity_a.name}) "
......@@ -448,6 +455,9 @@ def merge_entities(entity_a: Entity, entity_b: Entity, merge_references_with_emp
if sa_a != sa_b:
if sa_a is None:
setattr(entity_a, special_attribute, sa_b)
elif force:
# force overwrite
setattr(entity_a, special_attribute, sa_b)
else:
raise RuntimeError("Merge conflict.")
return entity_a
......
# -*- encoding: utf-8 -*-
#
# 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
# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
# Copyright (C) 2022 Florian Spreckelsen <f.spreckelsen@indiscale.com>
# Copyright (C) 2020-2022 IndiScale GmbH <info@indiscale.com>
# 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
......@@ -20,7 +20,6 @@
# 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
#
# Test apiutils
# A. Schlemmer, 02/2018
......@@ -466,3 +465,90 @@ def test_empty_diff():
"RefType"), value=[ref_rec_b, ref_rec_b])
assert not empty_diff(rec_a, rec_b)
assert empty_diff(rec_a, rec_b, compare_referenced_records=True)
def test_force_merge():
"""Test whether a forced merge overwrites existing properties correctly."""
# name overwrite
recA = db.Record(name="A")
recB = db.Record(name="B")
with pytest.raises(RuntimeError) as re:
merge_entities(recA, recB)
assert "Merge conflict" in str(re.value)
merge_entities(recA, recB, force=True)
assert "B" == recA.name
# unchanged
assert "B" == recB.name
# description overwrite
recA = db.Record()
recA.description = "something"
recB = db.Record()
recB.description = "something else"
with pytest.raises(RuntimeError) as re:
merge_entities(recA, recB)
assert "Merge conflict" in str(re.value)
merge_entities(recA, recB, force=True)
assert recA.description == "something else"
# unchanged
assert recB.description == "something else"
# property overwrite
recA = db.Record()
recA.add_property(name="propA", value="something")
recB = db.Record()
recB.add_property(name="propA", value="something else")
with pytest.raises(RuntimeError) as re:
merge_entities(recA, recB)
assert "Merge conflict" in str(re.value)
merge_entities(recA, recB, force=True)
assert recA.get_property("propA").value == "something else"
# unchanged
assert recB.get_property("propA").value == "something else"
# don't remove a property that's not in recB
recA = db.Record()
recA.add_property(name="propA", value="something")
recA.add_property(name="propB", value=5.0)
recB = db.Record()
recB.add_property(name="propA", value="something else")
merge_entities(recA, recB, force=True)
assert recA.get_property("propA").value == "something else"
assert recA.get_property("propB").value == 5.0
# also overwrite datatypes ...
rtA = db.RecordType()
rtA.add_property(name="propA", datatype=db.INTEGER)
rtB = db.RecordType()
rtB.add_property(name="propA", datatype=db.TEXT)
with pytest.raises(RuntimeError) as re:
merge_entities(rtA, rtB)
assert "Merge conflict" in str(re.value)
merge_entities(rtA, rtB, force=True)
assert rtA.get_property("propA").datatype == db.TEXT
# unchanged
assert rtB.get_property("propA").datatype == db.TEXT
# ... and units
recA = db.Record()
recA.add_property(name="propA", value=5, unit="m")
recB = db.Record()
recB.add_property(name="propA", value=5, unit="cm")
with pytest.raises(RuntimeError) as re:
merge_entities(recA, recB)
assert "Merge conflict" in str(re.value)
merge_entities(recA, recB, force=True)
assert recA.get_property("propA").unit == "cm"
# unchanged
assert recB.get_property("propA").unit == "cm"
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment