Skip to main content

Parse and apply Aider-style SEARCH/REPLACE patch blocks to files

Project description

search-replace-py

A standalone Python library for parsing and applying SEARCH/REPLACE patch blocks, extracted from Aider's editblock engine.

Use it to give any LLM the ability to propose and apply precise code changes using the battle-tested editblock format.


How it works

The editblock format is Aider's primary mechanism for LLM-driven code editing. The LLM is prompted to output changes as structured SEARCH/REPLACE blocks:

```python
mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```

This library provides:

  1. render_system_prompt() — returns the rendered system prompt string to instruct the LLM.
  2. get_example_messages() — returns a FewShotExampleMessages named tuple with four plain strings (two user + two assistant turns) to prepend to the conversation history.
  3. apply_diff(llm_response, root) — parses the LLM's response and applies all blocks to disk in one call.

What is included

  • Block parsing (<<<<<<< SEARCH, =======, >>>>>>> REPLACE) with filename discovery and fuzzy filename resolution.
  • Three replacement strategies:
    • exact match
    • leading-whitespace-tolerant match
    • dotdotdot (...) segmented replacement
  • Typed errors (ParseError, ApplyError) for clean error handling in retry loops.

Installation

pip install search-replace-py
# or with uv
uv add search-replace-py

Quick start

from pathlib import Path
from search_replace import render_system_prompt, get_example_messages, apply_diff

# 1. Build the system prompt — plain string, append your own context if needed
system_prompt = render_system_prompt()

# 2. Build the messages list; prepend few-shot examples before the real request
ex = get_example_messages()
messages = [{"role": "system", "content": system_prompt}]
messages += [
    {"role": "user",      "content": ex.first_user_message},
    {"role": "assistant", "content": ex.first_assistant_message},
    {"role": "user",      "content": ex.second_user_message},
    {"role": "assistant", "content": ex.second_assistant_message},
]
messages.append({"role": "user", "content": "Add a docstring to the greet() function in hello.py"})

# 3. Send to your LLM and get a response string
llm_response = "..."

# 4. Parse and apply in one call
apply_diff(llm_response, root=Path("."))

Integration with Pydantic AI

Pydantic AI accepts a string for instructions and a list of ModelMessage objects for message_history.

from pathlib import Path

from pydantic_ai import Agent, ModelRequest, ModelResponse, TextPart, UserPromptPart
from search_replace import render_system_prompt, get_example_messages, apply_diff

ex = get_example_messages()

few_shot = [
    ModelRequest(parts=[UserPromptPart(content=ex.first_user_message)]),
    ModelResponse(parts=[TextPart(content=ex.first_assistant_message)]),
    ModelRequest(parts=[UserPromptPart(content=ex.second_user_message)]),
    ModelResponse(parts=[TextPart(content=ex.second_assistant_message)]),
]

agent = Agent("openai:gpt-5.2", instructions=render_system_prompt())

auth_py = Path("auth.py").read_text()
result = agent.run_sync(
    f"Refactor the login function in auth.py to use bcrypt.\n\nauth.py\n```python\n{auth_py}\n```",
    message_history=few_shot,
)

apply_diff(result.output, root=Path("."))

With dry-run validation before writing

from search_replace import parse_edit_blocks, apply_edits
from search_replace.errors import ApplyError

blocks = parse_edit_blocks(result.output)

# Validate all blocks match before touching disk.
# Without dry_run, blocks that match are written immediately — a later failure
# would leave files partially patched with no rollback.
try:
    apply_edits(blocks.edits, root=Path("."), dry_run=True)
except ApplyError as e:
    # feed the error back to the LLM for a retry
    print(f"Patch would not apply: {e}")
else:
    apply_edits(blocks.edits, root=Path("."))

Public API

from search_replace import (
    # Prompt
    render_system_prompt,
    get_example_messages,    # returns FewShotExampleMessages
    FewShotExampleMessages,  # NamedTuple: first/second_user/assistant_message
    render_prompt,           # render a single template string
    EditBlockFencedPrompts,  # raw class with main_system, system_reminder, example_messages

    # Parse + apply (convenience)
    apply_diff,

    # Parsing
    parse_edit_blocks,
    find_original_update_blocks,
    EditBlock,

    # Applying
    apply_edits,              # pass dry_run=True to validate without writing

    # Errors
    ParseError,
    ApplyError,
    MissingFilenameError,
)

Tests and validation

  • tests/test_parser.py — block parsing, filename resolution, edge cases
  • tests/test_apply.py — replacement strategies, whitespace tolerance, new-file creation
  • tests/test_prompts.pyrender_system_prompt and get_example_messages output
  • tests/test_parity_harness.py — byte-for-byte comparison against Aider's reference output on the real 100K-line chat-history.md fixture
uv run python -m pytest tests/

Credits

The parsing engine, replacement strategies, and prompt templates in this library are derived from Aider, created by Paul Gauthier. Aider is an outstanding AI pair-programming tool — this library simply extracts and packages its editblock mechanism so it can be reused in other applications. All credit for the original design and implementation goes to him.


Extraction notes

  • Extracted from aider/coders/editblock_coder.py and the fenced editblock prompt module.
  • Runtime coupling to Aider's coder/model lifecycle is fully removed.
  • Error message contracts for malformed blocks and failed apply paths are preserved to maintain LLM retry-loop compatibility.
  • replace_closest_edit_distance() remains defined but inactive, preserving the original behaviour of the early return in replace_most_similar_chunk().

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

search_replace_py-0.0.1.tar.gz (866.2 kB view details)

Uploaded Source

Built Distribution

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

search_replace_py-0.0.1-py3-none-any.whl (16.3 kB view details)

Uploaded Python 3

File details

Details for the file search_replace_py-0.0.1.tar.gz.

File metadata

  • Download URL: search_replace_py-0.0.1.tar.gz
  • Upload date:
  • Size: 866.2 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 search_replace_py-0.0.1.tar.gz
Algorithm Hash digest
SHA256 b3b7077653658c39e98cc6693443fa5c9933e11d5f57dbe6188fa5ce427d0d5f
MD5 dfa8f24e751664150e9163c53f4b8879
BLAKE2b-256 24db8e9257a61dc37be4613e8e931678accc4fd19eacfbdfaff90943ce7c55b9

See more details on using hashes here.

File details

Details for the file search_replace_py-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: search_replace_py-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 16.3 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 search_replace_py-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 fe93df509b8f7b69cc20c3a7fe749152484defee7c7b7fa1915dadbd0a27ad45
MD5 b19e0c8b6a1e501fc03a4ed9b03fc4bc
BLAKE2b-256 d0087196eb0344f01f0ad5d93ff4eff1b310593794c5662ee010d06a516a6a14

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