diff --git a/capellambse/decl.py b/capellambse/decl.py index 5410bf26b..f4e2bb37d 100644 --- a/capellambse/decl.py +++ b/capellambse/decl.py @@ -240,11 +240,11 @@ def _operate_sync( parent: capellambse.ModelObject, modifications: dict[str, t.Any], ) -> cabc.Generator[_OperatorResult, t.Any, None]: - for attr, value in modifications.items(): - if not isinstance(value, cabc.Iterable): + for attr, v in modifications.items(): + if not isinstance(v, cabc.Iterable): raise TypeError("values below `extend:*:` must be lists") - for obj in value: + for obj in v: try: find_args = obj.pop("find") except KeyError: @@ -271,8 +271,10 @@ def _operate_sync( if candidates: candidate = candidates[0] - for k, v in obj.pop("set", {}).items(): - setattr(candidate, k, v) + if mods := obj.pop("set", None): + yield from _operate_modify(promises, candidate, mods) + if ext := obj.pop("add", None): + yield from _operate_extend(promises, candidate, ext) promise: str | Promise | None = obj.get("promise_id") if promise is not None: if isinstance(promise, str): diff --git a/tests/test_decl.py b/tests/test_decl.py index 555b0d935..db4a24acd 100644 --- a/tests/test_decl.py +++ b/tests/test_decl.py @@ -607,6 +607,82 @@ def test_sync_operation_finds_and_modifies_existing_objects_in_the_list( assert root_function.functions[0].description == new_description + @staticmethod + def test_sync_operation_finds_and_modifies_existing_objects_in_the_list_recurse( + model: capellambse.MelodyModel, + ) -> None: + root_function = model.by_uuid(ROOT_FUNCTION) + subfunc = root_function.functions[0] + new_input = "Water port" + initial_yml = f"""\ + - parent: !uuid {ROOT_FUNCTION} + sync: + functions: + - find: + name: {subfunc.name} + set: + inputs: + - name: {new_input} + """ + + decl.apply(model, io.StringIO(initial_yml)) + assert new_input in subfunc.inputs.by_name + input = "Power port" + yml = f"""\ + - parent: !uuid {ROOT_FUNCTION} + sync: + functions: + - find: + name: {root_function.functions[0].name} + set: + inputs: + - name: {input} + """ + + decl.apply(model, io.StringIO(yml)) + assert ( + input in subfunc.inputs.by_name + and new_input not in subfunc.inputs.by_name + ) + + @staticmethod + def test_sync_operation_finds_and_extends_existing_objects_in_the_list_recurse( + model: capellambse.MelodyModel, + ) -> None: + root_function = model.by_uuid(ROOT_FUNCTION) + subfunc = root_function.functions[0] + new_input = "Water port" + initial_yml = f"""\ + - parent: !uuid {ROOT_FUNCTION} + sync: + functions: + - find: + name: {subfunc.name} + add: + inputs: + - name: {new_input} + """ + + decl.apply(model, io.StringIO(initial_yml)) + assert new_input in subfunc.inputs.by_name + input = "Power port" + yml = f"""\ + - parent: !uuid {ROOT_FUNCTION} + sync: + functions: + - find: + name: {root_function.functions[0].name} + add: + inputs: + - name: {input} + """ + + decl.apply(model, io.StringIO(yml)) + assert ( + input in subfunc.inputs.by_name + and new_input in subfunc.inputs.by_name + ) + @staticmethod def test_sync_operation_creates_a_new_object_if_it_didnt_find_a_match( model: capellambse.MelodyModel,