rewrite.py


Overview

The [rewrite.py](/projects/286/67427) module is a core component of the **pytest** testing framework responsible for **assertion rewriting**. Its primary goal is to transform Python `assert` statements in test modules into enhanced forms that provide rich, detailed error messages upon assertion failures. This transformation is achieved by hooking into Python’s import system, parsing test source code into an Abstract Syntax Tree (AST), rewriting `assert` statements to collect intermediate sub-expression values, and compiling the rewritten code for execution.

This mechanism allows pytest to display informative, introspective assertion failure explanations without requiring any changes from the test author. It significantly improves the debugging experience by showing the values of sub-expressions involved in failed assertions.


Key Components

1. AssertionRewritingHook (Import Hook)

2. AssertionRewriter (AST Transformer)

3. Helper Functions


Detailed Explanation of Classes and Functions


Class: AssertionRewritingHook

**Purpose:** Acts as a PEP 302/451 import hook that intercepts module imports and rewrites `assert` statements in test modules to enhance error reporting.

**Key Methods:**

**Usage Example:**

hook = AssertionRewritingHook(config)
hook.mark_rewrite("my_test_module")
spec = hook.find_spec("my_test_module")
if spec:
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)

Class: AssertionRewriter (inherits ast.NodeVisitor)

**Purpose:** Transforms `assert` statements in an AST to enhanced versions that provide detailed failure explanations with intermediate values.

**Constructor:**

def __init__(self, module_path: str | None, config: Config | None, source: bytes) -> None:

**Main Method:**

def run(self, mod: ast.Module) -> None:

**Key Method:**

def visit_Assert(self, assert_: ast.Assert) -> list[ast.stmt]:

**Other Notable Visit Methods:**

**Helper Methods:**

**Usage Example:**

rewriter = AssertionRewriter(module_path="test_sample.py", config=pytest_config, source=source_bytes)
rewriter.run(ast_tree)
# The ast_tree now contains rewritten assert statements

Function: rewrite_asserts

def rewrite_asserts(
    mod: ast.Module,
    source: bytes,
    module_path: str | None = None,
    config: Config | None = None,
) -> None:

Function: _rewrite_test

def _rewrite_test(fn: Path, config: Config) -> tuple[os.stat_result, types.CodeType]:

Function: _read_pyc

def _read_pyc(
    source: Path, pyc: Path, trace: Callable[[str], None] = lambda x: None
) -> types.CodeType | None:

Function: _write_pyc

def _write_pyc(
    state: AssertionState,
    co: types.CodeType,
    source_stat: os.stat_result,
    pyc: Path,
) -> bool:

Utility Functions


Important Implementation Details and Algorithms


Interaction with Other Parts of the System


Visual Diagram: Class Structure and Main Workflow

classDiagram
    class AssertionRewritingHook {
        -config: Config
        -session: Session | None
        -_rewritten_names: dict[str, Path]
        -_must_rewrite: set[str]
        -_writing_pyc: bool
        +find_spec(name, path, target)
        +exec_module(module)
        +mark_rewrite(*names)
    }

    class AssertionRewriter {
        -module_path: str | None
        -config: Config | None
        -source: bytes
        -scope: tuple
        -variables_overwrite: defaultdict
        +run(mod)
        +visit_Assert(assert_)
        +visit_Name(name)
        +visit_BoolOp(boolop)
        +visit_BinOp(binop)
        +visit_Compare(comp)
        +variable()
        +assign(expr)
        +explanation_param(expr)
        +push_format_context()
        +pop_format_context(expl_expr)
    }

    AssertionRewritingHook --> AssertionRewriter : uses
    AssertionRewritingHook ..> Config : depends on
    AssertionRewriter ..> ast.NodeVisitor : inherits

    %% Helper functions are not shown here for brevity

Summary

The [rewrite.py](/projects/286/67427) module is a sophisticated system for enhancing assertion error reporting in pytest by rewriting `assert` statements at import time. It leverages Python's import system hooks and AST transformations to provide detailed introspection into assertion failures, improving developer productivity during test debugging. Through careful integration with pytest's configuration, caching mechanisms, and assertion utilities, it delivers a seamless and performant solution for assertion rewriting.


Appendix: Usage in pytest

When pytest runs tests, it installs the `AssertionRewritingHook` to intercept test module imports. This hook rewrites test modules on the fly so that when an assertion fails, pytest can display comprehensive, user-friendly messages showing the exact values and sub-expressions involved.

Test authors write normal Python `assert` statements as usual; the rewriting and enhanced messages happen transparently.


End of Documentation for rewrite.py