diff --git a/discord/ui/view.py b/discord/ui/view.py index 4868196d2..38ca97639 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -27,6 +27,7 @@ from typing import Any, Callable, ClassVar, Dict, Iterator, List, Optional, Sequ from functools import partial from itertools import groupby +import traceback import asyncio import sys import time @@ -252,7 +253,8 @@ class View: .. note:: If an exception occurs within the body then the interaction - check is considered failed. + check then :meth:`on_error` is called and it is considered + a failure. Parameters ----------- @@ -273,18 +275,37 @@ class View: """ pass + async def on_error(self, error: Exception, item: Item, interaction: Interaction) -> None: + """|coro| + + A callback that is called when an item's callback or :meth:`interaction_check` + fails with an error. + + The default implementation prints the traceback to stderr. + + Parameters + ----------- + error: :class:`Exception` + The exception that was raised. + item: :class:`Item` + The item that failed the dispatch. + interaction: :class:`~discord.Interaction` + The interaction that led to the failure. + """ + print(f'Ignoring exception in view {self} for item {item}:', file=sys.stderr) + traceback.print_exception(error.__class__, error, error.__traceback__, file=sys.stderr) + async def _scheduled_task(self, state: Any, item: Item, interaction: Interaction): try: allow = await self.interaction_check(interaction) - except Exception: - allow = False - - if not allow: - return - - await item.callback(interaction) - if not interaction.response._responded: - await interaction.response.defer() + if not allow: + return + + await item.callback(interaction) + if not interaction.response._responded: + await interaction.response.defer() + except Exception as e: + return await self.on_error(e, item, interaction) def _start_listening(self, store: ViewStore) -> None: self._cancel_callback = partial(store.remove_view)