Skip to main content

OpenRewrite automated refactoring for Python.

Project description

OpenRewrite Python

OpenRewrite automated refactoring for Python source code. This package provides the recipe framework, the Python Lossless Semantic Tree (LST), and the testing helpers you use to author and test Python recipes.

Installation

pip install openrewrite

How it works

OpenRewrite for Python uses a split JVM/Python architecture. You author and unit-test recipes in pure Python, but running a recipe against a real codebase is orchestrated by the JVM runtime via the Moderne CLI (with Python support configured) over an RPC bridge. There is no standalone, in-process Python parser entry point.

Quick start

The fastest way to author and exercise a recipe is the test harness, which parses a before snippet, runs your recipe, and asserts the result matches after:

from rewrite.test import RecipeSpec, python

def test_renames_a_call():
    spec = RecipeSpec(recipe=RenameFunctionCall(
        old_name="assertEquals",
        new_name="assertEqual",
    ))
    spec.rewrite_run(
        python("assertEquals(a, b)", "assertEqual(a, b)"),
    )

python(before, after) asserts a change; python(before) asserts no change.

Writing a recipe

A recipe is a @dataclass subclassing Recipe that returns a visitor from editor(). Each option must have a default value, or the recipe cannot be discovered or run.

from dataclasses import dataclass, field

from rewrite import ExecutionContext, Recipe, TreeVisitor, option
from rewrite.java import J
from rewrite.java.tree import MethodInvocation
from rewrite.python.visitor import PythonVisitor


@dataclass
class RenameFunctionCall(Recipe):
    """Rename calls to a function from one name to another."""

    old_name: str = field(default="", metadata=option(
        display_name="Old function name",
        description="The name of the function whose calls should be renamed.",
        example="assertEquals",
    ))

    new_name: str = field(default="", metadata=option(
        display_name="New function name",
        description="The name to rename matching calls to.",
        example="assertEqual",
    ))

    @property
    def name(self) -> str:
        return "com.yourorg.RenameFunctionCall"

    @property
    def display_name(self) -> str:
        return "Rename a function call"

    @property
    def description(self) -> str:
        return "Rename calls to a function from one name to another."

    def editor(self) -> TreeVisitor[J, ExecutionContext]:
        old_name = self.old_name
        new_name = self.new_name

        class Visitor(PythonVisitor[ExecutionContext]):
            def visit_method_invocation(self, method: MethodInvocation, p: ExecutionContext) -> J:
                method = super().visit_method_invocation(method, p)
                if method.name.simple_name == old_name:
                    renamed = method.name.replace(_simple_name=new_name)
                    return method.replace(_name=renamed)
                return method

        return Visitor()

Returning None from a visit method removes the node entirely — which is how recipes delete code.

Running recipes with the Moderne CLI

Expose an activate() function so the CLI can discover your recipe:

from rewrite.marketplace import RecipeMarketplace, Python

def activate(marketplace: RecipeMarketplace) -> None:
    marketplace.install(RenameFunctionCall, Python)

Then install and run it against a repository whose Python LSTs you've built, passing each option as a -P parameter:

# From your recipe project directory, install it into the CLI's marketplace:
mod config recipes pip install .

# Build the LSTs for the repository you want to refactor, then run the recipe:
mod build /path/to/your/repo
mod run /path/to/your/repo --recipe=com.yourorg.RenameFunctionCall \
    -P old_name=assertEquals -P new_name=assertEqual

Learn more

License

Moderne Source Available License - see LICENSE.md

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

openrewrite-8.88.0.dev20260717112653.tar.gz (322.1 kB view details)

Uploaded Source

Built Distribution

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

openrewrite-8.88.0.dev20260717112653-py3-none-any.whl (363.2 kB view details)

Uploaded Python 3

File details

Details for the file openrewrite-8.88.0.dev20260717112653.tar.gz.

File metadata

File hashes

Hashes for openrewrite-8.88.0.dev20260717112653.tar.gz
Algorithm Hash digest
SHA256 8b25c949b162a4cd81df4c52b0b61a2e903070b4a5a2b85830e460d813515fc1
MD5 5b480c24dd8dea2b68a4104edbfaad7d
BLAKE2b-256 f74d83924faa4dffa3b57d08c5eca56219d17c9121b4e7174bcf0a2efca9fdde

See more details on using hashes here.

File details

Details for the file openrewrite-8.88.0.dev20260717112653-py3-none-any.whl.

File metadata

File hashes

Hashes for openrewrite-8.88.0.dev20260717112653-py3-none-any.whl
Algorithm Hash digest
SHA256 961f947017dcbc99280c746cc4d15195a05149e9c6f011117dba948b552f83eb
MD5 3103365662dce7abd0c6b8e9dd75a95e
BLAKE2b-256 ca0d856b6bf80b34ad7a7717bb747d45bf8a3deb1d6cdf4b4de1c81a77ca0c1d

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