Skip to main content

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.py — render_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().

Metadata

Release files for search-replace-py 0.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for search-replace-py 0.0.2
File Size Uploaded Provenance
search_replace_py-0.0.2.tar.gz 866.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for search-replace-py 0.0.2
File Interpreter ABI Platform
search_replace_py-0.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 882.6 kB

Release files / search_replace_py-0.0.2.tar.gz

Download URL search_replace_py-0.0.2.tar.gz
Size 866.3 kB
Tags Source
SHA-256 checksum
How to use checksums
8faad502bf45a920df08ac8f5f9a4fff6d456de9efc48a456ca36d9cb83b868d
BLAKE2b-256 checksum
How to use checksums
46faad54b795d34c052bea240362bc8a6f076945c5523fa4f380bbb2662ee9a0
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release files / search_replace_py-0.0.2-py3-none-any.whl

Download URL search_replace_py-0.0.2-py3-none-any.whl
Size 16.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4c00b80832d527f50ad1a56ac9b29339d169ed87f3cda44e916686c9c034479d
BLAKE2b-256 checksum
How to use checksums
d154eebfbdf4968db8dfa1445d82efd3aef812b87153f7731f7ea2fdb10869f3
Upload date
Uploaded using Trusted Publishing?
What is 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}

Security for search-replace-py 0.0.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.0.2 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page