Skip to content

Support "upsert" behavior when applying patches #22

@ohkinozomu

Description

@ohkinozomu

Currently, applying a patch with an operation like {insert} fails when the target key does not exist in the input document. It would be useful if cels could optionally perform an upsert-like behavior — that is, automatically create missing keys when applying certain operations.

Reproduction

from cels import patch_document

patch = """\
list {insert}: c
"""

case1 = """\
foo:
  bar: 1
  baz: 2
list:
- a
- b
"""

case2 = """\
foo:
  bar: 1
  baz: 2
"""

def run_case(name, input_yaml):
    print(f"=== {name} ===")
    output = patch_document(
        input_format="yaml",
        input_text=input_yaml,
        patch_format="yaml",
        patch_text=patch,
        output_format="yaml",
    )
    print(output)

run_case("case1 (list exists)", case1)
run_case("case2 (list missing)", case2)

Result:

=== case1 (list exists) ===
foo:
  bar: 1
  baz: 2
list:
- a
- b
- c

=== case2 (list missing) ===
Traceback (most recent call last):
  File "/Users/ookinozomu/project6/tmp/python/main.py", line 34, in <module>
    run_case("case2 (list missing)", case2)
  File "/Users/ookinozomu/project6/tmp/python/main.py", line 24, in run_case
    output = patch_document(
             ^^^^^^^^^^^^^^^
  File "/Users/ookinozomu/project6/tmp/python/.venv/lib/python3.12/site-packages/cels/services/patch_document.py", line 105, in patch_document
    output_dict = patch_dictionary(
                  ^^^^^^^^^^^^^^^^^
  File "/Users/ookinozomu/project6/tmp/python/.venv/lib/python3.12/site-packages/cels/services/patch_dictionary.py", line 27, in patch_dictionary
    return patch_dictionary_rec(
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ookinozomu/project6/tmp/python/.venv/lib/python3.12/site-packages/cels/services/patch_dictionary.py", line 69, in patch_dictionary_rec
    change.apply(output_dict, key, patch, path, root_input_dict)
  File "/Users/ookinozomu/project6/tmp/python/.venv/lib/python3.12/site-packages/cels/models/change.py", line 122, in apply
    action(
  File "/Users/ookinozomu/project6/tmp/python/.venv/lib/python3.12/site-packages/cels/models/actions/__init__.py", line 17, in wrapped_func
    return action_func(
           ^^^^^^^^^^^^
  File "/Users/ookinozomu/project6/tmp/python/.venv/lib/python3.12/site-packages/cels/models/actions/action_insert.py", line 14, in action_insert
    safe_extend(container[index], None, [change_value])
                ~~~~~~~~~^^^^^^^
KeyError: 'list'

Expected behavior

It would be helpful if cels could create the missing key when applying a patch, for example:

# patch
list {insert}: c

If list does not exist, it would automatically be created as a list and then have the element appended.

Result:

foo:
  bar: 1
  baz: 2
list:
- c

Possible approaches

  1. Make upsert the default behavior
    When an {insert} (or similar) operation targets a missing key, cels would automatically create it with a suitable type (list, mapping, etc.).

  2. Introduce a new {upsert} operator
    A new operator could combine the semantics of {add} and {insert} — creating the key if missing, or inserting into it if it already exists.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions