Фантастические конфиги и где их найти

· nikoontelegram's blog


Сегодня поговорим про то, как удобно настраивать параметры запуска программ на питоне.

Код бота из последней заметки
 1import logging
 2
 3from aiogram import Bot, Dispatcher, F
 4from aiogram.filters import CommandStart
 5
 6TOKEN = "<TOKEN>"
 7
 8# включение логирования
 9logging.basicConfig(
10    level=logging.INFO,
11    format="%(asctime)s: "
12           "%(filename)s: "
13           "%(levelname)s: "
14           "%(funcName)s(): "
15           "%(lineno)d:\t"
16           "%(message)s",
17)
18
19dp = Dispatcher()
20bot = Bot(TOKEN)
21
22
23@dp.message(CommandStart())
24async def command_start_handler(message):
25    await message.answer(f"Hello, {message.from_user.full_name}!")
26
27
28@dp.message(F.text)
29async def echo_handler(message):
30    await message.answer_photo(message.text)
31
32
33@dp.message(F.photo)
34async def photo_handler(message):
35    await message.answer(message.photo[0].file_id)
36
37
38logging.info("Application started")
39dp.run_polling(bot)

Зачем это всё? #

В нашем боте пока что можно выделить один такой параметр - это токен. Почему бы не хранить его прямо в коде, как это сделано сейчас? Главный аргумент - удобство изменений. Сейчас чтобы изменить токен нам придётся вносить изменения в код, что не всегда желательно. Если же нам потребуется запустить двух разных ботов, то придётся продублировать весь код. Также немаловажным фактором является секретность. В случае если вы захотите поделиться вашим кодом с кем-нибудь или выложить его в своём репозитории, токены и пароли хочется оставить за рамками публичного просмотра. А это значит, нам нужен механизм, позволяющий программе взять откуда-то значения для её параметров. Самыми популярными можно назвать следующие решения:

Вариант про который я хочу рассказать - что-то среднее между вторым и третьим и позволяет достигнуть достаточной на практике гибкости и, одновременно, удобства.

 1from pydantic import BaseSettings
 2
 3class Settings(BaseSettings):
 4    bot_token: str
 5
 6    # Вложенный класс с дополнительными указаниями для настроек
 7    class Config:
 8        # Имя файла, откуда будут прочитаны данные
 9        # (относительно текущей рабочей директории)
10        env_file = ".env"
11        # Кодировка читаемого файла
12        env_file_encoding = "utf-8"

Что здесь происходит? #

Этот код определяет класс настроек. В нём используется библиотека pydantic которая берёт на себя всю грязную работу и предоставляет следующий воркфлоу: Сначала, при создании переменной типа Settings будет выполнена загрузка переменных среды из файла .env, если он есть. Затем произойдёт поиск переменной среды, совпадающей по названию с полем класса - в нашем случае bot_token. На момент написания этого текста поиск происходит независимо от регистра, так что файл .env в нашем случае может выглядеть так:

1BOT_TOKEN=<TOKEN>

При появлении новых параметров программы их нужно будет добавить всего в два места: создать ещё одно поле в классе Settings и задать значение в .env-файле. Главная причина существования такой схемы - удобство развёртывания на сторонних сервисах. Как правило, они позволяют задать набор переменных среды для запуска ваших программ, а локально можно использовать описание параметров через файл. При этом и тот, и другой вариант обрабатывает один и тот же код, и при доступе к параметрам изнутри программы нет никаких различий. Перепишем нашего бота с использованием этого механизма:

 1import logging
 2
 3from aiogram import Bot, Dispatcher, F
 4from aiogram.filters import CommandStart
 5
 6from pydantic import BaseSettings
 7
 8
 9class Settings(BaseSettings):
10    bot_token: str
11
12    class Config:
13        env_file = ".env"
14        env_file_encoding = "utf-8"
15
16# включение логирования
17logging.basicConfig(
18    level=logging.INFO,
19    format="%(asctime)s: "
20           "%(filename)s: "
21           "%(levelname)s: "
22           "%(funcName)s(): "
23           "%(lineno)d:\t"
24           "%(message)s",
25)
26
27dp = Dispatcher()
28sett = Settings()
29bot = Bot(sett.bot_token)
30
31
32@dp.message(CommandStart())
33async def command_start_handler(message):
34    await message.answer(f"Hello, {message.from_user.full_name}!")
35
36
37@dp.message(F.text)
38async def echo_handler(message):
39    await message.answer_photo(message.text)
40
41
42@dp.message(F.photo)
43async def photo_handler(message):
44    await message.answer(message.photo[0].file_id)
45
46
47logging.info("Application started")
48dp.run_polling(bot)

Так же для работы этого кода нам необходимо будет установить новый пакет в наше окружение.

1pip install pydantic[dotenv]

Итого #

Теперь код бота существует отдельно от своих параметров, в нём появилось возможность задать любой токен при запуске через переменную среды BOT_TOKEN, или через файл .env.


Мой канал про разработку под телеграм: @NikoOnTelegram