Skip to content

Patchdiff 🔍

Based on rfc6902 this library provides a simple API to generate bi-directional diffs between composite Python data structures composed out of lists, sets, tuples and dicts. The diffs are JSON-patch compliant, and can optionally be serialized to JSON format. Patchdiff has no dependencies and works on Python 3.9 and up.

A single call to diff gives you the patches in both directions:

from patchdiff import apply, diff

input = {"a": [5, 7, 9, {"a", "b", "c"}], "b": 6}
output = {"a": [5, 2, 9, {"b", "c"}], "b": 6, "c": 7}

ops, reverse_ops = diff(input, output)

assert apply(input, ops) == output
assert apply(output, reverse_ops) == input

Having both directions makes it easy to build undo/redo, to synchronize state between processes (send patches over the wire instead of whole documents), or to keep a log of exactly what changed.

As an alternative to diffing, patchdiff can also record patches while mutations are being made, using a proxy mechanism like Immer. See produce:

from patchdiff import produce

base = {"count": 0, "items": [1, 2, 3]}

def recipe(draft):
    draft["count"] = 5
    draft["items"].append(4)

result, patches, reverse_patches = produce(base, recipe)

assert base == {"count": 0, "items": [1, 2, 3]}  # base is untouched
assert result == {"count": 5, "items": [1, 2, 3, 4]}

Where to start

  • observ: reactive state management for Python. Patchdiff's produce(..., in_place=True) is designed to work with its reactive proxies.
  • rfc6902: the TypeScript library that patchdiff's diffing approach is based on.