116 lines
3.3 KiB
Python
116 lines
3.3 KiB
Python
"""
|
||
Управление SQLAlchemy движком и сессиями.
|
||
"""
|
||
from pathlib import Path
|
||
from typing import AsyncGenerator
|
||
|
||
from sqlalchemy.ext.asyncio import (
|
||
create_async_engine,
|
||
async_sessionmaker,
|
||
AsyncSession,
|
||
AsyncEngine
|
||
)
|
||
|
||
from middleware.loggers import logger
|
||
from .models import Base
|
||
|
||
__all__ = ("Database", "get_db")
|
||
|
||
|
||
class Database:
|
||
"""
|
||
Менеджер SQLAlchemy базы данных.
|
||
|
||
Attributes:
|
||
engine: Async движок SQLAlchemy
|
||
session_factory: Фабрика сессий
|
||
"""
|
||
|
||
def __init__(self, db_path: str = "banwords.db"):
|
||
"""
|
||
Args:
|
||
db_path: Путь к SQLite файлу
|
||
"""
|
||
# Создаём директорию если не существует
|
||
db_file = Path(db_path)
|
||
db_file.parent.mkdir(parents=True, exist_ok=True)
|
||
|
||
# SQLite URL для async
|
||
db_url = f"sqlite+aiosqlite:///{db_path}"
|
||
|
||
# Создаём async движок
|
||
self.engine: AsyncEngine = create_async_engine(
|
||
db_url,
|
||
echo=False, # Логирование SQL запросов (False для прода)
|
||
future=True,
|
||
pool_pre_ping=True, # Проверка соединения
|
||
)
|
||
|
||
# Фабрика сессий
|
||
self.session_factory = async_sessionmaker(
|
||
self.engine,
|
||
class_=AsyncSession,
|
||
expire_on_commit=False,
|
||
)
|
||
|
||
logger.info(
|
||
f"SQLAlchemy инициализирован: {db_path}",
|
||
log_type="DATABASE"
|
||
)
|
||
|
||
async def init(self) -> None:
|
||
"""Создаёт все таблицы в БД"""
|
||
try:
|
||
async with self.engine.begin() as conn:
|
||
await conn.run_sync(Base.metadata.create_all)
|
||
|
||
logger.info(
|
||
"Таблицы базы данных созданы",
|
||
log_type="DATABASE"
|
||
)
|
||
except Exception as e:
|
||
logger.error(
|
||
f"Ошибка создания таблиц: {e}",
|
||
log_type="DATABASE"
|
||
)
|
||
raise
|
||
|
||
async def close(self) -> None:
|
||
"""Закрывает соединения с БД"""
|
||
await self.engine.dispose()
|
||
logger.info("База данных закрыта", log_type="DATABASE")
|
||
|
||
def get_session(self) -> AsyncGenerator[AsyncSession, None]:
|
||
"""
|
||
Создаёт новую сессию (контекстный менеджер).
|
||
|
||
Usage:
|
||
async with db.get_session() as session:
|
||
result = await session.execute(select(BanWord))
|
||
words = result.scalars().all()
|
||
|
||
Yields:
|
||
AsyncSession: Сессия для работы с БД
|
||
"""
|
||
return self.session_factory()
|
||
|
||
|
||
# Глобальный экземпляр
|
||
_db_instance: Database | None = None
|
||
|
||
|
||
def get_db(db_path: str = "banwords.db") -> Database:
|
||
"""
|
||
Возвращает глобальный экземпляр Database (Singleton).
|
||
|
||
Args:
|
||
db_path: Путь к БД (используется только при первом вызове)
|
||
|
||
Returns:
|
||
Database: Экземпляр базы данных
|
||
"""
|
||
global _db_instance
|
||
if _db_instance is None:
|
||
_db_instance = Database(db_path)
|
||
return _db_instance
|