Skip to content
Snippets Groups Projects
Commit 9410d638 authored by Daniel Hornung's avatar Daniel Hornung
Browse files

Merge branch 'f-merge-conflict-error' into 'dev'

F merge conflict error

See merge request !81
parents 31bea860 d575fc54
Branches
Tags
2 merge requests!93Release 0.11.0,!81F merge conflict error
Pipeline #30847 failed
......@@ -9,8 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ###
* `apiutils.EntityMergeConflictError` class for unresesolvable merge conflicts
when merging two entities
### Changed ###
* `apiutils.merge_entities` now raises an `EntityMergeConflictError` in case of
unresolvable merge conflicts.
### Deprecated ###
### Removed ###
......
......@@ -27,12 +27,13 @@
Some simplified functions for generation of records etc.
"""
import logging
import sys
import tempfile
import warnings
from collections.abc import Iterable
from subprocess import call
from typing import Optional, Any, Dict, List
from caosdb.common.datatype import (BOOLEAN, DATETIME, DOUBLE, FILE, INTEGER,
......@@ -40,8 +41,13 @@ from caosdb.common.datatype import (BOOLEAN, DATETIME, DOUBLE, FILE, INTEGER,
from caosdb.common.models import (Container, Entity, File, Property, Query,
Record, RecordType, execute_query,
get_config, SPECIAL_ATTRIBUTES)
from caosdb.exceptions import CaosDBException
import logging
class EntityMergeConflictError(CaosDBException):
"""An error that is raised in case of an unresolvable conflict when merging
two entities.
"""
def new_record(record_type, name=None, description=None,
......@@ -365,14 +371,15 @@ 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, force=False):
"""
Merge entity_b into entity_a such that they have the same parents and properties.
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.
datatype, unit, value, name and description will only be changed in entity_a if they
are None for entity_a and set for entity_b. If there is a corresponding value
for entity_a different from None a RuntimeError will be raised informing of an
unresolvable merge conflict.
datatype, unit, value, name and description will only be changed in entity_a
if they are None for entity_a and set for entity_b. If there is a
corresponding value for entity_a different from None, an
EntityMergeConflictError will be raised to inform about an unresolvable merge
conflict.
The merge operation is done in place.
......@@ -392,13 +399,18 @@ def merge_entities(entity_a: Entity, entity_b: Entity, merge_references_with_emp
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.
If `False`, an EntityMergeConflictError is raised instead. Default is False.
Returns
-------
entity_a : Entity
The initial entity_a after the in-place merge
Raises
------
EntityMergeConflictError
In case of an unresolvable merge conflict.
"""
logging.warning(
......@@ -433,8 +445,8 @@ def merge_entities(entity_a: Entity, entity_b: Entity, merge_references_with_emp
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}) "
raise EntityMergeConflictError(
f"Entity a ({entity_a.id}, {entity_a.name}) "
f"has a Property '{key}' with {attribute}="
f"{diff_r2['properties'][key][attribute]}\n"
f"Entity b ({entity_b.id}, {entity_b.name}) "
......@@ -463,7 +475,9 @@ def merge_entities(entity_a: Entity, entity_b: Entity, merge_references_with_emp
# force overwrite
setattr(entity_a, special_attribute, sa_b)
else:
raise RuntimeError("Merge conflict.")
raise EntityMergeConflictError(
f"Conflict in special attribute {special_attribute}:\n"
f"A: {sa_a}\nB: {sa_b}")
return entity_a
......
......@@ -30,7 +30,8 @@ import pytest
import caosdb as db
import caosdb.apiutils
from caosdb.apiutils import (apply_to_ids, compare_entities, create_id_query,
empty_diff, resolve_reference, merge_entities)
empty_diff, EntityMergeConflictError,
resolve_reference, merge_entities)
from caosdb.common.models import SPECIAL_ATTRIBUTES
......@@ -307,7 +308,7 @@ def test_merge_bug_conflict():
r3 = db.Record()
r3.add_property(name="C", value=4, datatype="INTEGER")
with pytest.raises(RuntimeError) as excinfo:
with pytest.raises(EntityMergeConflictError):
merge_entities(r3, r2)
......@@ -402,15 +403,13 @@ def test_wrong_merge_conflict_reference():
rec_a.add_property(name=title_prop.name, value="Some dataset title")
# this does not compare referenced records, so it will fail
with pytest.raises(RuntimeError) as re:
with pytest.raises(EntityMergeConflictError):
merge_entities(rec_a, rec_b, merge_references_with_empty_diffs=False)
assert "Merge conflict" in str(re.value)
# ... as should this, of course
rec_b.get_property(license_rt.name).value.name = "Another license"
with pytest.raises(RuntimeError) as re:
with pytest.raises(EntityMergeConflictError) as re:
merge_entities(rec_a, rec_b)
assert "Merge conflict" in str(re.value)
def test_empty_diff():
......@@ -484,9 +483,8 @@ def test_force_merge():
recA = db.Record(name="A")
recB = db.Record(name="B")
with pytest.raises(RuntimeError) as re:
with pytest.raises(EntityMergeConflictError):
merge_entities(recA, recB)
assert "Merge conflict" in str(re.value)
merge_entities(recA, recB, force=True)
assert "B" == recA.name
......@@ -499,9 +497,11 @@ def test_force_merge():
recB = db.Record()
recB.description = "something else"
with pytest.raises(RuntimeError) as re:
with pytest.raises(EntityMergeConflictError) as emce:
merge_entities(recA, recB)
assert "Merge conflict" in str(re.value)
assert str(emce.value) == """Conflict in special attribute description:
A: something
B: something else"""
merge_entities(recA, recB, force=True)
assert recA.description == "something else"
......@@ -514,9 +514,8 @@ def test_force_merge():
recB = db.Record()
recB.add_property(name="propA", value="something else")
with pytest.raises(RuntimeError) as re:
with pytest.raises(EntityMergeConflictError):
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"
......@@ -540,9 +539,8 @@ def test_force_merge():
rtB = db.RecordType()
rtB.add_property(name="propA", datatype=db.TEXT)
with pytest.raises(RuntimeError) as re:
with pytest.raises(EntityMergeConflictError):
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
......@@ -555,9 +553,8 @@ def test_force_merge():
recB = db.Record()
recB.add_property(name="propA", value=5, unit="cm")
with pytest.raises(RuntimeError) as re:
with pytest.raises(EntityMergeConflictError):
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
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment