Skip to main content

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:

  1. Exact match
  2. Right-stripped match (rstrip)
  3. Trimmed match (strip)
  4. 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

apply_patch_py-0.4.1.tar.gz (171.1 kB view details)

Uploaded Source

Built Distribution

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

apply_patch_py-0.4.1-py3-none-any.whl (15.6 kB view details)

Uploaded Python 3

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

Hashes for apply_patch_py-0.4.1.tar.gz
Algorithm Hash digest
SHA256 8962aa19d12a2b48a92175f7c42e9ce90c54e3d9bcc39210549487d549c36d20
MD5 50226f5bb50b013aea61818ae4cb9172
BLAKE2b-256 3cad5b1cc95dc6e005b52c8f9f53fd02a34569bbce80ad3ca140e2f19f900a9c

See more details on using hashes here.

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

Hashes for apply_patch_py-0.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c66abf766051a527a6e3242d7ca157a070fbaf1c1c22701a78f347da3c0b5838
MD5 1c33679bce484f6d0cc6cc4825332336
BLAKE2b-256 9646bdb2ec889f639b5129c6424fc6b2fea7e3d3c22243e3bb7537a43d048df2

See more details on using hashes here.

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