Patch Clients¶
JsonPatchX can validate patch documents client-side before you send them.
Patch Client Flow¶
A Python patch client usually has three steps:
from httpx import Client
from jsonpatchx import JsonPatch
# Step 1: Build
full_restore = [
{"op": "copy", "from": "/stats/hp", "path": "/health"},
{"op": "replace", "path": "/status", "value": "healthy"}
]
# Step 2: Validate
patch = JsonPatch(full_restore)
# Step 3: Send
with Client(base_url="https://api.example.com") as client:
response = client.patch(
"/pokemon/pikachu",
content=patch.to_string(),
headers={"content-type": "application/json-patch+json"},
)
response.raise_for_status()
Building Patch Documents¶
There are 3 different ways to build patch documents.
From list[dict]¶
from jsonpatchx import JsonPatch
full_restore = [
{"op": "copy", "from": "/stats/hp", "path": "/health"},
{"op": "replace", "path": "/status", "value": "healthy"}
]
patch = JsonPatch(full_restore)
From JSON Text¶
from pathlib import Path
from jsonpatchx import JsonPatch
patch = JsonPatch.from_string(Path("full_restore_patch.json").read_text())
From Operation Models¶
from jsonpatchx import JsonPatch, CopyOp, ReplaceOp
full_restore = [
CopyOp(from_="/stats/hp", path="/health"),
ReplaceOp(path="/status", value="healthy")
]
patch = JsonPatch(full_restore)
Note:
fromis a reserved keyword in Python, soCopyOpandMoveOpusefrom_instead. This is only necessary when you instantiate them directly. This is implemented withField(alias="from").
When you build patches from operation models, validation errors can be caught eagerly:
from jsonpatchx import JsonPatch, CopyOp, ReplaceOp
full_restore = [
CopyOp(from_="/stats/hp", path="health"), # ERROR: invalid pointer!
ReplaceOp(path="/status", value="healthy")
]
Share Custom Operation Models¶
If an API service defines custom patch operations with JsonPatchX, it can publish those operation schemas together with the registry alias that matches the PATCH endpoint contract.
For example:
# some_service/patching.py
from .widget_ops import ClampOp, IncrementOp, MultiplyOp
type WidgetPatchRegistry = MultiplyOp | IncrementOp | ClampOp
That gives clients one import surface that mirrors the contract the API service actually accepts. Keep that surface stable. It can live in:
- a public module inside the service package
- a separate shared package published alongside the service
- an internal shared package when both sides live in the same organization
With that shared contract, a Python client can build a JsonPatch, validate it
locally, then send it:
from httpx import Client
from jsonpatchx import JsonPatch
from some_service.patching import (
ClampOp,
IncrementOp,
MultiplyOp,
WidgetPatchRegistry,
)
patch = JsonPatch(
[
MultiplyOp(path="/foo/bar", scalar=2),
IncrementOp(path="/foo/bar", amount=20),
ClampOp(path="/foo/bar", max=100),
],
registry=WidgetPatchRegistry,
)
with Client(base_url="https://api.example.com") as client:
response = client.patch(
"/widgets/123",
content=patch.to_string(),
headers={"content-type": "application/json-patch+json"},
)
response.raise_for_status()
For why this pattern is especially useful for agents, see Agentic Patching.