Magic filter F
F — встроенный декларативный фильтр aiomax2.
Он использует тот же механизм фильтрации, что Filter, Command и FSM state,
но позволяет описывать простые условия прямо при регистрации handler без
создания отдельного класса.
Например:
from aiomax2 import F
@router.message(F.text == "hello")
async def hello(message: Message) -> None:
await message.answer("Привет")
Handler будет вызван только в том случае, если message.text == "hello".
Для аналогичной проверки через собственный фильтр пришлось бы написать:
from aiomax2.filters import Filter
from aiomax2.types import Message
class HelloFilter(Filter):
async def __call__(self, message: Message) -> bool:
return message.text == "hello"
Для большинства простых проверок F позволяет избежать таких небольших
одноразовых классов.
Подробнее об общей системе фильтрации: Фильтры.
Как работает F
F не содержит заранее определённого списка полей.
Когда записывается выражение:
aiomax2 строит путь:
Само значение будет получено только тогда, когда фильтр будет применён к реальному событию.
Поэтому выражение:
можно воспринимать примерно как:
но с безопасной обработкой отсутствующих значений.
Простая проверка значения
Если не использовать оператор сравнения, значение проверяется на truthiness:
Такой handler подходит только для сообщений, у которых text содержит
непустое значение.
Например:
Это удобно для проверки наличия значения:
Доступ к вложенным полям
Можно обращаться к вложенным атрибутам события:
Например:
ADMIN_ID = 123456
@router.message(F.sender.user_id == ADMIN_ID)
async def admin(message: Message) -> None:
await message.answer("Привет, администратор")
Для Message также доступны удобные свойства верхнего уровня:
Поэтому вместо:
обычно удобнее писать:
Отсутствующие поля
Magic filter безопасно обрабатывает отсутствующие поля.
Например:
не приведёт к исключению, если sender отсутствует.
В таком случае выражение просто не пройдёт фильтрацию.
То же относится к несовместимым операциям. Например, если строковая операция применяется к значению неподходящего типа, handler не будет выбран вместо падения dispatcher.
Сравнения
Поддерживаются обычные операторы сравнения:
F.user_id == 123
F.user_id != 123
F.timestamp > 1000
F.timestamp >= 1000
F.timestamp < 2000
F.timestamp <= 2000
Например:
Работа со строками
startswith
Проверяет начало строки:
Пример:
endswith
Проверяет конец строки:
Пример:
contains
Проверяет наличие значения внутри объекта:
Для строки это соответствует:
Например:
contains() может использоваться и с другими объектами, поддерживающими
оператор in.
Проверка в наборе значений
Метод in_() проверяет, содержится ли значение поля в переданном контейнере:
Например:
ADMIN_IDS = {100, 200, 300}
@router.message(F.user_id.in_(ADMIN_IDS))
async def admin(message: Message) -> None: ...
Это соответствует проверке:
Преобразование регистра
lower
Преобразует строку в нижний регистр перед дальнейшей проверкой:
Теперь подойдут, например:
Пример:
upper
Аналогично преобразует строку в верхний регистр:
Преобразования выполняются до проверки условия.
Их можно комбинировать с другими возможностями F.
Длина значения
Метод len() применяет встроенный Python len() к найденному значению:
Например:
Если значение не поддерживает len(), фильтр просто не пройдёт.
Регулярные выражения
Для поиска по регулярному выражению используется regexp():
Внутри используется поиск через re.search().
Например:
Подойдёт сообщение:
и не подойдёт:
В regexp() можно передать как строку:
так и заранее скомпилированный pattern:
Передача найденного значения через as_()
Magic filter может добавить найденное значение в context.
Для этого используется as_():
После успешной проверки значение станет доступно handler:
@router.message(F.user_id.as_("sender_id"))
async def handler(
message: Message,
sender_id: int,
) -> None:
print(sender_id)
Это позволяет передавать значения в handler без отдельного класса Filter.
regexp() вместе с as_()
При использовании regexp() в context передаётся объект re.Match.
Например:
import re
from aiomax2 import F
from aiomax2.types import Message
@router.message(F.text.regexp(r"^order:(\d+)$").as_("match"))
async def order(
message: Message,
match: re.Match[str],
) -> None:
order_id = int(match.group(1))
await message.answer(f"Номер заказа: {order_id}")
Для сообщения:
значение:
будет равно:
Несколько условий через &
Оператор & объединяет два выражения как логическое AND.
Например:
@router.message((F.text.startswith("hello")) & (F.chat_id == 100))
async def hello(message: Message) -> None: ...
Оба условия должны быть истинными:
Если первое условие не проходит, второе уже не требуется для успешного результата.
Несколько вариантов через |
Оператор | работает как логическое OR.
Например:
@router.message((F.text == "yes") | (F.text == "да"))
async def confirm(message: Message) -> None: ...
Handler будет вызван, если выполняется хотя бы одно условие.
Скобки при использовании & и |
При композиции magic filters рекомендуется всегда заключать отдельные выражения в скобки:
и:
Это делает порядок вычисления очевидным и предотвращает проблемы с приоритетом операторов Python.
Не следует писать:
Не используйте Python and и or
Magic filter нельзя объединять обычными Python-операторами and и or:
Magic filter специально запрещает преобразование выражения в обычный
Python bool.
Используйте:
или:
as_() и составные выражения
На текущем этапе значения из as_() лучше не захватывать внутри выражения,
объединённого через & или |.
Вместо:
передайте условия как отдельные фильтры:
@router.message(
F.text.regexp(r"^order:(\d+)$").as_("match"),
F.chat_id == 100,
)
async def order(
message: Message,
match: re.Match[str],
) -> None: ...
Это также хорошо показывает разницу между двумя механизмами:
на уровне handler и:
внутри одного magic filter.
Несколько magic filters у одного handler
Часто отдельные условия удобнее передавать независимо:
@router.message(
F.text,
F.sender.user_id == ADMIN_ID,
F.text.startswith("/"),
)
async def handler(message: Message) -> None: ...
Как и любые другие filters, они выполняются последовательно и работают как
логическое AND.
Это особенно удобно, если один из фильтров использует as_().
Callback query
F работает не только с сообщениями.
Для callback query можно фильтровать payload:
@router.callback_query(F.payload == "confirm")
async def confirm(callback: CallbackQuery) -> None:
await callback.answer()
Или использовать другие поля callback:
Путь всегда строится относительно объекта события, который получает конкретный observer.
Примеры
Точное совпадение:
Начало строки:
Регистронезависимая проверка:
Проверка пользователя:
Несколько разрешённых пользователей:
Проверка текста и пользователя:
@router.message((F.text == "admin") & (F.user_id == ADMIN_ID))
async def admin_command(message: Message) -> None: ...
Регулярное выражение с извлечением значения:
@router.message(F.text.regexp(r"^order:(\d+)$").as_("match"))
async def order(
message: Message,
match: re.Match[str],
) -> None:
order_id = int(match.group(1))
Callback:
@router.callback_query(F.payload == "confirm")
async def confirm(callback: CallbackQuery) -> None: ...
Поддерживаемые операции
| Возможность | Пример |
|---|---|
| Проверка значения | F.text |
| Равенство | F.text == "hello" |
| Неравенство | F.text != "hello" |
| Больше | F.timestamp > 1000 |
| Больше или равно | F.timestamp >= 1000 |
| Меньше | F.timestamp < 2000 |
| Меньше или равно | F.timestamp <= 2000 |
| Начало строки | F.text.startswith("hello") |
| Конец строки | F.text.endswith("!") |
| Содержит значение | F.text.contains("max") |
| Значение входит в набор | F.user_id.in_({1, 2, 3}) |
| Нижний регистр | F.text.lower() |
| Верхний регистр | F.text.upper() |
| Длина | F.text.len() |
| Регулярное выражение | F.text.regexp(r"\d+") |
| Логическое AND | (F.text) & (F.user_id == 1) |
| Логическое OR | (F.text == "yes") \| (F.text == "да") |
| Передача в context | F.user_id.as_("sender_id") |
Когда использовать F
Magic filter хорошо подходит для компактных декларативных проверок:
Если фильтрация содержит сложную бизнес-логику, обращения к внешним сервисам,
несколько вычислений или должна переиспользоваться как самостоятельный
компонент, лучше создать собственный класс Filter.
Подробнее: Фильтры.