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
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.