🟣 Опис роботи echo-бота. Основні класи.
✅ Токен бота беремо з змінних оточення або
✅ Чисто з естетичних причин ми перехоплюємо виключення KeyboardInterrupt.
Це виключення виникає при натисненні юзером Ctrl+C у терміналі і в таких випадках мабуть ми не хочемо бачити stack trace помилок.
✅ Для автоматичного закриття сесії бота використовуємо асинхронний контекстний менеджер.
✅ Класи Dispatcher, Router дозволяють реєструвати обробники оновлень(подій).
Загалом система диспетчеризації оновлень по обробникам може мати складну деревовидну структуру.
Dispatcher - це головний об'єкт який може включати в собі Router-и, водночас Router може включати в себе інші Router-и. Звучить складно, але загалом практичний сенс цього полягає у тому що ми групуємо обробники, по якомусь принципу у логічну одиницю -- Router, а Router-и додаємо у Dispatcher. Користь Router-ів у тому що ми можемо легко винести обробники оновлень у окремі файли - модулі. Крім того кожен роутер може мати свій Filter і/та Middleware, що дуже зручно (про це можливо пізніше)
Розглянемо приклад структури проєкту:
main.py
Логіка розділення:
▪️Очевидно, що адміністратори бота і/або чату можуть мати особливі права і їх дії треба обробляти окремо.
▪️Окремо також варто оброблювати повідомлення у особисте спілкування з ботом.
▪️ Окремо оброблювати події в каналах та групах.
⚠️ Це лише приклад, структура-розділення має залежати від суті бота.
Головне мати логічну вибудовану структуру, а не всі обробники у одному файлі.
✅ MemoryStorage - це клас сховища станів. Воно потрібне щоб тимчасово зберігати стан заповнення форми, а також дані полів які користувач вже заповнив перед тим як ці дані застосовувати(наприклад записати у БД)
#tg_bots
✅ Токен бота беремо з змінних оточення або
.env-файлу якщо він є.✅ Чисто з естетичних причин ми перехоплюємо виключення KeyboardInterrupt.
Це виключення виникає при натисненні юзером Ctrl+C у терміналі і в таких випадках мабуть ми не хочемо бачити stack trace помилок.
✅ Для автоматичного закриття сесії бота використовуємо асинхронний контекстний менеджер.
✅ Класи Dispatcher, Router дозволяють реєструвати обробники оновлень(подій).
Загалом система диспетчеризації оновлень по обробникам може мати складну деревовидну структуру.
Dispatcher - це головний об'єкт який може включати в собі Router-и, водночас Router може включати в себе інші Router-и. Звучить складно, але загалом практичний сенс цього полягає у тому що ми групуємо обробники, по якомусь принципу у логічну одиницю -- Router, а Router-и додаємо у Dispatcher. Користь Router-ів у тому що ми можемо легко винести обробники оновлень у окремі файли - модулі. Крім того кожен роутер може мати свій Filter і/та Middleware, що дуже зручно (про це можливо пізніше)
Розглянемо приклад структури проєкту:
project/
├── main.py (містить dp: Dispatcher)
├── keyboards.py
├── middleware.py
└── handlers/
├── __init__.py
├── admins.py (містить admin_router: Router)
├── private.py (містить private_router: Router)
├── сhannels.py (містить сhannels_router: Router)
└── group_users.py (містить group_router: Router)
main.py
from handles.admins import admin_router
from handles.private import private_router
# ...
dp = Dispatcher(storage=storage)
dp.include_router(admin_router)
dp.include_router(private_router)
# ...
dp.include_router(group_router)
Логіка розділення:
▪️Очевидно, що адміністратори бота і/або чату можуть мати особливі права і їх дії треба обробляти окремо.
▪️Окремо також варто оброблювати повідомлення у особисте спілкування з ботом.
▪️ Окремо оброблювати події в каналах та групах.
⚠️ Це лише приклад, структура-розділення має залежати від суті бота.
Головне мати логічну вибудовану структуру, а не всі обробники у одному файлі.
✅ MemoryStorage - це клас сховища станів. Воно потрібне щоб тимчасово зберігати стан заповнення форми, а також дані полів які користувач вже заповнив перед тим як ці дані застосовувати(наприклад записати у БД)
#tg_bots
👍1
🟤 Опис роботи echo-бота. Lifespan events обробники
Коли бот запущен і готовий приймати оновлення викликається startup-обробник.
У ньому можна відкинути всі оновлення які накопичились поки бот не працював.
Коли бот зупиняється викликається shutdown-обробник
#tg_bots
async def startup(bot: Bot) -> None:
await bot.delete_webhook(drop_pending_updates=True)
print("Bot started.")
async def shutdown(bot: Bot) -> None:
print("Bot stopped.")
# ...
dp = Dispatcher(storage=storage)
dp.startup.register(startup)
dp.shutdown.register(shutdown)
Коли бот запущен і готовий приймати оновлення викликається startup-обробник.
У ньому можна відкинути всі оновлення які накопичились поки бот не працював.
Коли бот зупиняється викликається shutdown-обробник
#tg_bots
👍1
Що таке message у рядку кода "@router.message()" ? (може бути декілька прав. відповідей)
Anonymous Poll
4%
Функція
22%
Об'єкт класу, клас якого реалізує патерн Observer
4%
Об'єкт класу
33%
Метод
52%
Декоратор
4%
Клас
4%
Дескриптор
11%
Енкриптор
22%
Не знаю, хочу лише подивитись результати опитування ...
🔴 Опис роботи echo-бота. Підписка на події/оновлення.
Подивимось код реалізації класу Router у aiogram3:
aiogram/dispatcher/router.py 🔗
aiogram/dispatcher/event/telegram.py 🔗
З цих фрагментів коду бачимо що:
▪️ message це об'єкт класу TelegramEventObserver. Назва класа каже сама за себе.
▪️ У класі реалізований магічний метод
▪️ В тілі методу
Отже правильна відповідь на питання у опитуванні: message — це:
✅ Об'єкт класу, клас якого реалізує патерн Observer (реалізує підписку на події/оновлення)
✅ Об'єкт класу (клас-декоратор)
✅ Декоратор (клас-декоратор)
Cтаття про патерн Observer у контексті aiogram3 🔗
Що варто підкреслити:
🔺 У класі Router є атрибути назви яких відповідають типам подій(update) Telegram
🔺 Кожен з цих атрибутів дозволяє реєструвати асинхронну функцію обробник на відповідний тип події(update). Для цього використовується синтаксис декораторів.
🔺 Об'єкт клас-декоратора параметром може приймати фільтри. Функція обробник зберігаєтьься разом з фільтрами.
🔺 Асинхронна функція приймає першим параметром json від Telegram серверу десеріалізований у об'єкт події у нашему випадку це message: Message.
#tg_bots
Подивимось код реалізації класу Router у aiogram3:
aiogram/dispatcher/router.py 🔗
class Router:
def __init__(self, *, name: str | None = None) -> None:
# ...
self.message = TelegramEventObserver(
router=self,
event_name="message",
)
aiogram/dispatcher/event/telegram.py 🔗
class TelegramEventObserver:
# ...
def __call__(self, *filters: CallbackType, flags, **kwargs):
def wrapper(callback: CallbackType) -> CallbackType:
self.register(callback, *filters, flags=flags, **kwargs)
return callback
return wrapper
З цих фрагментів коду бачимо що:
▪️ message це об'єкт класу TelegramEventObserver. Назва класа каже сама за себе.
▪️ У класі реалізований магічний метод
__call__, тобто об'єкти цього класу можна викликати як функції/методи.▪️ В тілі методу
__call__ бачимо типову реалізацію декоратора.Отже правильна відповідь на питання у опитуванні: message — це:
✅ Об'єкт класу, клас якого реалізує патерн Observer (реалізує підписку на події/оновлення)
✅ Об'єкт класу (клас-декоратор)
✅ Декоратор (клас-декоратор)
Cтаття про патерн Observer у контексті aiogram3 🔗
Що варто підкреслити:
🔺 У класі Router є атрибути назви яких відповідають типам подій(update) Telegram
🔺 Кожен з цих атрибутів дозволяє реєструвати асинхронну функцію обробник на відповідний тип події(update). Для цього використовується синтаксис декораторів.
🔺 Об'єкт клас-декоратора параметром може приймати фільтри. Функція обробник зберігаєтьься разом з фільтрами.
🔺 Асинхронна функція приймає першим параметром json від Telegram серверу десеріалізований у об'єкт події у нашему випадку це message: Message.
#tg_bots
👍1
🟢 Опис роботи echo-бота. Обробники подій. Фільтри
Трохи теорії по Telegram:
👉 Команда - це текстове повідомлення яке починається на символ
👉 /start - команда з якої починається робота у особисті з ботом
У router реєструються дві функції-обробника при чому підписка йде на одну ту саму подію message.
Різниця лише у тому що
👉 Фільтр - це функція, спеціальний об'єкт або "магічний" вираз який перевіряє чи відповідають певні дані події/оновлення якимось критеріям.
#tg_bots
Трохи теорії по Telegram:
👉 Команда - це текстове повідомлення яке починається на символ
/ (або інший символ)👉 /start - команда з якої починається робота у особисті з ботом
router = Router()
@router.message(Command("start"))
async def start_handler(message: Message):
... # код обробника
@router.message()
async def echo_handler(message: Message):
... # код обробника
У router реєструються дві функції-обробника при чому підписка йде на одну ту саму подію message.
Різниця лише у тому що
start_handler завдяки встановленому фільтру Command("start") буде реагувати тільки команду /start, а на всі інші повідомлення буде реагувати echo_handler.👉 Фільтр - це функція, спеціальний об'єкт або "магічний" вираз який перевіряє чи відповідають певні дані події/оновлення якимось критеріям.
#tg_bots
👍1
Що буде, якщо поміняти оголошення обробників start_handler та echo_handler місцями (змінити порядок оголошення) у коді?
Anonymous Quiz
8%
Обробники працюватимуть так само, порядок оголошення не впливає на роботу.
0%
Порядок оголошення обробників впливає лише на швидкодію, але функціонально все буде правильно.
83%
echo_handler перехоплюватиме всі повідомлення включно з /start, start_handler ніколи не виконається.
8%
Буде помилка через зміну порядку оголошень обробників
👏1
🔗 Update propagation - схема наглядно показує як відбувається вибір обробника з урахуванням фільтрів та порядку оголошення.
⚠️ Завжди треба враховувати порядок оголошення обробників, і як наслідок порядок включення роутерів у диспетчер.
#tg_bots
⚠️ Завжди треба враховувати порядок оголошення обробників, і як наслідок порядок включення роутерів у диспетчер.
#tg_bots
👍1
🟣 Опис роботи echo-бота. Альтернативний спосіб реєстрації обробників.
Якщо повернутись до коду TelegramEventObserver у методі
#tg_bots
Якщо повернутись до коду TelegramEventObserver у методі
__call__ викликається метод register. Цей метод можна використовувати напряму. Не те що б це мало практичне застоcування, скоріше це пояснює як воно працює. У aiogram2 це мало сенс — це дозволяло виносити обробники у окремі модулі без використання Router-ів (бо їх ще не було) async def start_handler(message: Message):
... # код обробника
async def echo_handler(message: Message):
... # код обробника
# ...
router = Router()
router.message.register(start_handler, Command("start"))
router.message.register(echo_handler)
#tg_bots
Відео з гарним поясненням про те як працює телеграм бот.
(Але стосовно коду, особисто в мене є питання)
https://www.youtube.com/watch?v=fOQh61IoS4M
#tg_bots
(Але стосовно коду, особисто в мене є питання)
https://www.youtube.com/watch?v=fOQh61IoS4M
#tg_bots
YouTube
Корисний Телеграм Бот на Пайтоні
В цьому відео ми не тільки розберемося як працює Телеграм Бот і як можна взаємодіяти з ним задопомогою Рест Апі запитів, а також створимо корисного телеграм бота, який дозволить розраховувати вагу посилок і зберігати конфігурацію у локальному сховищі.
Підтримати…
Підтримати…
🔵 Telegram боти. Сигнатура обробника оновлень.
Як я вже зазначав першим параметром обробника оновлень обов'язково має бути об'єкт напряму асоційований з оновленням, у нього може бути будь-яке ім'я але він буде відповідного класу.
В даному випадку це клас Message
Крім першого обов'язкового параметру є ще необов'язкові параметри.
Наприклад ми можемо просто додати параметр bot: Bot у функцію обробник:
І це буде прауювати. Параметр bot: Bot буде автоматично переданий при виклику обробника. Тут важлива точна назва параметра так як він передається по "ключу". Аiogram3 для реалізації цього моменту під капотом використовує introspection (модуль inspect)
Серед необов'язкових параметрів також може передаватись наш власний об'єкт через механізм 🔗 Dependency injection .Це може бути якійсь налаштування, сесія БД, якийсь кеш.
#tg_bots
Як я вже зазначав першим параметром обробника оновлень обов'язково має бути об'єкт напряму асоційований з оновленням, у нього може бути будь-яке ім'я але він буде відповідного класу.
@router.message(Command("start"))
async def start_handler(message: Message):
... В даному випадку це клас Message
Крім першого обов'язкового параметру є ще необов'язкові параметри.
Наприклад ми можемо просто додати параметр bot: Bot у функцію обробник:
@router.message(Command("start"))
async def start_handler(message: Message, bot: Bot):
... # код використання bot І це буде прауювати. Параметр bot: Bot буде автоматично переданий при виклику обробника. Тут важлива точна назва параметра так як він передається по "ключу". Аiogram3 для реалізації цього моменту під капотом використовує introspection (модуль inspect)
Серед необов'язкових параметрів також може передаватись наш власний об'єкт через механізм 🔗 Dependency injection .Це може бути якійсь налаштування, сесія БД, якийсь кеш.
#tg_bots
👍1
🟠 Telegram боти. Приблизний список додаткових параметрів в обробниках.
bot: Bot
command: CommandObject
fsm_storage: MemoryStorage
state: FSMContext
dispatcher: Dispatcher
bots: tuple
raw_state: str | None
handler: HandlerObject
event_update: Update
event_router: Router
event_context: EventContext
event_from_user: User
event_chat: Chat
#tg_bots
bot: Bot
command: CommandObject
fsm_storage: MemoryStorage
state: FSMContext
dispatcher: Dispatcher
bots: tuple
raw_state: str | None
handler: HandlerObject
event_update: Update
event_router: Router
event_context: EventContext
event_from_user: User
event_chat: Chat
#tg_bots
👍2
🟡 Telegram боти. Приклад використання додаткових параметрів у обробнику
Команди телеграм бота можуть мати аргументи.
Уявімо ситуацію коли ми хочемо реалізувати команду яка рахує суму чисел:
У відповідь будемо очікувати:
До обробника додамо параметр command: CommandObject 🔗 , який полегшує розбір аргументів команди.
Атрибут command.args це рядок з аргументами, якщо вони є, а якщо немає він дорівнює None.
#tg_bots
Команди телеграм бота можуть мати аргументи.
Уявімо ситуацію коли ми хочемо реалізувати команду яка рахує суму чисел:
/sum 1 2 3.5
У відповідь будемо очікувати:
Сума: 6.5
До обробника додамо параметр command: CommandObject 🔗 , який полегшує розбір аргументів команди.
Атрибут command.args це рядок з аргументами, якщо вони є, а якщо немає він дорівнює None.
from aiogram.filters import CommandObject
@router.message(Command("sum"))
async def sum_handler(message: Message, command: CommandObject):
success = False
if args := command.args and command.args.split():
try:
result = sum(map(float, args))
success = True
await message.answer(f"Сума: {result}")
except ValueError:
pass
if not success:
await message.answer(
"Щось пішло не так, можливо передані неправильні аргументи /sum"
)
#tg_bots
👍1
🔴 Опис роботи echo-бота. Відправка повідомлень.
Розглянемо код тіла функції-обробника.
Є два способи відправляти повідомлення:
▪️ через об'єкт Message
▪️ через об'єкт Bot
Перевага відправки через bot: Bot у тому що можна відсилати повідомлення у інші чати і гілки чатів.
Telegram дозволяє надсилати форматований текст у форматах HTML і Markdown
(насправді — у їхніх дуже спрощених версіях).
🔗 Telegram Bot API: Markdown v2
🔗 Telegram Bot API: HTML
⚠️ Якщо передати некоректний HTML, виникне виняток типу:
Тому завжди потрібно зважати на текст, який передається у вигляді змінних — чи не може він зламати HTML-розмітку. У нашому прикладі message.from_user.first_name — це ім’я користувача, яке може містити символи
👉 Щоб уникнути таких помилок, використовуйте екранування за допомогою html.unparse() та інші функції для форматування у aiogram3
Корисні посилання по темі:
🔗 Aiogram 3 FAQ : Bad Request: can't parse entities
🔗 aiogram3 documentation: Formatting
#tg_bots
Розглянемо код тіла функції-обробника.
Є два способи відправляти повідомлення:
▪️ через об'єкт Message
▪️ через об'єкт Bot
from aiogram import html
@router.message(Command("start"))
async def start_handler(message: Message):
await message.answer(
f"<i>Hello</i> {html.unparse(message.from_user.first_name)}!",
parse_mode=ParseMode.HTML
)
Перевага відправки через bot: Bot у тому що можна відсилати повідомлення у інші чати і гілки чатів.
from aiogram import html
@router.message(Command("start"))
async def start_handler(message: Message, bot: Bot):
await bot.send_message(
chat_id=message.chat.id,
text=f"<i>Hello</i> {html.unparse(message.from_user.first_name)}!",
parse_mode=ParseMode.HTML
)
Telegram дозволяє надсилати форматований текст у форматах HTML і Markdown
(насправді — у їхніх дуже спрощених версіях).
🔗 Telegram Bot API: Markdown v2
🔗 Telegram Bot API: HTML
⚠️ Якщо передати некоректний HTML, виникне виняток типу:
TelegramBadRequest('Telegram server says - Bad Request: can't parse entities ...')
Тому завжди потрібно зважати на текст, який передається у вигляді змінних — чи не може він зламати HTML-розмітку. У нашому прикладі message.from_user.first_name — це ім’я користувача, яке може містити символи
< або > що зламає розмітку.👉 Щоб уникнути таких помилок, використовуйте екранування за допомогою html.unparse() та інші функції для форматування у aiogram3
Корисні посилання по темі:
🔗 Aiogram 3 FAQ : Bad Request: can't parse entities
🔗 aiogram3 documentation: Formatting
#tg_bots
👍1
У об'єкта message є атрибут from_user, які твердження правильні?
Anonymous Poll
6%
from_user.id може бути None
28%
from_user.first_name може бути None
56%
from_user.last_name може бути None
28%
from_user.username може бути None
28%
Сам атрибут message.from_user може бути None
22%
Не знаю, я просто хочу подивитися результати опитування
🟤 Телеграм боти. Використання магічних фільтрів.
🔗 Message.from_user
🔗 User
Як бачимо:
▪️ username і last_name можуть бути None
▪️ cам атрибут from_user може бути None
⚠️ Об'єкти у aiogram3 часто можуть бути None — варто зважати на це.
Те що ми використовуємо first_name у коді обробника не спричиняє проблем.А ось те що не перевіряєм from_user на None це потенційна проблема. Чесно кажучи я не можу назвати випадок коли повідомлення може приходити без відправника, але мабуть може ... і мабуть ми хотіли би уникнути проблем пов'язаних з цим. Крім того тайп чекери типу mypy при перевірці коду будут вказувати на цю потенційну проблему.
👉 Рішенням може бути використання магічних фільтрів:
Фільтр бере об'єкт message і перевіряє атрибут from_user, якщо from_user is None обробник просто не буде викликатись, якщо не None то from_user буде переданий як додатковий параметр.
Виклик методу .as_() як раз відповідає за передачу додаткового параметру у обробник, у ньому вказується ім'я цього параметру.
#tg_bots
🔗 Message.from_user
🔗 User
class User(TelegramObject):
id: int
first_name: str
last_name: str | None
username: str | None
# ...
Як бачимо:
▪️ username і last_name можуть бути None
▪️ cам атрибут from_user може бути None
⚠️ Об'єкти у aiogram3 часто можуть бути None — варто зважати на це.
Те що ми використовуємо first_name у коді обробника не спричиняє проблем.А ось те що не перевіряєм from_user на None це потенційна проблема. Чесно кажучи я не можу назвати випадок коли повідомлення може приходити без відправника, але мабуть може ... і мабуть ми хотіли би уникнути проблем пов'язаних з цим. Крім того тайп чекери типу mypy при перевірці коду будут вказувати на цю потенційну проблему.
👉 Рішенням може бути використання магічних фільтрів:
from aiogram F
from aiogram.types import User
@router.message(Command("start"), F.from_user.as_("from_user"))
async def start_handler(message: Message, from_user: User):
await message.answer(
f"<i>Hello</i> {html.unparse(from_user.first_name)}!",
parse_mode=ParseMode.HTML
)
Фільтр бере об'єкт message і перевіряє атрибут from_user, якщо from_user is None обробник просто не буде викликатись, якщо не None то from_user буде переданий як додатковий параметр.
Виклик методу .as_() як раз відповідає за передачу додаткового параметру у обробник, у ньому вказується ім'я цього параметру.
#tg_bots
👍1
Чи варто продовжувати писати пости і писати про телеграм ботів?
Здається про тг ботів писати можна вічно. Буде ще один канал по тг ботам?
Але наразі по телеграм ботам такий приблизний план:
▪️ pydantic-settings
▪️ Dependency injection
▪️ Filters / BotAdminFilter
▪️ Робота з фото.
Можливо варто переключитись на іншу тему, наприклад
про скрапінг даних? Якийсь сайт заскрапати?
Пишіть пропозиції у коментарях і голосуйте.
Здається про тг ботів писати можна вічно. Буде ще один канал по тг ботам?
Але наразі по телеграм ботам такий приблизний план:
▪️ pydantic-settings
▪️ Dependency injection
▪️ Filters / BotAdminFilter
▪️ Робота з фото.
Можливо варто переключитись на іншу тему, наприклад
про скрапінг даних? Якийсь сайт заскрапати?
Пишіть пропозиції у коментарях і голосуйте.
👍1👎1
❓ Що виведе код:
👉 🔗 Перечитати пост про екранування html.
🡇🡇🡇 Опитування нижче 🡇🡇🡇
from aiogram import html as aiogram_html # unparse
import html as builtin_html # escape
nickname = "DeadInside <x_x>"
print(
aiogram_html.unparse(nickname),
aiogram_html.unparse(nickname) == builtin_html.escape(nickname),
)
👉 🔗 Перечитати пост про екранування html.
🡇🡇🡇 Опитування нижче 🡇🡇🡇
✴️ Клас для налаштувань за допомогою pydantic_settings
python-dotenv — це добре, але лише тоді, коли налаштувань небагато.
Існує бібліотека pydantic_settings яка використовує pydantic та python-dotenv і дозволяє:
1️⃣ Об'єднати всі змінні в один клас.
2️⃣ Конвертувати та валідувати значення змінних із рядків у потрібні типи.
3️⃣ Підтримувати вкладені класи, що дає змогу згрупувати змінні за призначенням.
4️⃣ Зменшити ймовірність розкриття секретних значень при виводі через print або при логувані
5️⃣ Інші тонкі налаштування.
🔗 Pydantic Settings Documentation
Уявімо ситуацію, коли нам потрібно зберігати список ID адміністраторів бота та налаштування підключення до бази даних. pydantic_settings уміє працювати з базовими типами, включно зі списками та множинами (парсити їх за допомогою бібліотеки json). Також бібліотека надає широкий простір налаштування процесу валідації під конкретні потреби.
Файл .env
Фай main.py
Вивід:
python-dotenv — це добре, але лише тоді, коли налаштувань небагато.
Існує бібліотека pydantic_settings яка використовує pydantic та python-dotenv і дозволяє:
1️⃣ Об'єднати всі змінні в один клас.
2️⃣ Конвертувати та валідувати значення змінних із рядків у потрібні типи.
3️⃣ Підтримувати вкладені класи, що дає змогу згрупувати змінні за призначенням.
4️⃣ Зменшити ймовірність розкриття секретних значень при виводі через print або при логувані
5️⃣ Інші тонкі налаштування.
🔗 Pydantic Settings Documentation
Уявімо ситуацію, коли нам потрібно зберігати список ID адміністраторів бота та налаштування підключення до бази даних. pydantic_settings уміє працювати з базовими типами, включно зі списками та множинами (парсити їх за допомогою бібліотеки json). Також бібліотека надає широкий простір налаштування процесу валідації під конкретні потреби.
Файл .env
BOT__TOKEN="Ваш токен"
BOT__ADMINS=[1361728070, 1060557517]
DB__HOST=localhost
DB__PORT=5432
DB__USER=postgres
DB__PASSWORD=mysecretpassword
DB__NAME=mydatabase
Фай main.py
# pip insatall pydantic
from pydantic import BaseModel, SecretStr
# pip insatall pydantic_settings
from pydantic_settings import BaseSettings
class BotSettings(BaseModel):
token: SecretStr # BOT__TOKEN
admins: frozenset[int] # BOT__ADMINS
class DatabaseSettings(BaseModel):
host: str # DB__HOST
port: int = 5432 # DB__PORT
user: str # DB__USER
password: SecretStr # DB__PASSWORD
name: str # DB__NAME
class Settings(BaseSettings):
bot: BotSettings
db: DatabaseSettings
async def main():
settings = Settings(
_env_file=".env",
_env_nested_delimiter='__'
)
print(repr(settings))
# use: settings.bot.token.get_secret_value()
# for get origin token value
if __name__ == "__main__":
asyncio.run(main())
Вивід:
Settings(bot=BotSettings(token=SecretStr('**********'), admins=frozenset({1060557517, 1361728070})), db=DatabaseSettings(host='localhost', port=5432, user='postgres', password=SecretStr('**********'), name='mydatabase')✳️ Телеграм боти. Dependency Injection
Доопрацюємо нашого бота: зробимо різні привітання для звичайних користувачів та адміністраторів.
ID адміністраторів у нас міститься в об’єкті Settings, і виникає проблема — як його отримати у функції обробника.
Це можна зробити кількома способами:
1️⃣ Створити об’єкт Settings як глобальну змінну.
2️⃣ Використати Middleware для Dependency Injection.
3️⃣ Використати Dispatcher для Dependency Injection.
Dependency Injection (DI) — це підхід у програмуванні, який допомагає відокремити створення сутності від її використання.
Глобальні змінні — це не дуже добре. Використання Middleware — більш складний варіант. Тому давайте зосередимося на останньому варіанті.
🔗 aiogram documentation: Dependency Injection
У Dispatcher можна, як у словник, додати власні об'єкти-залежності, щоб потім отримати їх у обробнику як додаткові параметри:
На жаль, Router такої можливості не має. Якщо потрібно ізолювати дані одного роутера від іншого, варто використати Middleware.
#tg_bots
Доопрацюємо нашого бота: зробимо різні привітання для звичайних користувачів та адміністраторів.
ID адміністраторів у нас міститься в об’єкті Settings, і виникає проблема — як його отримати у функції обробника.
Це можна зробити кількома способами:
1️⃣ Створити об’єкт Settings як глобальну змінну.
2️⃣ Використати Middleware для Dependency Injection.
3️⃣ Використати Dispatcher для Dependency Injection.
Dependency Injection (DI) — це підхід у програмуванні, який допомагає відокремити створення сутності від її використання.
Глобальні змінні — це не дуже добре. Використання Middleware — більш складний варіант. Тому давайте зосередимося на останньому варіанті.
🔗 aiogram documentation: Dependency Injection
async def main():
settings = Settings(
_env_file=".env",
_env_nested_delimiter='__'
)
async with Bot(token=settings.bot.token.get_secret_value()) as bot:
storage = MemoryStorage()
dp = Dispatcher(storage=storage)
dp.startup.register(startup)
dp.include_router(router)
# Use Dependency Injection
dp["settings"] = settings
await dp.start_polling(bot)
У Dispatcher можна, як у словник, додати власні об'єкти-залежності, щоб потім отримати їх у обробнику як додаткові параметри:
@router.message(Command("start"), F.from_user.as_("from_user"))
async def start_handler(message: Message, from_user: User, settings: Settings):
if from_user.id in settings.bot.admins:
text = f"<i>Hello admin</i> {html.unparse(from_user.first_name)}!"
else:
text = f"<i>Hello</i> {html.unparse(from_user.first_name)}!"
await message.answer(text=text, parse_mode=ParseMode.HTML)На жаль, Router такої можливості не має. Якщо потрібно ізолювати дані одного роутера від іншого, варто використати Middleware.
#tg_bots
👍1