From aa0c29e1d96843e504e369a38e8a5244bd076f59 Mon Sep 17 00:00:00 2001 From: lordcodes <83734728+LORD-ME-CODE@users.noreply.github.com> Date: Wed, 22 Mar 2023 08:27:44 +0200 Subject: [PATCH 1/3] feat: error handlers --- hydrogram/dispatcher.py | 48 ++++++++++--- hydrogram/handlers/__init__.py | 2 + hydrogram/handlers/error_handler.py | 67 +++++++++++++++++++ hydrogram/methods/decorators/__init__.py | 4 +- hydrogram/methods/decorators/on_error.py | 49 ++++++++++++++ hydrogram/methods/utilities/__init__.py | 2 + .../methods/utilities/remove_error_handler.py | 39 +++++++++++ 7 files changed, 202 insertions(+), 9 deletions(-) create mode 100644 hydrogram/handlers/error_handler.py create mode 100644 hydrogram/methods/decorators/on_error.py create mode 100644 hydrogram/methods/utilities/remove_error_handler.py diff --git a/hydrogram/dispatcher.py b/hydrogram/dispatcher.py index 231250d3e..7cb04989d 100644 --- a/hydrogram/dispatcher.py +++ b/hydrogram/dispatcher.py @@ -31,6 +31,7 @@ ChosenInlineResultHandler, DeletedMessagesHandler, EditedMessageHandler, + ErrorHandler, InlineQueryHandler, MessageHandler, PollHandler, @@ -79,6 +80,7 @@ def __init__(self, client: "hydrogram.Client"): self.updates_queue = asyncio.Queue() self.groups = OrderedDict() self._init_update_parsers() + self.error_handlers = [] def _init_update_parsers(self): self.update_parsers = { @@ -160,24 +162,38 @@ async def stop(self): await asyncio.gather(*self.handler_worker_tasks) self.handler_worker_tasks.clear() self.groups.clear() + self.error_handlers.clear() + log.info("Stopped %s HandlerTasks", self.client.workers) def add_handler(self, handler, group: int): async def fn(): async with asyncio.Lock(): - if group not in self.groups: - self.groups[group] = [] - self.groups = OrderedDict(sorted(self.groups.items())) - self.groups[group].append(handler) + if isinstance(handler, ErrorHandler): + if handler not in self.error_handlers: + self.error_handlers.append(handler) + else: + if group not in self.groups: + self.groups[group] = [] + self.groups = OrderedDict(sorted(self.groups.items())) + self.groups[group].append(handler) self.loop.create_task(fn()) def remove_handler(self, handler, group: int): async def fn(): async with asyncio.Lock(): - if group not in self.groups: - raise ValueError(f"Group {group} does not exist. Handler was not removed.") - self.groups[group].remove(handler) + if isinstance(handler, ErrorHandler): + if handler not in self.error_handlers: + raise ValueError( + f"Error handler {handler} does not exist. Handler was not removed." + ) + + self.error_handlers.remove(handler) + else: + if group not in self.groups: + raise ValueError(f"Group {group} does not exist. Handler was not removed.") + self.groups[group].remove(handler) self.loop.create_task(fn()) @@ -225,7 +241,23 @@ async def _handle_update(self, handler, handler_type, parsed_update, update, use except hydrogram.ContinuePropagation: pass except Exception as e: - log.exception(e) + handled_error = False + for error_handler in self.error_handlers: + try: + if await error_handler.check(self.client, e): + await error_handler.callback(self.client, e) + handled_error = True + break + except hydrogram.StopPropagation: + raise + except hydrogram.ContinuePropagation: + continue + except Exception as e: + log.exception(e) + continue + + if not handled_error: + log.exception(e) async def _execute_callback(self, handler, *args): if inspect.iscoroutinefunction(handler.callback): diff --git a/hydrogram/handlers/__init__.py b/hydrogram/handlers/__init__.py index 8c3f5f944..ed9cd02ed 100644 --- a/hydrogram/handlers/__init__.py +++ b/hydrogram/handlers/__init__.py @@ -24,6 +24,7 @@ from .deleted_messages_handler import DeletedMessagesHandler from .disconnect_handler import DisconnectHandler from .edited_message_handler import EditedMessageHandler +from .error_handler import ErrorHandler from .inline_query_handler import InlineQueryHandler from .message_handler import MessageHandler from .poll_handler import PollHandler @@ -38,6 +39,7 @@ "DeletedMessagesHandler", "DisconnectHandler", "EditedMessageHandler", + "ErrorHandler", "InlineQueryHandler", "MessageHandler", "PollHandler", diff --git a/hydrogram/handlers/error_handler.py b/hydrogram/handlers/error_handler.py new file mode 100644 index 000000000..a479497ef --- /dev/null +++ b/hydrogram/handlers/error_handler.py @@ -0,0 +1,67 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2023-present Hydrogram +# +# This file is part of Hydrogram. +# +# Hydrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Hydrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Hydrogram. If not, see . + +from typing import TYPE_CHECKING, Callable + +from .handler import Handler + +if TYPE_CHECKING: + import hydrogram + + +class ErrorHandler(Handler): + """The Error handler class. Used to handle errors. + It is intended to be used with :meth:`~hydrogram.Client.add_handler` + + For a nicer way to register this handler, have a look at the + :meth:`~hydrogram.Client.on_message` decorator. + + Parameters: + callback (``Callable``): + Pass a function that will be called when a new Error arrives. It takes *(client, error)* + as positional arguments (look at the section below for a detailed description). + + errors (:obj:`Exception` | List of :obj:`Exception`): + Pass one or more exception classes to allow only a subset of errors to be passed + in your callback function. + + Other parameters: + client (:obj:`~hydrogram.Client`): + The Client itself, useful when you want to call other API methods inside the message handler. + + error (:obj:`~Exception`): + The error that was raised. + + update (:obj:`~hydrogram.Update`): + The update that caused the error. + """ + + def __init__(self, callback: Callable, errors=None): + if errors is None: + errors = [Exception] + elif not isinstance(errors, list): + errors = [errors] + + self.errors = errors + super().__init__(callback) + + async def check(self, client: "hydrogram.Client", error: Exception): + return any(isinstance(error, e) for e in self.errors) + + def check_remove(self, error: Exception): + return self.errors == error or any(isinstance(error, e) for e in self.errors) diff --git a/hydrogram/methods/decorators/__init__.py b/hydrogram/methods/decorators/__init__.py index 91c050c80..031af64e6 100644 --- a/hydrogram/methods/decorators/__init__.py +++ b/hydrogram/methods/decorators/__init__.py @@ -24,6 +24,7 @@ from .on_deleted_messages import OnDeletedMessages from .on_disconnect import OnDisconnect from .on_edited_message import OnEditedMessage +from .on_error import OnError from .on_inline_query import OnInlineQuery from .on_message import OnMessage from .on_poll import OnPoll @@ -31,7 +32,7 @@ from .on_user_status import OnUserStatus -class Decorators( +class Decorators( # noqa: N818 false-positive OnMessage, OnEditedMessage, OnDeletedMessages, @@ -44,5 +45,6 @@ class Decorators( OnChosenInlineResult, OnChatMemberUpdated, OnChatJoinRequest, + OnError, ): pass diff --git a/hydrogram/methods/decorators/on_error.py b/hydrogram/methods/decorators/on_error.py new file mode 100644 index 000000000..eb3c2dfd4 --- /dev/null +++ b/hydrogram/methods/decorators/on_error.py @@ -0,0 +1,49 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2023-present Hydrogram +# +# This file is part of Hydrogram. +# +# Hydrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Hydrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Hydrogram. If not, see . + +from typing import Callable + +import hydrogram +from hydrogram.filters import Filter + + +class OnError: + def on_error(self=None, errors=None) -> Callable: + """Decorator for handling new errors. + + This does the same thing as :meth:`~hydrogram.Client.add_handler` using the + :obj:`~hydrogram.handlers.MessageHandler`. + + Parameters: + errors (:obj:`~Exception`, *optional*): + Pass one or more errors to allow only a subset of errors to be passed + in your function. + """ + + def decorator(func: Callable) -> Callable: + if isinstance(self, hydrogram.Client): + self.add_handler(hydrogram.handlers.ErrorHandler(func, errors), 0) + elif isinstance(self, Filter) or self is None: + if not hasattr(func, "handlers"): + func.handlers = [] + + func.handlers.append((hydrogram.handlers.ErrorHandler(func, self), 0)) + + return func + + return decorator diff --git a/hydrogram/methods/utilities/__init__.py b/hydrogram/methods/utilities/__init__.py index 0e9e551eb..74da4a3db 100644 --- a/hydrogram/methods/utilities/__init__.py +++ b/hydrogram/methods/utilities/__init__.py @@ -19,6 +19,7 @@ from .add_handler import AddHandler from .export_session_string import ExportSessionString +from .remove_error_handler import RemoveErrorHandler from .remove_handler import RemoveHandler from .restart import Restart from .run import Run @@ -31,6 +32,7 @@ class Utilities( AddHandler, ExportSessionString, RemoveHandler, + RemoveErrorHandler, Restart, Run, Start, diff --git a/hydrogram/methods/utilities/remove_error_handler.py b/hydrogram/methods/utilities/remove_error_handler.py new file mode 100644 index 000000000..6680a8eeb --- /dev/null +++ b/hydrogram/methods/utilities/remove_error_handler.py @@ -0,0 +1,39 @@ +# Hydrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2023-present Hydrogram +# +# This file is part of Hydrogram. +# +# Hydrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Hydrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Hydrogram. If not, see . + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import hydrogram + + +class RemoveErrorHandler: + def remove_error_handler( + self: hydrogram.Client, error: Exception | tuple[Exception] = Exception + ): + """Remove a previously-registered error handler. (using exception classes) + + Parameters: + error (``Exception``): + The error(s) for handlers to be removed. + """ + for handler in self.dispatcher.error_handlers: + if handler.check_remove(error): + self.dispatcher.error_handlers.remove(handler) From 4ef5aa2cb20bb349c2e748036d2870546966541b Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Sun, 30 Jun 2024 16:57:34 -0300 Subject: [PATCH 2/3] doc: add news fragment and index ErrorHandler in the docs --- docs/source/api/handlers.rst | 2 ++ news/38.feature.rst | 1 + 2 files changed, 3 insertions(+) create mode 100644 news/38.feature.rst diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst index db66d845a..9b91bf0f9 100644 --- a/docs/source/api/handlers.rst +++ b/docs/source/api/handlers.rst @@ -41,6 +41,7 @@ Index - :class:`PollHandler` - :class:`DisconnectHandler` - :class:`RawUpdateHandler` + - :class:`ErrorHandler` ----- @@ -59,3 +60,4 @@ Details .. autoclass:: PollHandler() .. autoclass:: DisconnectHandler() .. autoclass:: RawUpdateHandler() +.. autoclass:: ErrorHandler() diff --git a/news/38.feature.rst b/news/38.feature.rst new file mode 100644 index 000000000..c01696304 --- /dev/null +++ b/news/38.feature.rst @@ -0,0 +1 @@ +Added support for error handlers. From 2b4d6bcdb7233646ba8e9007946092db1e499b86 Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Sun, 30 Jun 2024 17:22:16 -0300 Subject: [PATCH 3/3] chore(error-handler): some doc and typing hints improvements --- hydrogram/handlers/error_handler.py | 19 ++++++++++++------- .../methods/utilities/remove_error_handler.py | 6 ++++-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/hydrogram/handlers/error_handler.py b/hydrogram/handlers/error_handler.py index a479497ef..026ecbafc 100644 --- a/hydrogram/handlers/error_handler.py +++ b/hydrogram/handlers/error_handler.py @@ -16,6 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Hydrogram. If not, see . +from __future__ import annotations + +from collections.abc import Iterable from typing import TYPE_CHECKING, Callable from .handler import Handler @@ -29,38 +32,40 @@ class ErrorHandler(Handler): It is intended to be used with :meth:`~hydrogram.Client.add_handler` For a nicer way to register this handler, have a look at the - :meth:`~hydrogram.Client.on_message` decorator. + :meth:`~hydrogram.Client.on_error` decorator. Parameters: callback (``Callable``): Pass a function that will be called when a new Error arrives. It takes *(client, error)* as positional arguments (look at the section below for a detailed description). - errors (:obj:`Exception` | List of :obj:`Exception`): + errors (``Exception`` | Iterable of ``Exception``, *optional*): Pass one or more exception classes to allow only a subset of errors to be passed in your callback function. Other parameters: client (:obj:`~hydrogram.Client`): - The Client itself, useful when you want to call other API methods inside the message handler. + The Client itself, useful when you want to call other API methods inside the error handler. - error (:obj:`~Exception`): + error (``Exception``): The error that was raised. update (:obj:`~hydrogram.Update`): The update that caused the error. """ - def __init__(self, callback: Callable, errors=None): + def __init__( + self, callback: Callable, errors: type[Exception] | Iterable[type[Exception]] | None = None + ): if errors is None: errors = [Exception] - elif not isinstance(errors, list): + elif not isinstance(errors, Iterable): errors = [errors] self.errors = errors super().__init__(callback) - async def check(self, client: "hydrogram.Client", error: Exception): + async def check(self, client: hydrogram.Client, error: Exception): return any(isinstance(error, e) for e in self.errors) def check_remove(self, error: Exception): diff --git a/hydrogram/methods/utilities/remove_error_handler.py b/hydrogram/methods/utilities/remove_error_handler.py index 6680a8eeb..e70ee17d1 100644 --- a/hydrogram/methods/utilities/remove_error_handler.py +++ b/hydrogram/methods/utilities/remove_error_handler.py @@ -21,17 +21,19 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: + from collections.abc import Iterable + import hydrogram class RemoveErrorHandler: def remove_error_handler( - self: hydrogram.Client, error: Exception | tuple[Exception] = Exception + self: hydrogram.Client, error: type[Exception] | Iterable[type[Exception]] = Exception ): """Remove a previously-registered error handler. (using exception classes) Parameters: - error (``Exception``): + error (``Exception`` | Iterable of ``Exception``, *optional*): The error(s) for handlers to be removed. """ for handler in self.dispatcher.error_handlers: