Skip to content

Rename

rename

In-place symbol rename across a workspace.

:func:rename_symbols renames one or more top-level symbols in place in their defining module (definition + internal usages) and rewrites every cross-file caller that imports the renamed names from that module. Unlike :func:axm_anvil.core.move.move_symbols, no block is copied between files: the symbol keeps its module, only its name changes.

The rewrite reuses :class:axm_anvil._cst.transformers.RenameSymbols for both the defining module and the callers. Applied to a caller, RenameSymbols rewrites the imported alias (from mod import Old -> from mod import New) and every bare-name usage in a single pass; attribute members (obj.Old) are deliberately left untouched.

Caller discovery is pattern-based on the import statement (it scans for from <module> import <name> via :func:_discover_callers). The following cases are intentionally NOT handled and are deferred to Tier 2.1:

  • shadowing — a local binding named like a renamed symbol in a caller is rewritten blindly (no scope analysis on the rename path);
  • aliased imports / alias chains — from mod import Old as O rewrites the Old token but downstream O usages are not reconciled;
  • re-exports / star imports — symbols reached through import * or a re-export module are not discovered.

RenamePlan dataclass

Result of a :func:rename_symbols call.

Carries the rewritten text of the defining module, the names that were actually renamed (old -> new), the caller-file rewrites and any non-fatal warnings (e.g. a requested symbol that was absent in non-strict mode).

Source code in packages/axm-anvil/src/axm_anvil/core/rename.py
Python
@dataclass
class RenamePlan:
    """Result of a :func:`rename_symbols` call.

    Carries the rewritten text of the defining module, the names that were
    actually renamed (``old -> new``), the caller-file rewrites and any
    non-fatal warnings (e.g. a requested symbol that was absent in
    non-strict mode).
    """

    source_text_new: str
    renamed: dict[str, str]
    callers_updated: list[CallerRewrite] = field(default_factory=list)
    warnings: list[str] = field(default_factory=list)
    files_modified: list[str] = field(default_factory=list)

rename_symbols(path, file, mapping, *, dry_run=False, workspace_root=None, strict=False)

Rename top-level symbols in file and rewrite cross-file callers.

Parameters

path: Workspace root used to resolve a relative file and to constrain caller discovery. file: Python file defining the symbols. Relative paths resolve against path. mapping: {old_name: new_name} for the top-level symbols to rename. dry_run: When True, compute the :class:RenamePlan without writing. workspace_root: Explicit workspace root; falls back to the nearest ancestor with a pyproject.toml when None. strict: When True a requested old name absent from the module raises :class:SymbolNotFoundError; when False (default) it is skipped with a warning on :attr:RenamePlan.warnings.

Returns

RenamePlan The rewritten module text, the active renames, caller rewrites and warnings. Caller rewriting is pattern-based on imports; see the module docstring for the uncovered cases.

Source code in packages/axm-anvil/src/axm_anvil/core/rename.py
Python
def rename_symbols(  # noqa: PLR0913
    path: str | Path,
    file: str | Path,
    mapping: dict[str, str],
    *,
    dry_run: bool = False,
    workspace_root: Path | None = None,
    strict: bool = False,
) -> RenamePlan:
    """Rename top-level symbols in ``file`` and rewrite cross-file callers.

    Parameters
    ----------
    path:
        Workspace root used to resolve a relative ``file`` and to constrain
        caller discovery.
    file:
        Python file defining the symbols. Relative paths resolve against
        ``path``.
    mapping:
        ``{old_name: new_name}`` for the top-level symbols to rename.
    dry_run:
        When ``True``, compute the :class:`RenamePlan` without writing.
    workspace_root:
        Explicit workspace root; falls back to the nearest ancestor with a
        ``pyproject.toml`` when ``None``.
    strict:
        When ``True`` a requested ``old`` name absent from the module raises
        :class:`SymbolNotFoundError`; when ``False`` (default) it is skipped
        with a warning on :attr:`RenamePlan.warnings`.

    Returns
    -------
    RenamePlan
        The rewritten module text, the active renames, caller rewrites and
        warnings. Caller rewriting is pattern-based on imports; see the
        module docstring for the uncovered cases.
    """
    root = Path(workspace_root) if workspace_root is not None else Path(path).resolve()
    source_path = Path(file)
    if not source_path.is_absolute():
        source_path = root / source_path

    source_text = source_path.read_text()
    source_tree = cst.parse_module(source_text)

    active, warnings = _resolve_mapping(
        _top_level_names(source_tree), mapping, strict=strict
    )
    if not active:
        return RenamePlan(
            source_text_new=source_text,
            renamed={},
            warnings=warnings,
            files_modified=[],
        )

    source_text_new = _render_renamed(source_text, active)
    caller_texts, caller_rewrites = _rewrite_callers(root, source_path, active)

    files_modified = [str(source_path), *(str(p) for p in caller_texts)]
    plan = RenamePlan(
        source_text_new=source_text_new,
        renamed=active,
        callers_updated=caller_rewrites,
        warnings=warnings,
        files_modified=files_modified,
    )
    if dry_run:
        return plan

    _write_rename(root, source_path, source_text, source_text_new, caller_texts)
    return plan