Skip to content

💽 Использование Базы Данных

Бот использует SQLAlchemy ORM в асинхронном режиме для работы с базой данных. Документация.

WARNING

Прежде чем вы сможете использовать базу данных, вы должны добавить разрешение use_db или require_db в список permissions в файле config.yaml вашего модуля!

Объявление моделей

Определите свои модели ДБ, используя декларативное отображение SQLAlchemy (например, наследуя от DeclarativeBase). Обычно их размещают в отдельном файле, например, db.py.

python
# db.py
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import String, Integer

class Base(DeclarativeBase):
    pass

class UserProfile(Base):
    __tablename__ = "user_profiles"

    user_id: Mapped[int] = mapped_column(primary_key=True)
    display_name: Mapped[str] = mapped_column(String(100))
    points: Mapped[int] = mapped_column(Integer, default=0)

# Здесь вы можете определить и другие таблицы/модели

Предоставление метаданных

Ваш основной класс модуля должен отображать объект метаданных SQLAlchemy, связанный с вашими моделями. Это делается путем переопределения свойства db_meta. Фреймворк использует эти метаданные для автоматического создания таблиц при загрузке модуля (если они не существуют).

python
# main.py (внутри вашего класса BaseModule)
from .db_models import Base
from sqlalchemy import MetaData

# ... другие импорты и определение классов ...

    @property
    def db_meta(self) -> MetaData:
        """Предоставляет метаданные SQLAlchemy для создания таблицы."""
        return Base.metadata

Инициализация и Доступ к Базе Данных

Если доступ к базе данных разрешен (use_db или require_db в конфигурации) и база данных бота включена, то ModuleLoader будет работать:

  1. Создаст объект базы данных (base.db.Database) специально для вашего модуля. По умолчанию это может быть отдельный файл SQLite (например, ./ModuleName/module_db.sqlite) или схема/база данных в общей RDBMS, в зависимости от config.db_url бота.
  2. Присвойте этот объект базы данных атрибуту self.db экземпляра вашего модуля.
  3. Используйте self.db_meta для асинхронного запуска metadata.create_all(bind=engine).
  4. Вызовите метод async def on_db_ready(self) в вашем модуле после того, как таблицы будут гарантированно существовать.

Объект self.db содержит:

  • engine: Экземпляр AsyncEngine, подключенный к конкретной базе данных/схеме вашего модуля.
  • session_maker: Привязанный к движку async_sessionmaker, настроенный на expire_on_commit=False. Используйте его для создания асинхронных сессий.

Выполнение Операций с Базой Данных

Используйте async_sessionmaker (self.db.session_maker) для создания сессий внутри блока async с операциями с базой данных.

python
# main.py (внутри вашего класса BaseModule)
from .db_models import UserProfile
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update

# ... другие методы ...

    async def get_user_points(self, user_id: int) -> int:
        """Fetches user points from the database."""
        async with self.db.session_maker() as session: # Создать сессию
            session: AsyncSession # Type hint for clarity
            # Используйте scalar_one_or_none для получения одного необязательного столбца записи
            points = await session.scalar(
                select(UserProfile.points).where(UserProfile.user_id == user_id)
            )
            return points if points is not None else 0

    async def add_points(self, user_id: int, points_to_add: int):
        """Adds points to a user, creating profile if needed."""
        async with self.db.session_maker() as session:
            # Попробуйте обновить существующего пользователя
            stmt = (
                update(UserProfile)
                .where(UserProfile.user_id == user_id)
                .values(points=UserProfile.points + points_to_add)
            )
            result = await session.execute(stmt)

            # Если ни одна строка не была обновлена, пользователь еще не существует
            if result.rowcount == 0:
                # Создайте новый профиль (при условии, что display_name может быть получено из другого места)
                # В реальном сценарии вы можете получить имя или сделать это по-другому
                display_name = f"User_{user_id}" # Заглушка
                new_profile = UserProfile(
                    user_id=user_id,
                    display_name=display_name,
                    points=points_to_add
                )
                session.add(new_profile)

            # Зафиксируйте изменения для этой сессии
            await session.commit()

    async def on_db_ready(self):
        """Вызывается после настройки БД. Может использоваться для первоначальной загрузки данных."""
        self.logger.info("База данных готова! Выполнение первоначальных проверок...")
        # Пример: Регистрация количества существующих профилей пользователей
        async with self.db.session_maker() as session:
            count = await session.scalar(select(func.count(UserProfile.user_id)))
            self.logger.info(f"Найдено {count} профилей пользователей в базе данных.")

Миграция ДБ

Чтобы управлять изменениями схемы базы данных при обновлении модуля, поместите скрипты миграции в каталог db_migrations/ в вашем модуле. Смотрите base.db_migration.DBMigration и метод ModuleManager.update_from_git о том, как они потенциально могут применяться во время обновлений на основе сравнения версий.