Architecture¶
This page is the map for where code goes in rhiza_tools. The rationale
behind the layout lives in the Architecture Decision Records — see
ADR-0005 (splitting command modules by
responsibility). This page is the living reference; the ADRs are the history.
Layers¶
cli.py Typer surface — argument parsing, --help text, option wiring
└─ commands/<command>.py single-file commands that never grew satellites
└─ commands/<command>/__init__.py orchestration — for a command that outgrew one file
└─ <sibling>.py an extracted responsibility (parsing, reporting, …)
There are no command modules at present. A new command starts as a single
module (commands/<command>.py); one that outgrows the
500-line gate (tests/test_module_size.py)
is promoted to a subpackage (commands/<command>/) whose __init__.py
holds the orchestration and whose siblings own the extracted responsibilities.
The command-subpackage convention¶
When a command is promoted to a commands/<command>/ subpackage, the file name
tells you what lives there: __init__.py is the Typer-facing command
orchestration (the entry point invoked from cli.py), and each sibling owns one
extracted responsibility (parse.py, report.py, io.py, …). Dependencies
point downward only: __init__.py calls into its siblings; the siblings do not
import the package back.
Extracted helpers are re-exported from the orchestration __init__.py, so
callers and tests keep importing commands.<command>.<helper> even after a
move. When a moved function resolves a dependency in its new module's
namespace, the test patch target moves with it.
Where does my code go?¶
Work top-down through the first match:
- A new user-facing command? Add a
@app.commandincli.py(parsing +--help) and acommands/<command>.pyorchestration module for the workflow. Promote it to acommands/<command>/subpackage only once it outgrows one file. - A distinct responsibility within a command (parsing, reporting, I/O)? → a sibling module inside that command's subpackage.
Keep modules at or below the 500-line ceiling; when one approaches it, extract
the next responsibility into a sibling module within the subpackage and
re-export it from __init__.py.
Tests mirror the package¶
tests/ mirrors this layout: single-file command tests sit directly in
tests/commands/, a subpackage command's tests live under
tests/commands/<command>/, and
cross-cutting suites (CLI wiring, structural meta-tests) stay at the tests/
root. Test module basenames are kept unique across the tree because pytest runs
in prepend import mode without __init__.py package markers.