Applying Patches
Patchdiff can apply a list of operations to an object in two ways:
applyfirst makes a deep copy, patches that, and returns it. The input is left untouched.iapplypatches the object in place and returns the same object. This is faster (no copy) and is what you want for objects that must keep their identity, such as observ reactive proxies.
from patchdiff import apply, diff, iapply
input = {"a": [5, 7, 9], "b": 6}
output = {"a": [5, 2, 9], "b": 6, "c": 7}
ops, reverse_ops = diff(input, output)
patched = apply(input, ops)
assert patched == output
assert input == {"a": [5, 7, 9], "b": 6} # unchanged
result = iapply(input, ops)
assert result is input # same object, mutated
assert input == output
Order matters
Operations are applied sequentially, and paths refer to the state of the object at that point in the sequence, just like in RFC 6902. List indices in particular shift as adds and removes are applied. Both lists that diff returns are already ordered accordingly, so apply them as-is. Don't reorder or cherry-pick individual operations.
Undo and redo
Since every diff comes with its reverse, undo/redo is just a pair of stacks of patch lists:
from patchdiff import diff, iapply
state = {"count": 0}
undo_stack = []
redo_stack = []
def update(new_state):
ops, reverse_ops = diff(state, new_state)
iapply(state, ops)
undo_stack.append((ops, reverse_ops))
redo_stack.clear()
def undo():
ops, reverse_ops = undo_stack.pop()
iapply(state, reverse_ops)
redo_stack.append((ops, reverse_ops))
def redo():
ops, reverse_ops = redo_stack.pop()
iapply(state, ops)
undo_stack.append((ops, reverse_ops))
update({"count": 1})
update({"count": 2})
undo()
assert state == {"count": 1}
redo()
assert state == {"count": 2}
Patch values are copied on write
When a patch is applied, its "value" is deep-copied before being written into the target. The patched object and the patch list never share mutable state, so you can keep patches around (on an undo stack for example) and freely mutate the object afterwards: