Skip to main content

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

CI PyPI Python License: MIT

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) == b and apply(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).
  • purepatch the 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 0 disables tolerance entirely.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

purepatch-0.1.0.tar.gz (21.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

purepatch-0.1.0-py3-none-any.whl (17.1 kB view details)

Uploaded Python 3

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

Hashes for purepatch-0.1.0.tar.gz
Algorithm Hash digest
SHA256 7128ee4bd72ce3ff191efbc7374976f073499569874d82692e00c96b53169de7
MD5 17d9f54d55dfe97d4ae26ebb6fb8c762
BLAKE2b-256 327b2146c2cc196bef28b731f4aac8dfeca52db5ba12a4df84982ff19797644c

See more details on using hashes here.

Provenance

The following attestation bundles were made for purepatch-0.1.0.tar.gz:

Publisher: release.yml on adam2go/purepatch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for purepatch-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ef47c8c8f4162af96743149fb2985806635d925ca926eeab62ea2c6182e27ec5
MD5 ffcc3d9188047a68e4a433bbcf81da17
BLAKE2b-256 52ba529de2ff8278136d942b5f5ad135b88fb1611ecb82312defe19d0fab3402

See more details on using hashes here.

Provenance

The following attestation bundles were made for purepatch-0.1.0-py3-none-any.whl:

Publisher: release.yml on adam2go/purepatch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page