name: polylith-migrate-isolate-base-and-big-component
description: "[Internal sub-skill of polylith-migrate-orchestrator. Do not load directly — load polylith-migrate-orchestrator first, which drives all phases.] Shrink the temporary migration base into thin base(s) + one big component. Bases contain only entrypoints/wiring, while the big component contains everything else."
Skill: polylith-migrate-isolate-base-and-big-component
Goal
Shrink the temporary migration base into thin base(s) + one big component:
- Bases: Contain only entrypoints/wiring (e.g., FastAPI endpoints, CLI wiring, consumer wiring).
- Big Component: Contains all other code.
⚠ Base vs component naming (read first). A base and a component cannot share the same brick name — both would resolve to the Python package
<TARGET_TOP_NS>.<name>and collide. So the big component takesINITIAL_BASE_NAME(<TARGET_TOP_NS>.<INITIAL_BASE_NAME>) and each base is renamed to a distinct, entrypoint-specific name (the<base>used in step 5 — e.g.<INITIAL_BASE_NAME>_api,<INITIAL_BASE_NAME>_handler). This is why the temporary base must be renamed when the component claims its name.Multiple entrypoints → multiple thin bases. A real service often has several deployable entrypoints (an HTTP API + one or more queue consumers + scheduled jobs). Create one thin base per entrypoint (e.g.
<x>_api,<x>_handler,<x>_jobs), each exposing only its run/wiring function and importing logic from components. Do not force them into a single base. (Shared startup wiring —init_db,init_logging, … — goes into a small shared component, not duplicated per base; seepolylith-migrate-distribute-wiring.)
Inputs
From migration/<PROJECT>/state.md:
TARGET_TOP_NSINITIAL_BASE_NAME- Verification commands.
From migration/<PROJECT>/manifest.md:
- Entrypoints list.
All inputs from
state.mdare assumed to satisfy the validation rules inpolylith-migrate-discover(### Validation rules). Validate before proceeding.
Steps
1. Create the Big Component
- Create
components/<TARGET_TOP_NS>/<INITIAL_BASE_NAME>/. - ⚠ If the temporary base currently occupies
bases/<TARGET_TOP_NS>/<INITIAL_BASE_NAME>/, rename the base(s) first — they must not keep the same name as the big component (see the naming callout above). Create one renamed thin base per entrypoint (<base>in step 5), and update the project's run scripts / Dockerfile to point at the new base name(s).
2. Move Non-Entrypoint Code
- Move non-entrypoint code from the base(s) to the big component.
3. Define Public API
- Define a minimal public API in
components/<TARGET_TOP_NS>/<INITIAL_BASE_NAME>/__init__.py.
4. Update Bases
- Update bases to import only from component APIs:
from <TARGET_TOP_NS>.<INITIAL_BASE_NAME> import ...
5. Update pyproject.toml
- Add the new component to
[tool.polylith.bricks]:[tool.polylith.bricks] "../../bases/<TARGET_TOP_NS>/<base>" = "<TARGET_TOP_NS>/<base>" "../../components/<TARGET_TOP_NS>/<INITIAL_BASE_NAME>" = "<TARGET_TOP_NS>/<INITIAL_BASE_NAME>"
6. Update manifest.md
- Reflect the new structure in
migration/<PROJECT>/manifest.md.
7. FastAPI Guidance
| Stays in Base | Moves to Big Component |
|---|---|
app = FastAPI(...) |
Domain/business logic |
| Middleware, router registration | Persistence/repositories |
| Route handlers (endpoints) | External integrations |
| Startup/shutdown/lifespan wiring | Reusable parsing/validation |
Verify
RUN_TEST_CMDsucceeds.- If set,
RUN_LINT_CMDandRUN_TYPECHECK_CMDsucceed. - Run
POLY_CMD_PREFIX checkto validate the workspace structure. - Run
POLY_CMD_PREFIX syncto synchronize the[tool.polylith.bricks]table with actual imports.
Common failure modes
| Symptom | Likely cause | Remediation |
|---|---|---|
| Circular import: base imports from component, component imports back from base | Some "non-entrypoint" code was moved but still references base-only helpers (e.g. the FastAPI app instance). |
Move the helper down into the component, or invert the dependency by passing the needed value as a function argument. The base's app instance must never be imported by a component. |
poly check / import error: base and big component both named <INITIAL_BASE_NAME> |
The base wasn't renamed when the component claimed INITIAL_BASE_NAME — two bricks now map to <TARGET_TOP_NS>.<INITIAL_BASE_NAME>. |
Rename the base to an entrypoint-specific name (<INITIAL_BASE_NAME>_api, _handler, …), update [tool.polylith.bricks] and the run scripts, then POLY_CMD_PREFIX sync. |
ImportError: cannot import name '<x>' from '<TARGET_TOP_NS>.<INITIAL_BASE_NAME>' |
The big component's __init__.py doesn't re-export <x>. Code that previously reached into submodules now needs the public API. |
Add from <TARGET_TOP_NS>.<INITIAL_BASE_NAME>.<submodule> import <x> to the component's __init__.py, or have the caller import the submodule directly (and accept the brick-interface violation that poly deps --interface will flag). |
| Base file becomes near-empty after the split | Good — that's the goal. But check: is there any wiring left, or did you accidentally move the entrypoint itself? | If the entrypoint (e.g., app = FastAPI(...)) ended up in the component, move it back to the base. The base must own the entrypoint object. |
poly check flags the component as not used by any project |
The base's imports go to the wrong namespace (e.g., from <ORIG_TOP_NS>...) so the import graph doesn't reach the component. |
Update base imports to from <TARGET_TOP_NS>.<INITIAL_BASE_NAME> import … and re-run POLY_CMD_PREFIX sync. |
| Tests for moved code now fail to find fixtures | conftest.py was left in the base or moved to the wrong scope. |
Move test fixtures alongside the code they cover; usually that's under test/components/<TARGET_TOP_NS>/<INITIAL_BASE_NAME>/. |
| Verification fails and you can't quickly diagnose | Phase commit not yet made. | git reset --hard HEAD to roll back to the previous phase's commit and consult the user. |
Commit
After verification passes, commit this phase to the migration branch:
git add -A && git commit -m "migrate(<PROJECT>): phase <N> — isolate-base-and-big-component"
Substitute <PROJECT>, <N>, and <phase-name> from state.md and the orchestrator's phase table. Do not proceed to the next phase without a clean commit — the per-phase commit is the rollback point for the next phase's failure-mode tables.