purepatch
The patch engine for code agents, in pure Python. Apply unified diffs
and fuzzy search/replace edits with no git, no patch binary, no C
extension — in sandboxes, Pyodide/WASM, Lambda, anywhere pip install
works. And because it runs in-process, it applies a patch in ~25 µs where
spawning a binary costs milliseconds.
pip install purepatch
import purepatch
new_text = purepatch.apply(diff_text, old_text) # unified diff -> text
report = purepatch.apply_files(diff_text, root=".") # multi-file patch
new_text = purepatch.apply_edit(text, search, replace) # fuzzy block edit
purepatch --dry-run < change.patch # the familiar CLI, agent-friendly
purepatch -R < change.patch # un-apply
Why
LLMs edit code by emitting unified diffs and SEARCH/REPLACE blocks — and both arrive slightly wrong: line numbers drifted, context rotted, indentation moved, trailing whitespace differs. The existing Python options either only parse diffs (unidiff) or are long abandoned (python-patch, last release 2019). So every agent framework re-implements patching, badly, or shells out to git.
purepatch is that missing engine:
- GNU patch semantics for unified diffs: cumulative offset tracking, bidirectional position search, fuzz degradation — verified against the real thing (below).
- A fuzzy edit ladder for LLM edit blocks: exact match → trailing whitespace tolerance → indentation transplant (the block the model wrote at top level gets re-indented to where it actually lives). Refuses to guess on ambiguity.
- Errors an agent can act on: failed matches report the closest
near-miss (
closest match: line 41, 87% similar) so the model can correct its edit instead of retrying blind. - Git extended headers understood: new/deleted files, renames, quoted
paths,
\ No newline at end of file, CRLF content.
Verified against GNU patch and git apply
Following the pure* series methodology: behavior is checked by differential testing against the reference implementations, run in CI on every commit —
- 500 random clean patches:
purepatch ≡ GNU patch ≡ git apply ≡ expected output, byte for byte; - 200 drift scenarios (the file gained unrelated lines): offset behavior matches GNU patch exactly;
- 200 rotted-context scenarios: fuzz behavior matches GNU patch's output wherever GNU patch succeeds;
- 300 property cases:
apply(diff(a,b), a) == bandapply(diff(a,b), b, reverse=True) == a.
Performance
Per-application latency — how a code agent actually uses a patcher: one
patch at a time. Spawn cost is the binaries' real cost; in-process is
purepatch's real cost. Median of 7, three independent rounds (spread
<10%), outputs verified equal before timing. Reproduce:
python tools/bench.py --verify.
| workload | purepatch (in-process) | GNU patch (spawn) | git apply (spawn) |
|---|---|---|---|
| 200-line file, 5 edits | 0.021 ms | 2.6 ms (~120×) | 7.8 ms (~370×) |
| 2k-line file, 30 edits | 0.17 ms | 2.9 ms (17×) | 8.8 ms (52×) |
| 20k-line file, 200 edits | 1.4 ms | 6.0 ms (4.3×) | 16.2 ms (12×) |
Fuzzy apply_edit on a 400-line file: ~0.01 ms per call.
The slow paths are engineered too, because an agent waits on them: hunk placement uses tiered C-speed anchor scans (a patch drifted by 500 lines in a 20k-line file still applies in ~1.5 ms), and a failed fuzzy edit produces its closest-match diagnostics on a 10k-line file in ~10 ms (two-pass candidate scoring; 21× faster than naively diffing every window).
An agent loop applying hundreds of edits per session pays milliseconds total, not seconds — and needs no git in its sandbox.
API sketch
purepatch.parse(text) -> PatchSet # inspect hunks/files
purepatch.apply(patch, source, reverse=False, max_fuzz=2) -> str
purepatch.apply_files(patch, root=".", strip=None, # strip auto-detected
reverse=False, dry_run=False) -> ApplyReport
purepatch.apply_edit(content, search, replace) -> str
purepatch.find_block(content, search) -> (start, end, strategy)
ApplyReport.ok, per-file actions (patched/created/deleted/renamed/ failed), and per-hunk offset/fuzz are all inspectable — log them and an
agent can explain exactly what happened.
Exceptions: ParseError, HunkApplyError, NoMatchError (with
closest_line / closest_similarity), AmbiguousMatchError (with all
locations).
Limitations (honest ones)
- Binary patches are rejected, not applied.
- File modes are parsed from git headers but not applied to the filesystem (chmod is on the roadmap).
purepatchthe CLI covers the agent subset (-p -d -R --fuzz --dry-run), not every GNU patch flag.- Like GNU patch, fuzzy hunk placement can in principle pick a wrong spot
in pathological inputs;
--fuzz 0disables tolerance entirely.
License
Release files for purepatch 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| purepatch-0.1.1.tar.gz | 23.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| purepatch-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size:42.5 kB
Release files / purepatch-0.1.1.tar.gz
| Download URL | purepatch-0.1.1.tar.gz |
|---|---|
| Size | 23.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6ef21e3adf0479caa58e5938700c186f98a0fe2857c3b4b5a568c58aa8d19048
|
|
BLAKE2b-256 checksum How to use checksums |
1166f3e7df43f9914541b07c1bfeb91448f5a7d8ffa45f727ed81dfb746aac69
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency logRelease files / purepatch-0.1.1-py3-none-any.whl
| Download URL | purepatch-0.1.1-py3-none-any.whl |
|---|---|
| Size | 19.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2ca06da8107bce752357821120d0f9224b0cd312c8229266c0bc43cc03730fcd
|
|
BLAKE2b-256 checksum How to use checksums |
2adff4d984f588b1313231b3d7fda72730e247036386ef31cad9cc645a18bddd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency log