DATABASE_URL_asyncpg нигде не использовался — реально работает только DATABASE_URL_syncpg/pg8000 через синхронный engine. validate_db_port был не подключён как pydantic-валидатор (нет декоратора) и никогда не вызывался — DB_PORT конвертируется строкой выше через голый int(). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
78 lines
2.5 KiB
Python
78 lines
2.5 KiB
Python
# 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
|