legacybot / database /engine.py.bak
zoldyckff999oy's picture
update project files
251964d
Raw History Blame Contribute Delete
3.93 kB
"""
Async SQLAlchemy Engine & Session factory for PostgreSQL (Neon)
"""
from __future__ import annotations
import logging
from typing import Optional
from sqlalchemy.ext.asyncio import (
AsyncEngine,
AsyncSession,
async_sessionmaker,
create_async_engine,
)
from config import settings
logger = logging.getLogger(__name__)
engine: Optional[AsyncEngine] = None
async_session_factory: Optional[async_sessionmaker[AsyncSession]] = None
def _build_engine() -> AsyncEngine:
if not settings.database_url:
raise RuntimeError(
"DATABASE_URL is not set. "
"Provide a Neon / PostgreSQL async connection string, e.g. "
"postgresql+asyncpg://user:pass@host/db?sslmode=require"
)
url = settings.database_url
# Ensure async driver
if url.startswith("postgresql://"):
url = url.replace("postgresql://", "postgresql+asyncpg://", 1)
elif url.startswith("postgres://"):
url = url.replace("postgres://", "postgresql+asyncpg://", 1)
# 🔥 CRITICAL FIX: Remove sslmode from URL and use connect_args instead
# This fixes: TypeError: connect() got an unexpected keyword argument 'sslmode'
ssl_mode = None
if "?sslmode=" in url:
# Extract and remove sslmode from URL
url_parts = url.split("?")
base_url = url_parts[0]
params = url_parts[1] if len(url_parts) > 1 else ""
# Parse params and remove sslmode
param_list = []
for param in params.split("&"):
if param.startswith("sslmode="):
ssl_mode = param.split("=")[1]
else:
param_list.append(param)
# Rebuild URL without sslmode
if param_list:
url = f"{base_url}?{'&'.join(param_list)}"
else:
url = base_url
# Configure SSL via connect_args (asyncpg compatible)
connect_args = {}
if ssl_mode:
# Map sslmode to asyncpg's ssl parameter
if ssl_mode in ("require", "verify-ca", "verify-full", "prefer"):
connect_args["ssl"] = True
elif ssl_mode == "disable":
connect_args["ssl"] = False
elif ssl_mode == "allow":
connect_args["ssl"] = True # asyncpg prefers SSL when available
# If no sslmode was found but we're using Neon, enable SSL by default
if not ssl_mode and "neon.tech" in url:
connect_args["ssl"] = True
logger.info("🔒 Neon detected: enabling SSL by default")
if connect_args:
logger.info(f"🔧 Using connect_args: {connect_args}")
return create_async_engine(
url,
echo=False,
pool_size=10,
max_overflow=20,
pool_pre_ping=True,
pool_recycle=300,
connect_args=connect_args, # 👈 THE FIX
)
async def init_database() -> None:
"""Create engine + session factory + tables. Call once at startup."""
global engine, async_session_factory
if engine is not None:
return
engine = _build_engine()
async_session_factory = async_sessionmaker(
bind=engine,
class_=AsyncSession,
expire_on_commit=False,
autoflush=False,
autocommit=False,
)
# إنشاء الجداول تلقائيًا (بديل مجاني عن Alembic على Render)
from database.base import Base
import database.models # noqa: F401 — لازم عشان تتحمل كل الجداول
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
logger.info("✅ SQLAlchemy async engine initialized + tables created")
async def close_database() -> None:
"""Dispose engine on shutdown."""
global engine, async_session_factory
if engine is not None:
await engine.dispose()
engine = None
async_session_factory = None
logger.info("✅ SQLAlchemy engine disposed")