diff --git a/CHANGES/1099.doc.rst b/CHANGES/1099.doc.rst new file mode 100644 index 00000000..74064656 --- /dev/null +++ b/CHANGES/1099.doc.rst @@ -0,0 +1 @@ +Added error handling example `examples/error_handling.py` diff --git a/examples/error_handling.py b/examples/error_handling.py new file mode 100644 index 00000000..72ab8e34 --- /dev/null +++ b/examples/error_handling.py @@ -0,0 +1,105 @@ +import asyncio +import html +import logging + +from aiogram import Bot, Dispatcher, types +from aiogram.filters import Command, CommandObject, ExceptionMessageFilter, ExceptionTypeFilter +from aiogram.types import ErrorEvent + +TOKEN = "42:TOKEN" + +dp = Dispatcher() + +logger = logging.getLogger(__name__) + + +class InvalidAge(Exception): + pass + + +class InvalidName(Exception): + def __init__(self, message: str): + super().__init__(message) + + +@dp.errors(ExceptionTypeFilter(InvalidAge)) +async def handle_invalid_age_exception(event: ErrorEvent, bot: Bot) -> None: + """ + This handler receives only error events with `InvalidAge` exception type. + """ + # To get the original event that caused the exception you can use `event.update` property. + # In this case it will be `Message` object. + # To get the exception itself you can use `event.exception` property. + # In this case we filter errors, so we can be sure `event.exception` is an `InvalidAge` object. + assert isinstance(event.exception, InvalidAge) + logger.error("Error caught: %r while processing %r", event.exception, event.update) + + assert event.update.message is not None + chat_id = event.update.message.chat.id + + # Bot instance is passed to the handler as a keyword argument. + # We can use `bot.send_message` method to send a message to the user, logging the error. + text = f"Error caught: {html.escape(repr(event.exception))}" + await bot.send_message(chat_id=chat_id, text=text) + + +@dp.errors(ExceptionMessageFilter("Invalid")) +async def handle_invalid_exceptions(event: ErrorEvent) -> None: + """ + This handler receives error events with "Invalid" message in them. + """ + # Because we specified `ExceptionTypeFilter` with `InvalidAge` exception type earlier, + # this handler will receive error events with any exception type except `InvalidAge` and + # only if the exception message contains "Invalid" substring. + logger.error("Error `Invalid` caught: %r while processing %r", event.exception, event.update) + + +@dp.message(Command(commands=["age"])) +async def handle_set_age(message: types.Message, command: CommandObject) -> None: + """ + This handler receives only messages with `/age` command. + + If the user sends a message with `/age` command, but the age is invalid, + the `InvalidAge` exception will be raised and the `handle_invalid_age_exception` + handler will be called. + """ + # To get the command object you can use `command` keyword argument with `CommandObject` type. + # To get the command arguments you can use `command.args` property. + age = command.args + if not age: + raise InvalidAge("No age provided. Please provide your age as a command argument.") + + # If the age is invalid, raise an exception. + if not age.isdigit(): + raise InvalidAge("Age should be a number") + + # If the age is valid, send a message to the user. + age = int(age) + await message.reply(text=f"Your age is {age}") + + +@dp.message(Command(commands=["name"])) +async def handle_set_name(message: types.Message, command: CommandObject) -> None: + """ + This handler receives only messages with `/name` command. + """ + # To get the command object you can use `command` keyword argument with `CommandObject` type. + # To get the command arguments you can use `command.args` property. + name = command.args + if not name: + raise InvalidName("Invalid name. Please provide your name as a command argument.") + + # If the name is valid, send a message to the user. + await message.reply(text=f"Your name is {name}") + + +async def main() -> None: + # Initialize Bot instance with a default parse mode which will be passed to all API calls + bot = Bot(TOKEN, parse_mode="HTML") + # And the run events dispatching + await dp.start_polling(bot) + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + loop.run_until_complete(main())