Skip to content

Extract

extract

Extract pipeline: relocate symbols into a brand-new module.

extract_symbols is a thin adapter over :func:move_symbols. The only semantic difference between extract and move is that the target module is created (it does not yet exist). All dependency resolution, caller rewriting and atomic writing are delegated to the move pipeline — this module never duplicates that logic.

extract_symbols(source_path, target_path, symbol_names, dry_run=False, workspace_root=None, shared_helpers='duplicate', shared_helpers_module=None, rename=None, strict=False, insert_after=None, include_helpers=True, side_effect_decorators=None)

Extract symbol_names from source_path into a new module.

extract is the specialisation of :func:move_symbols where target_path is created rather than amended. The moved blocks and their transitive dependencies (imports, local helpers, constants) are copied into the new module, and cross-file callers are rewritten to import from it — all via the move pipeline.

When target_path does not exist it is scaffolded as an empty module so the move pipeline can fill it. A pre-existing target that already defines a requested symbol raises :class:SymbolAlreadyExistsError (no silent overwrite).

With dry_run=True the :class:MovePlan is computed without leaving any file on disk: a target scaffolded for the dry run is removed before returning, so the source layout is byte-identical to before the call.

All other parameters mirror :func:move_symbols and are forwarded verbatim. reexport and check are intentionally not exposed: re-exporting from / cycle-checking against a freshly created module is meaningless for an extract.

Source code in packages/axm-anvil/src/axm_anvil/core/extract.py
Python
def extract_symbols(  # noqa: PLR0913
    source_path: str | Path,
    target_path: str | Path,
    symbol_names: Sequence[str],
    dry_run: bool = False,
    workspace_root: Path | None = None,
    shared_helpers: str = "duplicate",
    shared_helpers_module: str | None = None,
    rename: dict[str, str] | None = None,
    strict: bool = False,
    insert_after: str | None = None,
    include_helpers: bool = True,
    side_effect_decorators: frozenset[str] | None = None,
) -> MovePlan:
    """Extract ``symbol_names`` from ``source_path`` into a *new* module.

    ``extract`` is the specialisation of :func:`move_symbols` where
    ``target_path`` is created rather than amended. The moved blocks and
    their transitive dependencies (imports, local helpers, constants) are
    copied into the new module, and cross-file callers are rewritten to
    import from it — all via the move pipeline.

    When ``target_path`` does not exist it is scaffolded as an empty module
    so the move pipeline can fill it. A pre-existing target that already
    defines a requested symbol raises :class:`SymbolAlreadyExistsError`
    (no silent overwrite).

    With ``dry_run=True`` the :class:`MovePlan` is computed without leaving
    any file on disk: a target scaffolded for the dry run is removed before
    returning, so the source layout is byte-identical to before the call.

    All other parameters mirror :func:`move_symbols` and are forwarded
    verbatim. ``reexport`` and ``check`` are intentionally not exposed:
    re-exporting from / cycle-checking against a freshly created module is
    meaningless for an extract.
    """
    source_path = Path(source_path)
    target_path = Path(target_path)

    _check_collision(target_path, symbol_names)

    created_scaffold = False
    if not target_path.exists():
        target_path.parent.mkdir(parents=True, exist_ok=True)
        target_path.write_text("")
        created_scaffold = True

    try:
        plan = move_symbols(
            source_path,
            target_path,
            symbol_names,
            dry_run=dry_run,
            workspace_root=workspace_root,
            shared_helpers=shared_helpers,
            shared_helpers_module=shared_helpers_module,
            rename=rename,
            strict=strict,
            insert_after=insert_after,
            include_helpers=include_helpers,
            side_effect_decorators=side_effect_decorators,
        )
    finally:
        # A dry run must not leave the scaffold behind: drop the empty file
        # (and a now-empty parent we created) so disk state is unchanged.
        if dry_run and created_scaffold and target_path.exists():
            target_path.unlink()

    return plan