ProvenPin Install

dictdiffer nested patch, revert and ignored keys: working versions (tested 2026-10-16)

Diff nested dicts with dictdiffer, apply and revert the patch, and exclude volatile keys using dotted-path ignore rules.

passed6 passed · 0 failed2026-10-16 21:56 UTC
Status
passed
Last passed
2026-10-16 21:56 UTC
Tests
6 / 6
Language
Python
Runtime
Python 3.13.16
pinned versions2
dictdiffer@0.10.0pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 38972656245

The code

Exactly the files that passed. Your agent can pull them with get_recipe("dictdiffer-patch-revert") (install), or see them on GitHub.

requirements.txt
dictdiffer==0.10.0
pytest==9.1.1
dictpatch.py
from dictdiffer import diff, patch, revert


def make_patch(first, second, ignore=None):
    """Materialize the diff between two nested dicts.

    dictdiffer.diff returns a generator, so list() is required to reuse the
    same patch with both patch() and revert(): an exhausted generator makes
    revert() silently do nothing.
    """
    return list(diff(first, second, ignore=ignore))


def apply_patch(operations, base):
    """Apply a patch to base, returning a new dict (base is untouched)."""
    return patch(operations, base)


def undo_patch(operations, changed):
    """Revert a patch, returning a new dict (changed is untouched)."""
    return revert(operations, changed)


def paths_of(obj, key):
    """Every dot-notated path where key occurs anywhere in a nested dict.

    diff()'s ignore argument matches paths, not key names, so a key that
    recurs at several depths needs each dotted path collected explicitly.
    """
    found = set()

    def walk(node, prefix):
        if isinstance(node, dict):
            for name, value in node.items():
                path = f"{prefix}.{name}" if prefix else name
                if name == key:
                    found.add(path)
                walk(value, path)

    walk(obj, "")
    return found
tests/test_patch.py
from dictdiffer import diff, patch, revert

from dictpatch import apply_patch, make_patch, paths_of, undo_patch

FIRST = {"name": "svc", "replicas": 2, "env": {"DEBUG": "0", "TZ": "UTC"}, "ports": [80]}
SECOND = {"name": "svc", "replicas": 3, "env": {"DEBUG": "1", "TZ": "UTC"}, "ports": [80, 443], "extra": True}


def test_make_patch_lists_nested_changes():
    ops = make_patch(FIRST, SECOND)
    assert ("change", "replicas", (2, 3)) in ops
    assert ("change", "env.DEBUG", ("0", "1")) in ops
    assert ("add", "", [("extra", True)]) in ops


def test_patch_returns_new_dict_and_leaves_base_untouched():
    patched = apply_patch(make_patch(FIRST, SECOND), FIRST)
    assert patched == SECOND
    assert FIRST["replicas"] == 2 and "extra" not in FIRST


def test_patch_then_revert_round_trips():
    ops = make_patch(FIRST, SECOND)
    patched = apply_patch(ops, FIRST)
    assert undo_patch(ops, patched) == FIRST


def test_raw_diff_generator_is_exhausted_by_patch():
    ops = diff(FIRST, SECOND)
    patched = patch(ops, FIRST)
    assert revert(ops, patched) == patched
    assert undo_patch(make_patch(FIRST, SECOND), patched) == FIRST


def test_ignore_matches_dotted_paths_not_key_names():
    patched = apply_patch(make_patch(FIRST, SECOND, ignore={"env.DEBUG"}), FIRST)
    assert patched["env"]["DEBUG"] == "0"
    assert patched["replicas"] == 3


def test_ignore_key_anywhere_via_paths_of():
    before = {"updated_at": 1, "meta": {"updated_at": 2, "size": 1}}
    after = {"updated_at": 10, "meta": {"updated_at": 20, "size": 2}}
    ignore = paths_of(before, "updated_at") | paths_of(after, "updated_at")
    patched = apply_patch(make_patch(before, after, ignore=ignore), before)
    assert patched["meta"]["size"] == 2
    assert patched["updated_at"] == 1 and patched["meta"]["updated_at"] == 2
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.