Сегодня поговорим про то, как удобно настраивать параметры запуска программ на питоне.
Код бота из последней заметки
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
.