Skip to main content

A Python library for runtime function patching using AST manipulation

Project description

awepatch

Awesome Patch - A Python library for runtime function patching using AST manipulation.

Build Status Python Version License

Overview

awepatch is a powerful Python library that allows you to dynamically patch callable objects at runtime by manipulating their Abstract Syntax Tree (AST). Unlike traditional monkey patching, awepatch modifies the actual code object of functions, providing a cleaner and more maintainable approach to runtime code modification.

Features

  • 🔧 Runtime Function Patching: Modify function behavior without changing source code
  • 🎯 AST-Based Manipulation: Clean and precise code modifications using AST
  • 🔄 Automatic Restoration: Context manager support for temporary patches
  • 🎭 Multiple Patch Modes: Insert code before, after, or replace existing statements
  • 📦 Batch Patching: Apply multiple patches to a function in a single call
  • 🧩 Pattern Matching: Use string, regex, or tuple patterns to locate code to patch
  • 🎯 Nested Matching: Target nested code blocks with tuple pattern syntax
  • 🔄 Manual Control: Apply and restore patches manually with CallablePatcher
  • 🔗 Decorator Support: Works with decorated functions, class methods, and static methods
  • Type-Safe: Full type hints support with strict type checking

Installation

pip install awepatch

Or using uv:

uv pip install awepatch

Quick Start

Function Patching

import re

from awepatch import FunctionPatcher


def calculate(x: int, y: int) -> int:
    x = x + 10
    y = y * 2
    result = x + y
    return result


patcher = FunctionPatcher()
patcher.add_patch(
    calculate,
    target="x = x + 10",
    content="print(f'processing: {x=}')",
    mode="before",
)
patcher.add_patch(
    calculate,
    target="y = y * 2",
    content="y = y * 3",
    mode="replace",
)
patcher.add_patch(
    calculate,
    target=re.compile(r"result = x \+ y"),
    content="print(f'result: {result}')",
    mode="after",
)
with patcher:
    print(calculate(5, 10))

# Output:
# processing: x=5
# result: 45
# 45

Module Patching

# foo.py
from dataclasses import dataclass

@dataclass(slots=True)
class User:
    name: str
    age: int

def greet(user: User) -> str:
    if hasattr(user, "gender"):
        return f"Hello, {user.name}! You are {user.age} years old. Your gender is {user.gender}."
    else:
        return f"Hello, {user.name}! You are {user.age} years old."

# example.py
from awepatch.module import ModulePatcher

patcher = ModulePatcher()
patcher.add_patch(
    "foo",
    target=(
        "class User:",
        "age: int",
    ),
    content=""" 
gender: str = "unspecified"
""",
    mode="after",
)
with patcher:
    import foo
    user = foo.User(name="Bob", age=25)
    print(foo.greet(user))

# Output: Hello, Bob! You are 25 years old. Your gender is unspecified.

Nested Pattern Matching

For complex nested structures, you can use tuple patterns or lineno offsets to match nested AST nodes:

from awepatch import FunctionPatcher
from awepatch.utils import Ident


def nested_function(x: int) -> int:
    if x > 0:
        x = x * 2
    x = x * 2
    return x


# Match nested statement inside if block
with FunctionPatcher().add_patch(
    nested_function,
    target=("if x > 0:", "x = x * 2"),
    content="x = x * 3",
    mode="replace",
):
    print(nested_function(5))  # Output: 30


# Or match by line number offset
with FunctionPatcher().add_patch(
    nested_function,
    target=Ident("x = x * 2", lineno="+2"),
    content="x = x * 3",
    mode="replace",
):
    print(nested_function(5))  # Output: 30

Use Cases

  • Testing: Mock function behavior without complex mocking frameworks
  • Debugging: Inject logging or debugging code at runtime
  • Hot-patching: Apply fixes or modifications without restarting applications
  • Experimentation: Test code changes quickly without modifying source files
  • Instrumentation: Add monitoring or profiling code dynamically

Limitations

  • Lambda functions cannot be patched (they lack proper source code information)
  • Functions must have accessible source code via inspect.getsourcelines()
  • Pattern matching must uniquely identify target statement(s) in the function
  • Only single function definitions are supported in the AST
  • Conflicting patches (e.g., combining 'replace' with 'before'/'after' on same target) are not allowed

Development

Setup Development Environment

# Clone the repository
git clone https://github.com/fanck0605/awepatch.git
cd awepatch

# Install development dependencies
uv sync

Running Tests

# Run all tests
pytest

# Run with coverage
pytest --cov=awepatch --cov-report=html

# Run specific test file
pytest tests/test_patch_callable.py

Code Quality

# Format code
ruff format

# Lint code
ruff check

# Fix auto-fixable issues
ruff check --fix

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Author

Chuck Fan - fanck0605@qq.com

Acknowledgments

  • Inspired by the need for cleaner runtime code modification in Python
  • Built with modern Python tooling and best practices

Note: This library modifies function code objects at runtime. Use with caution in production environments and always test thoroughly.

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

awepatch-0.1.1.tar.gz (51.1 kB view details)

Uploaded Source

Built Distribution

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

awepatch-0.1.1-py3-none-any.whl (13.8 kB view details)

Uploaded Python 3

File details

Details for the file awepatch-0.1.1.tar.gz.

File metadata

  • Download URL: awepatch-0.1.1.tar.gz
  • Upload date:
  • Size: 51.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for awepatch-0.1.1.tar.gz
Algorithm Hash digest
SHA256 a4a3e6e770d553add67475b770c9c5aac7191bad2264eeb6853ef9fb37977c57
MD5 5e97e3ca4cbd8c99206a3de6113cf0c8
BLAKE2b-256 c41842d4e3ff9621cab77acf3108175b5121e6a95b845a5fc81f85c8d21bfc7a

See more details on using hashes here.

Provenance

The following attestation bundles were made for awepatch-0.1.1.tar.gz:

Publisher: build.yml on fanck0605/awepatch

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

File details

Details for the file awepatch-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: awepatch-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 13.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for awepatch-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f9529735051e491b5052670c3488d44b0906364a6d04831ba834fa5de0cf4c83
MD5 89b03e16ad1b895673ef69783b18373e
BLAKE2b-256 254284e1f5ba158fddcba7b677c7eea9dde8be941bc9ffb290548d19c31a15ab

See more details on using hashes here.

Provenance

The following attestation bundles were made for awepatch-0.1.1-py3-none-any.whl:

Publisher: build.yml on fanck0605/awepatch

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