Двухфазный загрузчик: валидация yaml целиком до записи (SeedError), затем upsert по натуральному ключу без удалений; run_seed(session, *, dev, seed_dir) без commit, CLI коммитит. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
69 lines
3.4 KiB
Python
69 lines
3.4 KiB
Python
"""Загрузчик справочников продукта: `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())
|