База данных
Время поговорить о том, как сохранять данные бота в постоянном хранилище. Chiori предоставляет следующие компоненты для работы с базой данных:
DBModel: Представление элемента в базе данных.DBTable: Отдельная таблица для базы данных.ChioDB: Обёртка над базой данных Postgres.
Chiori во время запуска создаёт пул соединений через экземпляр ChioDB и подключается к базе данных Postgres. Во время загрузки плагинов в базе данных регистрируются таблицы CHioDB.
После, при помощи DI вы можете получить и использовать таблицы, которые были ранее подключены к базе данных.
Почему не ORM?
Заголовок раздела «Почему не ORM?»Для небольшого проекта накладно тащить полноценную ORM.
Все они выглядят достаточно громоздко и приносят больше неудобств (пока что).
Возможно в будущем начнётся использоваться одна из ORM.
Потому всё взаимодействие построек поверх asyncpg.
Определение
Заголовок раздела «Определение»Для того чтобы начать хранить наши данные в базе, нужно определить модель и таблицу, которая будет её соответствовать.
Для того чтобы создать таблицу, создайте класс-наследник от DBTable.
class WarnsTable(DBTable, table='user_warns'): def create_table(self) -> None: """Создаёт таблицу для базы данных.""" await self.pool.execute("CREATE TABLE IF NOT EXISTS user_warns (...)")По традиции название класса оканчивается на Table.
Параметр table определяет как будет называться таблица в базе данных.
Метод create_table вызывается, когда нужно создать таблицу в базе данных.
Обычно здесь вызывается sql запрос для создания таблицы.
Для начала давайте определимся с моделью данных, которую мы собираемся хранить.
Модель данных представляет из себя класс-наследник от DBModel.
DBModel в свою очередь реализует метод сборки модели из asyncpg.Record.
Если вы немного запутались, давайте рассмотрим на примере.
@dataclass(slots=True)class UserWarn(DBModel): """Предупреждение пользователя."""
id: int user_id: int guild_id: int from_id: int reason: str created_at: datetime expired_at; datetime | NoneВзаимодействие с базой
Заголовок раздела «Взаимодействие с базой»Для того чтобы взаимодействовать с базой данных, используется аттрибут pool и
его методы:
execute: Выполнить запрос.fetchrow: Выполнить запрос и получить результат.fetch: Выполнить запрос и получить все результаты.
async def get_user(self, user_id: int) -> UserActive | None: """Получает пользователя по ID.""" cur = await self.pool.fetchrow( "SELECT * FROM active WHERE user_id=$1", user_id ) return None if cur is None else UserActive.from_row(cur)Подобным образом выглядят все базовые методы для работы с базой данных. Сначала мы эти данные получаем, а после преобразуем их в модель.
Базовые методы
Заголовок раздела «Базовые методы»Ранее мы затронули базовые методы. Всё сводится к созданию, получению, изменению, удалению.
async def select(self, guild_id: int) -> list[GuildRole]: """Возвращает все роли сервера.""" cur = await self.pool.fetch( "SELECT * FROM roles_shop WHERE guild_id=$1", guild_id ) return [GuildRole.from_row(row) for row in cur]
async def get(self, guild_id: int, role_id: int) -> GuildRole | None: """Возвращает роль из магазина.""" cur = await self.pool.fetchrow( f"SELECT * FROM role_shop " "WHERE guild_id=$1 AND role_id=$2", guild_id, role_id, ) return None if cur is None else GuildRole.from_row(cur)
async def add(self, role: GuildRole) -> None: """Добавляет роль в магазин.""" await self.pool.execute( "INSERT INTO roles_shop" "(guild_id,role_id,price,require_role) VALUES($1,$2,$3,$4)", role.guild_id, role.role_id, role.price, role.require_role, )
async def remove(self, guild_id: int, role_id: int) -> None: """Удаляет роль из магазина.""" await self.pool.execute( "DELETE FROM roles_shop WHERE guild_id=$1 AND role_id=$2", guild_id, role_id, )
async def set(self, role: GuildRole) -> None: """Обновляет роль на сервере.""" await self.pool.execute( "UPDATE roles_shop SET require_role=$1, price=$2 " "WHERE guild_id=$3 AND role_id=$4", role.require_role, role.price, role.guild_id, role.role_id, )ПОдобные методы должны быть реализованы во всех таблицах для комфортной работы.
Использование
Заголовок раздела «Использование»После того как вы создали таблицу, вам обязательно нужно её зарегистрировать в клиенте. Делается это следующим образом.
plugin.add_table(WarnsTable)
# Или сразуclient.add_table(WarnsTable)Чаще всего подключение происходит во время загрузки расширения. Отсюда и появляется необходимость в расширениях-помощниках.
@arc.loaderdef loader(client: ChioClient) -> None: """Действия при загрузке плагина.""" plugin.set_config(WarnsConfig) plugin.add_table(WarnsTable) # Вот и происходит подключение client.add_plugin(plugin)Использование таблицы
Заголовок раздела «Использование таблицы»Теперь вы можете использовать созданную таблицу через DI:
@plugin.include@arc.slash_command("warns", description="Предупреждения.")async def user_warns( ctx: ChioContext, user: arc.Option[ hikari.User | None, arc.UserParams("Для какого пользователя.") ] = None, warns: WarnsTable = arc.inject() # Наша табличка) -> None: """Таблица лидеров по сообщениям."""