Apply unified diffs and fuzzy search/replace edits in pure Python - the patch engine for code agents. No git, no patch binary.
Project description
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.025 ms | 2.6 ms (~100×) | 7.3 ms (~290×) |
| 2k-line file, 30 edits | 0.17 ms | 2.8 ms (16×) | 7.9 ms (46×) |
| 20k-line file, 200 edits | 1.6 ms | 5.3 ms (3.4×) | 15.5 ms (10×) |
Fuzzy apply_edit on a 400-line file: ~0.01 ms per call.
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file purepatch-0.1.0.tar.gz.
File metadata
- Download URL: purepatch-0.1.0.tar.gz
- Upload date:
- Size: 21.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7128ee4bd72ce3ff191efbc7374976f073499569874d82692e00c96b53169de7
|
|
| MD5 |
17d9f54d55dfe97d4ae26ebb6fb8c762
|
|
| BLAKE2b-256 |
327b2146c2cc196bef28b731f4aac8dfeca52db5ba12a4df84982ff19797644c
|
Provenance
The following attestation bundles were made for purepatch-0.1.0.tar.gz:
Publisher:
release.yml on adam2go/purepatch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
purepatch-0.1.0.tar.gz -
Subject digest:
7128ee4bd72ce3ff191efbc7374976f073499569874d82692e00c96b53169de7 - Sigstore transparency entry: 1797318079
- Sigstore integration time:
-
Permalink:
adam2go/purepatch@fa1df95f31f72350ed870da623bcbbecdcbdc445 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/adam2go
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fa1df95f31f72350ed870da623bcbbecdcbdc445 -
Trigger Event:
release
-
Statement type:
File details
Details for the file purepatch-0.1.0-py3-none-any.whl.
File metadata
- Download URL: purepatch-0.1.0-py3-none-any.whl
- Upload date:
- Size: 17.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ef47c8c8f4162af96743149fb2985806635d925ca926eeab62ea2c6182e27ec5
|
|
| MD5 |
ffcc3d9188047a68e4a433bbcf81da17
|
|
| BLAKE2b-256 |
52ba529de2ff8278136d942b5f5ad135b88fb1611ecb82312defe19d0fab3402
|
Provenance
The following attestation bundles were made for purepatch-0.1.0-py3-none-any.whl:
Publisher:
release.yml on adam2go/purepatch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
purepatch-0.1.0-py3-none-any.whl -
Subject digest:
ef47c8c8f4162af96743149fb2985806635d925ca926eeab62ea2c6182e27ec5 - Sigstore transparency entry: 1797318200
- Sigstore integration time:
-
Permalink:
adam2go/purepatch@fa1df95f31f72350ed870da623bcbbecdcbdc445 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/adam2go
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fa1df95f31f72350ed870da623bcbbecdcbdc445 -
Trigger Event:
release
-
Statement type: