T6: Decompose CLI — main.py → 9 command files, config.py → config_cmd.py

Split infrastructure/cli/:
- main.py (330 LOC) → 9 files in infrastructure/cli/commands/:
  * init.py, forecast.py, scenario.py, whatif.py, compare.py
  * import_xlsx.py, export_xlsx.py, analyze.py, info.py
- config.py (428 LOC) → commands/config_cmd.py (15 sub-commands)
- main.py → app.py (entry point: registers all commands)
- New: paths.py (MODEL_PATH, DATA_DIR)
- New: services.py (composition root + helpers: load_model, save_model,
  build_services, resolve_currency, convert_value, fmt_amount)
- cli/__init__.py (empty) удалён

Each command module: <100 LOC.
pyproject.toml: cf = infrastructure.cli.app:app
tests/test_cli.py: import обновлён на infrastructure.cli.app

CLI behavior идентично, все команды работают.
Tests: 67/67 pass.
This commit is contained in:
2026-10-08 16:56:49 +03:00
parent 541a76835f
commit b6b64a9878
17 changed files with 543 additions and 400 deletions
@@ -0,0 +1,29 @@
"""cf import-xlsx — импорт данных из Excel."""
import typer
from infrastructure.cli.i18n import t
from infrastructure.cli.services import console, save_model
from infrastructure.repositories import ExcelRepository, ExcelRepositoryError
app = typer.Typer()
_repo = ExcelRepository()
@app.command(name="import-xlsx")
def import_xlsx(
path: str = typer.Argument(..., help=t("cmd.import.arg.path")),
) -> None:
"""Импорт данных из Excel"""
try:
model = _repo.load(path)
save_model(model)
console.print(t("cmd.import.ok",
accounts=len(model.accounts),
transactions=len(model.transactions),
recurring=len(model.recurring),
assets=len(model.assets),
liabilities=len(model.liabilities)))
console.print(t("cmd.import.base_currency", currency=model.base_currency))
except ExcelRepositoryError as e:
console.print(f"[red]{t('cmd.import.err', error=str(e))}[/red]")
raise typer.Exit(1)