# Database configuration package from pydantic import BaseModel import os import urllib.parse class Settings(BaseModel): DB_HOST: str = os.getenv("DB_HOST", "postgres") DB_PORT: int = int(os.getenv("DB_PORT", "5432")) DB_USER: str = os.getenv("DB_USER", "postgres") DB_PASS: str = os.getenv("DB_PASS", "") DB_NAME: str = os.getenv("DB_NAME", "profi") @property def DATABASE_URL_syncpg(self) -> str: pwd = urllib.parse.quote_plus(str(self.DB_PASS)) return f"postgresql+pg8000://{self.DB_USER}:{pwd}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}" @property def PG8000_CONNECT_ARGS(self) -> dict: """connect_args для pg8000 — отключаем SSL (сервер его не поддерживает).""" return {"ssl_context": None} def make_engine(): """Создаёт SQLAlchemy engine с правильными параметрами для pg8000.""" from sqlalchemy import create_engine s = Settings() return create_engine( s.DATABASE_URL_syncpg, connect_args=s.PG8000_CONNECT_ARGS, future=True, pool_pre_ping=True, pool_size=5, max_overflow=5, pool_timeout=30, pool_recycle=1800, ) def wait_for_db(retries: int = 15, delay: float = 2.0) -> None: """Ждёт, пока PostgreSQL станет доступен. Вызывается при старте приложения.""" import time import logging from sqlalchemy import text logger = logging.getLogger("bd") engine = make_engine() for attempt in range(1, retries + 1): try: with engine.connect() as conn: conn.execute(text("SELECT 1")) logger.info("PostgreSQL is ready.") return except Exception as exc: logger.warning("DB not ready (attempt %d/%d): %s", attempt, retries, exc) if attempt < retries: time.sleep(delay) raise RuntimeError("PostgreSQL did not become ready in time.") # ── Singleton engine + session factory (один движок на весь процесс) ─────── _engine = None _SessionFactory = None def get_engine(): global _engine if _engine is None: _engine = make_engine() return _engine def get_session(): global _SessionFactory if _SessionFactory is None: from sqlalchemy.orm import sessionmaker _SessionFactory = sessionmaker(bind=get_engine(), future=True) return _SessionFactory