💽 Использование Базы Данных
Бот использует SQLAlchemy ORM в асинхронном режиме для работы с базой данных. Документация.
WARNING
Прежде чем вы сможете использовать базу данных, вы должны добавить разрешение use_db
или require_db
в список permissions
в файле config.yaml
вашего модуля!
Объявление моделей
Определите свои модели ДБ, используя декларативное отображение SQLAlchemy (например, наследуя от DeclarativeBase
). Обычно их размещают в отдельном файле, например, db.py
.
# 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. Фреймворк использует эти метаданные для автоматического создания таблиц при загрузке модуля (если они не существуют).
# 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
будет работать:
- Создаст объект базы данных (
base.db.Database
) специально для вашего модуля. По умолчанию это может быть отдельный файл SQLite (например,./ModuleName/module_db.sqlite
) или схема/база данных в общей RDBMS, в зависимости отconfig.db_url
бота. - Присвойте этот объект базы данных атрибуту
self.db
экземпляра вашего модуля. - Используйте
self.db_meta
для асинхронного запускаmetadata.create_all(bind=engine)
. - Вызовите метод
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 с операциями с базой данных.
# 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
о том, как они потенциально могут применяться во время обновлений на основе сравнения версий.