Skip to content

redef

A correctness kit for decorator-scoped AST rewriting in Python.

Working name

redef is a working name; the PyPI name is taken and the package will be renamed before release. The import name in these pages will change with it.

Why would I rewrite a function?

Because sometimes a function's code is the interface, and its values are not enough:

  • a DSL wants the expression the user wrote (p.price > 500 and "iPad" in p.name) to compile it to SQL or to another engine, and tracing objects never see the and at all;
  • an instrumenter must add a check inside the body, at an annotated assignment or a yield, where no wrapper can reach;
  • a test-style tool wants assert to explain its operands when it fails;
  • a runner must ship the body to a worker, a database, another interpreter.

Use cases shows working code for each.

Why a kit, when the pipeline is four lines?

Because the four obvious lines (getsource, dedent, parse, compile) work in demos and fail in the field, and we measured how. Across eighteen execution contexts and five CPython versions: eight contexts have no source at all, nested functions need repairs the demo never hits, and tracebacks point to line 2 without location fixing. The worst case is a re-executed notebook cell that can hand the pipeline valid source for the wrong function, which the naive pipeline rewrites silently. In our audit of the 500 most-downloaded PyPI packages, every deployed implementation of this pipeline hand-rolls a different subset of the repairs, and the stale-source case is caught by none of them. The full argument, with the failure table, is on the Why? page.

redef is the pipeline with seven repairs on by default, typed refusals at every edge the platform imposes, and a verified stale-source detector (zero false positives across 330 measured cells) that catches the notebook case at decoration time.

What it looks like

import ast
from redef import rewrite

class Double(ast.NodeTransformer):
    def visit_Constant(self, node):
        if isinstance(node.value, int) and not isinstance(node.value, bool):
            return ast.copy_location(ast.Constant(node.value * 2), node)
        return node

@rewrite(Double())          # innermost decorator, always
def answer(x):
    return x + 20

answer(1)                    # 41
print(rewrite.expansion(answer))   # the source that actually ran

The decorated file stays ordinary Python: no import hook, no source codec, no global state, and every static checker reads it unchanged. Only decorated functions pay, at tens to hundreds of microseconds each, once, at definition time.

Where to go

  • Why?: the full argument, and why not a wrapper, a hook, tracing, or bytecode.
  • Use cases: four patterns with runnable code.
  • Getting started: install and first rewrite.
  • Support matrix: exactly where recovery works, and the remedies.
  • Reference: the whole API on one page.

Status

Unreleased research software, developed inside the cs-lab research programme that measured the gap it fills. The API is small and stable in shape; the name, packaging, and versioning are not. Its test suite is the research programme's experiment battery.