Skip to main content

A smart, context-aware patch tool that applies diffs using fuzzy matching, ideal for AI-generated code.

Project description

Mpatch (Python)

PyPI version Python versions Type Hints License: MIT

mpatch is a blazing-fast, context-aware patching library made with Rust. It applies diffs using fuzzy matching, making it the perfect tool for processing patches generated by LLMs (ChatGPT, Claude, Gemini, etc.) that often hallucinate line numbers or drift in context.

Standard patch or git apply fails if the target file has been modified even slightly. mpatch looks for the surrounding lines, dynamically adjusts indentation, and applies the change safely.

🦀 Looking for the CLI tool or Rust library? This is the documentation for the Python bindings. Check out the main mpatch repository and CLI documentation.


💡 The Problem vs. The Solution

When an AI tries to edit a file that has been modified locally since the AI last saw it, standard tools fail.

Original File AI-Generated Patch Standard patch mpatch
def main():
# Updated comment
print("Hello")
 def main():
- print("Hello")
+ print("World")
❌ Failed
Hunk #1 FAILED at 1.
1 out of 1 hunk FAILED
def main():
# Updated comment
print("World")

✨ Features

  • 🧠 Fuzzy Matching: Resilient to stale context, whitespace changes, and minor code drift.
  • 🤖 Format Independent: Automatically recognizes Unified Diffs, Markdown code blocks (```diff), and Conflict Markers (<<<< ==== >>>>).
  • ✨ Smart Indentation: Automatically translates tabs/spaces and aligns injected code to match the target file perfectly.
  • 🛡️ Secure: Built-in protection against directory traversal attacks (e.g., --- a/../../../etc/passwd).
  • ⚡ Blazing Fast & Concurrent: Written in Rust. It heavily optimizes the diffing algorithms and releases the GIL during patching, allowing true multithreading in Python.
  • 🐍 Pythonic & Typed: Supports slice indexing, unaries (~patch to invert), rich error handling, and ships with comprehensive .pyi stubs.

📦 Installation

Install directly from PyPI (pre-compiled wheels are provided for macOS, Linux, and Windows):

pip install mpatch

🚀 Quick Start

The simplest way to use mpatch is the high-level patch_content function. It takes a diff (even one wrapped in Markdown) and applies it to a string.

import mpatch

original_code = """\
def greet():
    print("Hello, old friend")
"""

# Input can be a raw diff, or a diff wrapped in Markdown (common chat output)
diff = """\
Here is the fix:
```diff
--- a/greet.py
+++ b/greet.py
@@ -1,2 +1,2 @@
 def greet():
-    print("Hello, old friend")
+    print("Hello, new world!")
```
"""

# Apply the patch in-memory!
new_code = mpatch.patch_content(diff, original=original_code)

print(new_code)
# Output:
# def greet():
#     print("Hello, new world!")

🛠️ Advanced API Tour

For tool builders and Agent frameworks, mpatch provides a rich Object-Oriented API for parsing, inspecting, and applying patches.

1. Parsing Diffs

You can parse a diff string into a list of Patch objects. mpatch automatically detects the format.

import mpatch

patches = mpatch.parse_auto(diff_string)

for patch in patches:
    print(f"Target file: {patch.file_path}")
    print(f"Number of hunks: {len(patch)}")

    if patch.is_creation:
        print("This patch creates a new file.")

2. Inspecting Hunks

Patches are composed of Hunk objects. You can slice, index, and inspect the exact lines added or removed.

patch = mpatch.parse_auto(diff_string)[0]

for i, hunk in enumerate(patch):
    print(f"Hunk {i+1} changes:")
    print(f"  Removed: {hunk.removed_lines}")
    print(f"  Added:   {hunk.added_lines}")

3. Applying to the Filesystem (Batch Processing)

Apply patches directly to a directory on disk. mpatch handles file reading, writing, creation, and deletion automatically.

from pathlib import Path
import mpatch

diff = """... your diff here ..."""
target_directory = Path("./my_project")

# Apply all patches in a diff directly to the filesystem
success = mpatch.apply_directory(diff, target_directory)

if success:
    print("All files updated successfully!")

For granular control over multiple patches, use apply_patches_to_dir:

patches = mpatch.parse_auto(diff)
batch_result = mpatch.apply_patches_to_dir(patches, target_directory)

if not batch_result.all_succeeded:
    # Inspect hard failures (e.g., IO errors, Permission denied, Path traversal)
    for path, error_msg in batch_result.hard_failures:
        print(f"Critical error on {path}: {error_msg}")

4. Detailed Reporting & Error Handling

When applying a specific Patch, you receive a rich PatchResult or InMemoryResult detailing exactly what happened to every hunk.

patch = mpatch.parse_auto(diff)[0]
result = patch.apply_to_file("./my_project")

if result.report.has_failures:
    print(f"Patch partially failed. {result.report.failure_count} hunks failed.")
    
    for failure in result.report.failures:
        print(f"Hunk {failure.hunk_index} failed due to: {failure.error_type}")
        
        # If it was a fuzzy match that didn't meet the threshold:
        if failure.error_type == "FuzzyMatchBelowThreshold":
            print(f"Best score was {failure.best_score}, needed {failure.threshold}")

5. Dry Runs & Fuzz Factor

Want to see what would happen without modifying your files? Use dry_run=True. You can also tweak the fuzz_factor (0.0 to 1.0, default is 0.7).

result = patch.apply_to_file(
    "./my_project",
    fuzz_factor=0.5, # Lower is more lenient, higher is stricter (0.0 is exact match only)
    dry_run=True     # Generate a diff without writing to disk
)

if result.diff:
    print("Proposed changes:")
    print(result.diff)

6. Reversing Patches

You can reverse a patch (swap additions and deletions) easily using Python's bitwise NOT operator (~) or the invert() method.

patch = mpatch.parse_auto(diff)[0]

# Invert the patch
reversed_patch = ~patch

# Apply the reversed patch to undo changes
reversed_patch.apply_to_file("./my_project")

7. Creating Diffs Programmatically

Need to generate a unified diff between two strings? You can generate the raw string representation or get a Patch object directly.

old_text = "apple\nbanana\npineapple\n"
new_text = "apple\norange\npineapple\n"

# 1. Generate a raw unified diff string
diff_str = mpatch.create_unified_diff("fruits.txt", old_text, new_text)
print(diff_str)
# --- a/fruits.txt
# +++ b/fruits.txt
# @@ -1,3 +1,3 @@
#  apple
# -banana
# +orange
#  pineapple

# 2. Generate a Patch object directly for programmatic manipulation
patch = mpatch.Patch.from_texts("fruits.txt", old_text, new_text)
print(patch[0].removed_lines)  # ['banana']

🚨 Exceptions

mpatch provides specific Python exceptions so you can handle failures gracefully:

  • mpatch.MpatchError: Base exception for all mpatch errors.
  • mpatch.ParseError: Raised when the diff format is malformed or invalid.
  • mpatch.ApplyError: Raised during strict patch application when a hunk fails.
  • mpatch.PathTraversalError: Raised when a patch attempts to modify files outside the target directory (e.g., ../../../etc/passwd).
try:
    mpatch.apply_patch_to_file(malicious_patch, "./src")
except mpatch.PathTraversalError as e:
    print(f"Security blocked path traversal: {e}")

📄 License

This project is licensed under the MIT 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

mpatch-1.6.2.tar.gz (143.1 kB view details)

Uploaded Source

Built Distributions

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

mpatch-1.6.2-cp313-cp313t-win_arm64.whl (317.1 kB view details)

Uploaded CPython 3.13tWindows ARM64

mpatch-1.6.2-cp38-abi3-win_arm64.whl (318.6 kB view details)

Uploaded CPython 3.8+Windows ARM64

File details

Details for the file mpatch-1.6.2.tar.gz.

File metadata

  • Download URL: mpatch-1.6.2.tar.gz
  • Upload date:
  • Size: 143.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mpatch-1.6.2.tar.gz
Algorithm Hash digest
SHA256 b8b5211fdca3f0ef7453dd2e0f4d635d899f0d8daa3aead9f3af39459236661e
MD5 7c899f3212ef5a195d483377f2df8b8e
BLAKE2b-256 7d987d88150f9a80432e7b4a66658e2201b4ba48e5cdd3eea8353d39dc4c9de0

See more details on using hashes here.

Provenance

The following attestation bundles were made for mpatch-1.6.2.tar.gz:

Publisher: pypi.yml on Romelium/mpatch

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

File details

Details for the file mpatch-1.6.2-cp313-cp313t-win_arm64.whl.

File metadata

  • Download URL: mpatch-1.6.2-cp313-cp313t-win_arm64.whl
  • Upload date:
  • Size: 317.1 kB
  • Tags: CPython 3.13t, Windows ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mpatch-1.6.2-cp313-cp313t-win_arm64.whl
Algorithm Hash digest
SHA256 bb5c26b5f6188707d730c9311f0769a4ac16c64e52f669e191b15a2922086283
MD5 0826b3f20aeec518e1ca81e7bd9273c2
BLAKE2b-256 278a1d4b19e58528361e4c144097cd2e26e955e932df61ca937f155c3349d241

See more details on using hashes here.

Provenance

The following attestation bundles were made for mpatch-1.6.2-cp313-cp313t-win_arm64.whl:

Publisher: pypi.yml on Romelium/mpatch

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

File details

Details for the file mpatch-1.6.2-cp38-abi3-win_arm64.whl.

File metadata

  • Download URL: mpatch-1.6.2-cp38-abi3-win_arm64.whl
  • Upload date:
  • Size: 318.6 kB
  • Tags: CPython 3.8+, Windows ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mpatch-1.6.2-cp38-abi3-win_arm64.whl
Algorithm Hash digest
SHA256 2ff3a00e0727978f097fcad0f43f55127aa964e6791896d8abdf322d555a52d4
MD5 61d9b90f93a1478d3fc63ccb37c504f6
BLAKE2b-256 b8d55f7e5db167843e1ee8d338add98ed4627fa4c08addd8f5dd8ff41c579575

See more details on using hashes here.

Provenance

The following attestation bundles were made for mpatch-1.6.2-cp38-abi3-win_arm64.whl:

Publisher: pypi.yml on Romelium/mpatch

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