arm-core/api/scripts/seed.py
Andrey Epifancev 186ae802e6 feat: seed ролей, прав, матрицы §10 и Головного офиса, загрузчик scripts.seed
Двухфазный загрузчик: валидация yaml целиком до записи (SeedError), затем upsert по
натуральному ключу без удалений; run_seed(session, *, dev, seed_dir) без commit, CLI коммитит.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 21:07:07 +04:00

69 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Загрузчик справочников продукта: `python -m scripts.seed [--dev]` (запуск из `api/`).
Spec reference-data-seed; design D-4 и решение #18 (R-5): `run_seed(session, *, dev)` не делает commit — коммитит CLI.
Источник данных — `api/seed/core/*.yaml|csv` (+ `api/seed/dev/` при `--dev`, задача 15).
Загрузка двухфазная:
1. `load_seed` (`seed_lib/loaders.py`) читает ВСЕ файлы в память и проверяет их (обязательные поля, enum'ы из
`src.models.enums`, ссылки внутри наборов); любая ошибка — `SeedError`, база при этом не затрагивается.
2. `apply_seed` (`seed_lib/apply.py`) применяет набор к БД шагами в порядке зависимостей: upsert по натуральному
ключу, без удалений.
Расширение (задачи 13–15): поле в `SeedData` (`seed_lib/model.py`), читатель в `load_seed`, шаг в `apply_seed`.
"""
import argparse
import asyncio
import sys
from pathlib import Path
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from scripts.seed_lib.apply import apply_seed
from scripts.seed_lib.loaders import DEFAULT_SEED_DIR, load_seed
from scripts.seed_lib.model import SeedData, SeedError, SeedReport
__all__ = ["DEFAULT_SEED_DIR", "SeedError", "SeedReport", "apply_seed", "load_seed", "main", "run_seed"]
async def run_seed(session: AsyncSession, *, dev: bool = False, seed_dir: Path | None = None) -> SeedReport:
"""Загрузить и применить seed (решение #18 / R-5): валидация до записи, без commit.
`seed_dir` — каталог с `core/` (и `dev/`), по умолчанию `api/seed`; подменяется в тестах.
"""
return await apply_seed(session, load_seed(seed_dir, dev=dev))
async def _run_cli(data: SeedData) -> SeedReport:
# импорт здесь: настройки читаются из ENV_FILE только при реальном запуске,
# поэтому `--help` и ошибки валидации работают без env
from src.core.config import settings # noqa: PLC0415
engine = create_async_engine(
settings.OPENBAO.DATABASE.DATABASE_URL, connect_args={"server_settings": {"search_path": "dkb"}}
)
try:
async with async_sessionmaker(engine, expire_on_commit=False)() as session, session.begin():
return await apply_seed(session, data)
finally:
await engine.dispose()
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(prog="python -m scripts.seed", description="Загрузка справочников продукта.")
parser.add_argument("--dev", action="store_true", help="дополнительно загрузить dev-данные из seed/dev/")
parser.add_argument("--seed-dir", type=Path, default=DEFAULT_SEED_DIR, help="каталог seed (по умолчанию api/seed)")
args = parser.parse_args(argv)
try:
data = load_seed(args.seed_dir, dev=args.dev)
report = asyncio.run(_run_cli(data))
except SeedError as error:
print(f"Ошибка seed: {error}", file=sys.stderr)
return 1
print(report.format())
return 0
if __name__ == "__main__":
sys.exit(main())