Skip to content

API Reference

The public API is small: two ways to obtain patches (diff and produce), two ways to apply them (apply and iapply), and serialization helpers. All of these can be imported directly from patchdiff. The Pointer class lives in patchdiff.pointer.

Diffing

patchdiff.diff.diff

diff(input, output, ptr=None)

Compute the difference between two objects as JSON patch operations.

Recursively compares input and output and returns operations in both directions. Dicts, lists and sets are compared structurally; any other value (scalars, but also tuples and frozensets) is treated as atomic and replaced wholesale when it differs.

Parameters:

Name Type Description Default
input Diffable

The source object.

required
output Diffable

The target object.

required
ptr Pointer | None

Pointer prefix for the emitted operations; used internally during recursion. Leave as None to diff from the root.

None

Returns:

Type Description
list[Operation]

A tuple (ops, reverse_ops): applying ops to input yields

list[Operation]

output, and applying reverse_ops to output yields input

tuple[list[Operation], list[Operation]]

again. Each operation is a dict with an "op" key ("add",

tuple[list[Operation], list[Operation]]

"remove" or "replace"), a "path" key holding a

tuple[list[Operation], list[Operation]]

Pointer, and a "value" key for

tuple[list[Operation], list[Operation]]

add/replace operations.

Applying patches

patchdiff.apply.apply

apply(obj, patches)

Apply a list of patches to a deep copy of an object.

Parameters:

Name Type Description Default
obj Diffable

The object to copy and patch; it is left unchanged.

required
patches list[Operation]

Operations as returned by diff or produce.

required

Returns:

Type Description
Diffable

The patched copy.

patchdiff.apply.iapply

iapply(obj, patches)

Apply a list of patches to an object, in place.

Patch values are deep-copied as they are written, so mutating the patched object afterwards never writes through into the patch list (and vice versa).

Parameters:

Name Type Description Default
obj Diffable

The object to mutate.

required
patches list[Operation]

Operations as returned by diff or produce.

required

Returns:

Type Description
Diffable

The same object, mutated.

Proxy-based patch generation

patchdiff.produce.produce

produce(base, recipe, in_place=False)

Produce a new state by applying mutations, tracking patches along the way.

This is an alternative to the diff() function that uses proxy objects to track mutations in real-time instead of comparing before/after snapshots.

Parameters:

Name Type Description Default
base Any

The base object to mutate (dict, list, or set)

required
recipe Callable[[Any], None]

A function that receives a proxy-wrapped draft and mutates it

required
in_place bool

If True, mutate the original object directly (useful for reactive objects like observ). If False (default), operate on a deep copy and leave the original unchanged.

False

Returns:

Type Description
Any

A tuple of (result, patches, reverse_patches) where:

list[Operation]
  • result: The mutated object (same as base if in_place=True)
list[Operation]
  • patches: List of patches representing the mutations
tuple[Any, list[Operation], list[Operation]]
  • reverse_patches: List of patches to reverse the mutations
Example

base = {"count": 0, "items": []} def increment(draft): ... draft["count"] += 1 ... draft["items"].append("new") result, patches, reverse = produce(base, increment) print(result) {"count": 1, "items": ["new"]} print(patches) [{"op": "replace", "path": "/count", "value": 1}, {"op": "add", "path": "/items/-", "value": "new"}]

Example with in_place=True for reactive objects: >>> from observ import reactive >>> state = reactive({"count": 0}) >>> result, patches, reverse = produce(state, lambda d: d.setitem("count", 5), in_place=True) >>> # state["count"] is now 5, and watchers were triggered

Serialization

patchdiff.serialize.to_json

to_json(ops, **kwargs)

Serialize a list of operations to a JSON patch (RFC 6902) string.

Pointer paths are rendered as JSON pointer strings; any keyword arguments (like indent) are forwarded to json.dumps.

patchdiff.serialize.to_str_paths

to_str_paths(ops)

Return a copy of the operations with each path rendered as a string.

The Pointer objects in the "path" fields are replaced by their escaped JSON pointer (RFC 6901) string form; the operations themselves are not mutated.

Pointers

patchdiff.pointer.Pointer

A JSON pointer (RFC 6901): a path into a nested structure.

A pointer is an immutable sequence of reference tokens; append returns a new pointer rather than mutating. str() renders the escaped JSON pointer string form and from_str parses one back. Unlike strict RFC 6901, tokens can be arbitrary hashable values (not just strings), so pointers can address set members and integer list indices directly.

from_str staticmethod

from_str(path)

Parse an escaped JSON pointer string (e.g. "/a/0/b") into a Pointer.

All tokens are parsed as strings; numeric list indices become string tokens, which evaluate and iapply convert back as needed.

evaluate

evaluate(obj)

Resolve the pointer against an object.

Returns:

Type Description
Diffable | None

A tuple (parent, key, value): the container holding the

Hashable

addressed leaf, the leaf's key within that container, and

Any

the leaf's current value — None when the leaf does not

tuple[Diffable | None, Hashable, Any]

exist yet (the target of an "add", or a list append via

tuple[Diffable | None, Hashable, Any]

the "-" token).

append

append(token)

Return a new Pointer with token appended; self is unchanged.

patchdiff.pointer.escape

escape(token)

Encode a reference token for a JSON pointer string (~~0, /~1).

patchdiff.pointer.unescape

unescape(token)

Decode a JSON pointer reference token (~1/, ~0~).