Skip to content
Snippets Groups Projects
Commit f9695ed8 authored by Henrik tom Wörden's avatar Henrik tom Wörden
Browse files

Merge branch 'f-merge-id-with-resolved-entity' into 'dev'

F merge id with resolved entity

See merge request !124
parents 29aecf7d 6f8451cd
Branches
Tags
2 merge requests!130Release v0.14.0,!124F merge id with resolved entity
Pipeline #46610 passed
......@@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ###
* `apiutils.merge_entities` now has a `merge_id_with_resolved_entity` keyword
which allows to identify property values with each other in case that one is
an id and the other is an Entity with this id. Default is ``False``, so no
change to the default behavior.
### Changed ###
### Deprecated ###
......
......@@ -354,7 +354,7 @@ def empty_diff(old_entity: Entity, new_entity: Entity, compare_referenced_record
def merge_entities(entity_a: Entity, entity_b: Entity, merge_references_with_empty_diffs=True,
force=False):
force=False, merge_id_with_resolved_entity: bool = 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
......@@ -380,8 +380,14 @@ def merge_entities(entity_a: Entity, entity_b: Entity, merge_references_with_emp
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`, an EntityMergeConflictError is raised instead. Default is False.
values of `entity_a` are replaced by those of `entity_b` in the
merge. If `False`, an EntityMergeConflictError is raised
instead. Default is False.
merge_id_with_resolved_entity : bool, optional
If true, the values of two reference properties will be considered the
same if one is an integer id and the other is a db.Entity with this
id. I.e., a value 123 is identified with a value ``<Record
id=123/>``. Default is False.
Returns
-------
......@@ -427,6 +433,24 @@ 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_error = True
if merge_id_with_resolved_entity is True and attribute == "value":
# Do a special check for the case of an id value on the
# one hand, and a resolved entity on the other side.
this = entity_a.get_property(key).value
that = entity_b.get_property(key).value
same = False
if isinstance(this, list) and isinstance(that, list):
if len(this) == len(that):
same = all([_same_id_as_resolved_entity(a, b)
for a, b in zip(this, that)])
else:
same = _same_id_as_resolved_entity(this, that)
if same is True:
setattr(entity_a.get_property(key), attribute,
diff_r2["properties"][key][attribute])
raise_error = False
if raise_error is True:
raise EntityMergeConflictError(
f"Entity a ({entity_a.id}, {entity_a.name}) "
f"has a Property '{key}' with {attribute}="
......@@ -586,3 +610,16 @@ def create_flat_list(ent_list: List[Entity], flat: List[Entity]):
flat.append(p.value)
# TODO: move inside if block?
create_flat_list([p.value], flat)
def _same_id_as_resolved_entity(this, that):
"""Checks whether ``this`` and ``that`` either are the same or whether one
is an id and the other is a db.Entity with this id.
"""
if isinstance(this, Entity) and not isinstance(that, Entity):
# this is an Entity with an id, that is not
return this.id is not None and this.id == that
if not isinstance(this, Entity) and isinstance(that, Entity):
return that.id is not None and that.id == this
return this == that
......@@ -588,3 +588,48 @@ def test_merge_missing_list_datatype_82():
with pytest.raises(TypeError) as te:
merge_entities(recA, recB_without_DT, force=True)
assert "Invalid datatype: List valued properties" in str(te.value)
def test_merge_id_with_resolved_entity():
rtname = "TestRT"
ref_id = 123
ref_rec = db.Record(id=ref_id).add_parent(name=rtname)
# recA has the resolved referenced record as value, recB its id. Otherwise,
# they are identical.
recA = db.Record().add_property(name=rtname, value=ref_rec)
recB = db.Record().add_property(name=rtname, value=ref_id)
# default is strict: raise error since values are different
with pytest.raises(EntityMergeConflictError):
merge_entities(recA, recB)
# Overwrite from right to left in both cases
merge_entities(recA, recB, merge_id_with_resolved_entity=True)
assert recA.get_property(rtname).value == ref_id
assert recA.get_property(rtname).value == recB.get_property(rtname).value
recA = db.Record().add_property(name=rtname, value=ref_rec)
merge_entities(recB, recA, merge_id_with_resolved_entity=True)
assert recB.get_property(rtname).value == ref_rec
assert recA.get_property(rtname).value == recB.get_property(rtname).value
# id mismatches
recB = db.Record().add_property(name=rtname, value=ref_id*2)
with pytest.raises(EntityMergeConflictError):
merge_entities(recA, recB, merge_id_with_resolved_entity=True)
other_rec = db.Record(id=None).add_parent(name=rtname)
recA = db.Record().add_property(name=rtname, value=other_rec)
recB = db.Record().add_property(name=rtname, value=ref_id)
with pytest.raises(EntityMergeConflictError):
merge_entities(recA, recB, merge_id_with_resolved_entity=True)
# also works in lists:
recA = db.Record().add_property(
name=rtname, datatype=db.LIST(rtname), value=[ref_rec, ref_id*2])
recB = db.Record().add_property(name=rtname, datatype=db.LIST(rtname), value=[ref_id, ref_id*2])
merge_entities(recA, recB, merge_id_with_resolved_entity=True)
assert recA.get_property(rtname).value == [ref_id, ref_id*2]
assert recA.get_property(rtname).value == recB.get_property(rtname).value
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment