From 7fd1ee2d04e4b8037e3136f9d729f9344765f59a Mon Sep 17 00:00:00 2001 From: Verum Date: Mon, 23 Feb 2026 14:34:52 +0700 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=B2=D0=B8=D0=B6=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=20=D0=B1=D0=B0=D0=B7=D1=8B=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database/database.py | 116 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 database/database.py diff --git a/database/database.py b/database/database.py new file mode 100644 index 0000000..08685c3 --- /dev/null +++ b/database/database.py @@ -0,0 +1,116 @@ +""" +Управление SQLAlchemy движком и сессиями. +""" +from pathlib import Path +from typing import AsyncGenerator + +from sqlalchemy.ext.asyncio import ( + create_async_engine, + async_sessionmaker, + AsyncSession, + AsyncEngine +) + +from configs import settings +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 = settings.DATABASE_PATH): + """ + 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 = settings.DATABASE_PATH) -> Database: + """ + Возвращает глобальный экземпляр Database (Singleton). + + Args: + db_path: Путь к БД (используется только при первом вызове) + + Returns: + Database: Экземпляр базы данных + """ + global _db_instance + if _db_instance is None: + _db_instance = Database(db_path) + return _db_instance