To refactor a legacy Python module with Atlas, you enumerate its callers before you touch it. Atlas maps the module's public surface with the lsp tool's documentSymbol operation, runs findReferences on each exported symbol to list every callsite across your package, pins current behavior by running pytest through the bash tool to record a green baseline, and only then restructures with apply_patch. apply_patch anchors each hunk on context lines and old_lines and fails with "Failed to find context" if the file has drifted, so a stale edit against src/legacy/parser.py cannot land quietly. You re-run pytest after each hunk, not once at the end, and Atlas tracks the remaining callsites in a todowrite list so a half-migrated module is never mistaken for a finished one.
How do you refactor a legacy Python module without breaking its callers?
The risk in any Python refactor is silent breakage at a callsite you did not know about. Atlas closes that gap with 2 lsp operations: documentSymbol on the module to map its public surface, then findReferences on each exported symbol, so every import of src/legacy/parser.py is enumerated before 1 line changes.
Python makes it easy to hide a caller. A function can be imported in a conftest.py fixture, referenced through a Django settings string, or re-exported from a package __init__.py that nothing greps for. Atlas attacks that with the symbol graph rather than text search: documentSymbol enumerates what the legacy module actually exports, and findReferences on each of those symbols enumerates who imports it. The result is a list of concrete files, not a guess. Atlas also indexes code by AST declarations using tree-sitter, not blind line windows, so when it does fall back to codebase_search the candidates it returns are real Python declarations rather than arbitrary chunks of a 900-line module.
Why does Atlas run pytest before it changes any Python code?
Atlas runs the existing pytest suite through the bash tool before the refactor starts, recording a green baseline. Without that baseline, a failure after the 1st apply_patch hunk is ambiguous: it could be the refactor, or it could be a test that was already red before Atlas touched src/legacy/.
Pinning behavior first is the whole discipline of a behavior-preserving Python refactor. Atlas runs pytest with the bash tool, captures the pass count, and treats that as the contract the refactor must not violate. Every bash invocation is permission-gated against allow, ask, and deny rules before it runs, so the test command you approve is the test command that executes. From that point the loop is mechanical: apply a hunk, re-run pytest, compare against the baseline. If the module has no tests worth trusting, the baseline is the signal to write some first, because a refactor without a green pytest run to defend is just a rewrite.
What is apply_patch and why does Atlas use it instead of edit for Python refactors?
apply_patch is the Atlas tool for structural, multi-hunk Python changes. Each hunk carries 2 things, context lines and old_lines, and apply_patch seeks that context in the target file. If src/legacy/parser.py has drifted since Atlas read it, apply_patch fails with "Failed to find context" rather than writing to the wrong place.
A legacy Python module refactor is rarely one edit. Splitting a 900-line module into src/parsing/tokens.py and src/parsing/nodes.py means moving class bodies, rewriting imports at the top of half a dozen files, and updating the package __init__.py. apply_patch expresses that as a set of anchored hunks and refuses to apply against a drifted file, which is the safety property that matters when a human or another process edited the file in between. Atlas also computes a unified diff for every file edit and surfaces it for approval before writing, and it snapshots file changes as git patches, so a patch that lands badly can be diffed and rolled back rather than untangled by hand.
How does Atlas track a partially migrated Python module?
Atlas records the remaining callsites in a todowrite list. In a Python refactor that touches 12 import sites across src/ and tests/, the todowrite list is what stops a module that is 8 callers migrated from looking, in a fresh context window, exactly like a module that is finished.
A partially migrated Python module is the most dangerous state a refactor can be in: the new structure exists, the old imports still resolve, pytest is green because the compatibility shim is still there, and nothing signals that the job is half done. Atlas turns the findReferences output into a todowrite list with one entry per remaining callsite, so the count is explicit and survives across turns. Combined with re-running pytest after each hunk rather than once at the end, the todowrite list means the refactor either completes or visibly does not, and Atlas fans out work to subagents when the sweep is wide enough to justify parallel background sessions.
Which Python files and commands are involved in an Atlas refactor?
Atlas expects a Python repo with a pyproject.toml or requirements.txt. Atlas reads your package layout, your virtualenv, and your installed dependencies, then works against the real toolchain in 2026: pytest for tests, uv for dependency management, and ruff format for formatting the modules it restructures.
The concrete artifacts of an Atlas Python refactor are ordinary Python artifacts. pyproject.toml declares the package and the pytest configuration Atlas reads. The virtualenv and its installed dependencies tell Atlas which imports actually resolve. uv is the package manager, so a refactor that moves a module into a new subpackage keeps the dependency graph honest. ruff format is the formatter Atlas runs over the restructured files so the diff you review is a diff of substance, not whitespace. Atlas's documented Python setup is exactly this: run atlas in a repo with a pyproject.toml or requirements.txt, let Atlas read your package layout, virtualenv, and installed dependencies, then ask it to add type hints, write pytest cases, or refactor a module, and review the diff.
Step by step
- 01Run atlas in a Python repo that has a pyproject.toml or requirements.txt so Atlas can read your package layout, virtualenv, and installed dependencies.
- 02Map the legacy module's public surface with the lsp tool's documentSymbol operation, then run findReferences on each exported symbol to enumerate every callsite across src/ and tests/.
- 03Pin behavior first: run pytest through the bash tool and record the green baseline before changing anything in the module.
- 04Restructure with apply_patch, which seeks each hunk's context lines and old_lines and fails with "Failed to find context" if the file has drifted since Atlas read it.
- 05Re-run pytest with the bash tool after each hunk lands, not once at the end, so a regression is attributable to a single hunk.
- 06Track every remaining callsite in a todowrite list so a partially migrated module cannot be mistaken for a finished one.
- 07Run ruff format over the restructured modules so the unified diff Atlas surfaces for approval is substance rather than whitespace.
- 08Use uv to reconcile dependencies if the refactor moved code into a new subpackage, then re-run pytest one final time against the full suite.
Frequently asked questions
- how to safely refactor a large legacy python module without breaking imports
- Enumerate the callers first. Atlas runs the lsp tool's documentSymbol operation to list the module's exports, then findReferences on each export to find every import site. It records a green pytest baseline with the bash tool, restructures with apply_patch, and re-runs pytest after each hunk.
- what does Failed to find context mean in atlas apply_patch
- apply_patch anchors each hunk on context lines and old_lines. "Failed to find context" means the target Python file has drifted since Atlas read it, so the hunk no longer matches. apply_patch refuses to apply rather than writing to the wrong location. Re-read the file and regenerate the patch.
- should i run pytest before or after refactoring a python module
- Both, and after every hunk. Atlas runs pytest through the bash tool before touching the module to record a green baseline, then re-runs pytest after each apply_patch hunk lands rather than once at the end, so a regression is traceable to a single change.
- how does atlas find callers of a python function that is imported indirectly
- Atlas uses the lsp tool's findReferences operation, which walks the symbol graph rather than matching text. That surfaces imports that a grep for the function name would miss, including re-exports through a package __init__.py. Atlas also indexes code by AST declarations using tree-sitter for semantic search.
- can atlas refactor a django or fastapi module
- Yes. Atlas is documented for use across Python projects, from scripts to Django and FastAPI services. Run atlas in a repo with a pyproject.toml or requirements.txt, let it read your package layout and virtualenv, and it will work against your existing pytest suite and uv dependency setup.
- apply_patch vs edit in atlas which should i use for a refactor
- Use apply_patch for a structural Python refactor that spans several hunks. apply_patch anchors on context lines and old_lines and fails on a drifted file, which is the property you want when moving class bodies and rewriting imports across multiple files at once.
- does atlas format python code after refactoring
- ruff format is the formatter in Atlas's documented Python toolchain, alongside pytest as the test runner and uv as the package manager. Running ruff format over the restructured modules keeps the unified diff Atlas surfaces for approval focused on real changes rather than whitespace.
- how do i roll back a bad python refactor done by an AI agent
- Atlas snapshots file changes as git patches so edits can be diffed and rolled back. Atlas also computes a unified diff for every file edit and surfaces it for approval before writing, so a bad hunk is usually caught at review rather than after it lands.
Try Atlas in your terminal
The terminal-native AI coding agent. Free core, single binary.
Install AtlasRelated guides
Refactor a Legacy Module with Atlas in 2026
How to refactor a legacy module with Atlas in 2026: findReferences maps every callsite, apply_patch refuses to apply against a drifted file, and bash proves behavior.
Atlas for Python in 2026
Atlas is a terminal-native AI coding agent for Python in 2026. Run it in a repo with a pyproject.toml or requirements.txt and review every diff before it lands.
Onboard to an Unfamiliar Python Codebase with Atlas in 2026
Onboard to an unfamiliar Python codebase in 2026. Atlas uses codebase_search, glob, read, and lsp to map a pyproject.toml repo without opening every module.
Document a Python Module With a README Using Atlas (2026)
Atlas writes Python docs from source, not memory: lsp documentSymbol lists the real exports, read supplies behavior, and every code sample is proven with pytest.
Plan a Multi-File Change Before Editing in Python with Atlas in 2026
Plan a multi-file Python change before editing in 2026. Atlas's plan agent denies edit for every path except .atlas/plans/*.md, then plan_exit hands off to build.
Rename a Symbol Across the Repo in Python with Atlas (2026)
Rename a Python function, class, or constant repo-wide with Atlas in 2026: lsp findReferences for the true callsite set, grep for strings and docs, pytest to prove it.
Upgrade Python Dependencies and Fix Breakage with Atlas in 2026
In 2026, Python developers use Atlas to upgrade dependencies like Django or FastAPI, automatically fixing compile and test failures with `uv`, `pytest`, and `ruff format`.
Research a Third-Party API Before Integrating It in Python with Atlas in 2026
Research a third-party API before integrating it in Python in 2026. Atlas uses websearch and webfetch to pull live docs, then writes against real signatures.