Apply Codex-style patch blocks (*** Begin Patch ... *** End Patch) to a file
Project description
apply-patch-py
Apply Codex-style patch blocks to a working directory.
This project is heavily inspired by (and effectively a Python port of) the patching mechanism used by Codex. Some tools (including Codex and opencode) already emit this patch format; this implementation aims to be more forgiving and will try harder to apply slightly malformed patches by using whitespace/normalization fallbacks and an additional “anchor line” fallback.
The CLI exists and is useful for quick manual runs, but the primary intent is to use this package as the patch-application backend for agent tools and MCP
apply-patch-py consumes patch text shaped like:
*** Begin Patch
*** Update File: path/to/file.txt
@@
-old line
+new line
*** End Patch
Supported operations:
*** Add File: <path>*** Delete File: <path>*** Update File: <path>(optionally with*** Move to: <new_path>)
LLMs sometimes emit patches that are “almost correct” but fail strict application due to:
- whitespace drift
- minor punctuation differences (Unicode quotes/dashes)
- slightly malformed hunks
This library tries strict matching first (OpenAI models will match almost everytime, strictly), then progressively relaxes how it matches context lines:
- Exact match
- Right-stripped match (
rstrip) - Trimmed match (
strip) - Normalized match (Unicode punctuation normalization + whitespace normalization)
Install
From PyPI:
uv add apply-patch-py
Or run without installing:
uvx apply-patch-py "*** Begin Patch
*** End Patch"
PydanticAI tool Example
You can try it from examples folder:
# 1) Clone and run the example
git clone https://github.com/marcius-llmus/apply-patch-py
cd apply-patch-py
# Provide your LLM key (example: OpenAI)
export OPENAI_API_KEY="sk-proj-..."
# Run the example with uv
uv run examples/pydantic_example/pydantic_example.py
Then you can start asking edits to files inside example_repo folder.
For example:
Request> Create a new file named "notes/hello.txt" with the content:
hello from the patch tool
or
Request> Remove content "xyz" from existing_file.txt 123
or
Request> Edit file xyz, remove the middle block.
Direct usage
You can also call the library directly:
import asyncio
from apply_patch_py import apply_patch
patch = """\
*** Begin Patch
*** Add File: hello.txt
+hello
*** End Patch
"""
async def main() -> None:
affected = await apply_patch(patch)
assert affected.success
asyncio.run(main())
CLI usage
Apply a patch provided as a command-line argument:
apply-patch-py "*** Begin Patch
*** Add File: hello.txt
+hello\n
*** End Patch"
Apply a patch from stdin:
cat patch.txt | apply-patch-py
The CLI prints a summary of affected files:
Success. Updated the following files:
A hello.txt
M existing.txt
D obsolete.txt
Add a file
*** Begin Patch
*** Add File: nested/new.txt
+created
*** End Patch
Delete a file
*** Begin Patch
*** Delete File: obsolete.txt
*** End Patch
Update a file (single hunk)
*** Begin Patch
*** Update File: modify.txt
@@
-line2
+changed
*** End Patch
Update a file (multiple hunks)
*** Begin Patch
*** Update File: multi.txt
@@
-line2
+changed2
@@
-line4
+changed4
*** End Patch
Rename/move a file while updating
*** Begin Patch
*** Update File: old/name.txt
*** Move to: renamed/dir/name.txt
@@
-old content
+new content
*** End Patch
Run tests
uv run pytest
Integration tests (LLM providers)
This repo also contains integration tests that validate patch generation via real LLM providers. They are skipped by default unless explicitly selected:
uv run pytest -m integration
See tests/integration/ for provider configuration.
Notes
- The patch format and workflow are directly inspired by OpenAI Codex diff patching.
- Some other tools (e.g. opencode) emit the same format.
- This project is essentially a port from their rust patcher with a few changes to improve success rates on imperfect LLM output.
License
MIT. See LICENSE.
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
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 apply_patch_py-0.4.1.tar.gz.
File metadata
- Download URL: apply_patch_py-0.4.1.tar.gz
- Upload date:
- Size: 171.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8962aa19d12a2b48a92175f7c42e9ce90c54e3d9bcc39210549487d549c36d20
|
|
| MD5 |
50226f5bb50b013aea61818ae4cb9172
|
|
| BLAKE2b-256 |
3cad5b1cc95dc6e005b52c8f9f53fd02a34569bbce80ad3ca140e2f19f900a9c
|
File details
Details for the file apply_patch_py-0.4.1-py3-none-any.whl.
File metadata
- Download URL: apply_patch_py-0.4.1-py3-none-any.whl
- Upload date:
- Size: 15.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c66abf766051a527a6e3242d7ca157a070fbaf1c1c22701a78f347da3c0b5838
|
|
| MD5 |
1c33679bce484f6d0cc6cc4825332336
|
|
| BLAKE2b-256 |
9646bdb2ec889f639b5129c6424fc6b2fea7e3d3c22243e3bb7537a43d048df2
|