from fastapi import APIRouter, HTTPException import pkgutil import importlib from pathlib import Path from typing import List from bd import Settings from sqlalchemy import create_engine from sqlalchemy.exc import SQLAlchemyError from pydantic import BaseModel router = APIRouter(tags=["db"]) def _iter_table_modules() -> List[str]: try: import bd.tables as tables_pkg pkg_paths = getattr(tables_pkg, "__path__", None) if not pkg_paths: pkg_paths = [str(Path(__file__).resolve().parent.parent / "bd" / "tables")] except Exception: pkg_paths = [str(Path(__file__).resolve().parent.parent / "bd" / "tables")] names = [] for finder, name, ispkg in pkgutil.iter_modules(pkg_paths): names.append(name) return names def collect_metadatas(): metadatas = [] for mod_name in _iter_table_modules(): try: module = importlib.import_module(f"bd.tables.{mod_name}") except Exception: continue Base = getattr(module, "Base", None) if Base is not None and hasattr(Base, "metadata"): metadatas.append(Base.metadata) return metadatas @router.post("/db/create-tables") async def create_tables(): """Создаёт все таблицы, описанные в модулях `db.tables`. Endpoint вызывается по нажатию кнопки в UI (POST). """ settings = Settings() db_url = settings.DATABASE_URL_syncpg engine = create_engine(db_url, future=True) metadatas = collect_metadatas() if not metadatas: raise HTTPException(status_code=400, detail="No table metadata found in db.tables") # deduplicate metadata objects (multiple modules may expose the same Base.metadata) unique = [] seen = set() for md in metadatas: if id(md) not in seen: seen.add(id(md)) unique.append(md) try: for md in unique: md.create_all(bind=engine) return {"status": "ok", "detail": f"Created {len(unique)} metadata groups"} except SQLAlchemyError as e: raise HTTPException(status_code=500, detail=str(e)) class ClearDBIn(BaseModel): confirm: bool @router.post("/db/clear") async def clear_tables(payload: ClearDBIn): """Полная очистка всех таблиц, описанных в `bd.tables`. Требуется явное подтверждение: POST с телом {"confirm": true}. """ if not payload.confirm: raise HTTPException(status_code=400, detail="Confirmation required") settings = Settings() db_url = settings.DATABASE_URL_syncpg engine = create_engine(db_url, future=True) metadatas = collect_metadatas() if not metadatas: raise HTTPException(status_code=400, detail="No table metadata found in db.tables") # deduplicate metadata objects unique = [] seen = set() for md in metadatas: if id(md) not in seen: seen.add(id(md)) unique.append(md) try: for md in unique: md.drop_all(bind=engine) return {"status": "ok", "detail": f"Dropped {len(unique)} metadata groups"} except SQLAlchemyError as e: raise HTTPException(status_code=500, detail=str(e))