Skip to content

Commit 542d34d

Browse files
committed
Exception handlers
1 parent 12f841e commit 542d34d

File tree

8 files changed

+151
-17
lines changed

8 files changed

+151
-17
lines changed

src/telegrambots/custom/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .filters import callback_query as callback_query_filters
55
from .contexts import (
66
MessageContext,
7+
TextMessageContext,
78
CallbackQueryContext,
89
ContinueWithInfo,
910
ContextTemplate,
@@ -29,4 +30,5 @@
2930
"create_message_key",
3031
"create_key",
3132
"KeyBuilder",
33+
"TextMessageContext"
3234
]

src/telegrambots/custom/contexts/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from ._contexts.context_template import Context, ContextTemplate
2-
from ._contexts.message_context import MessageContext
2+
from ._contexts.message_context import MessageContext, TextMessageContext
33
from ._contexts.callback_query_context import CallbackQueryContext
44
from ._contexts._continuously_handler import ContinuouslyHandler, ContinueWithInfo
55

@@ -10,4 +10,5 @@
1010
"CallbackQueryContext",
1111
"ContinuouslyHandler",
1212
"ContinueWithInfo",
13+
"TextMessageContext",
1314
]

src/telegrambots/custom/contexts/_contexts/message_context.py

+15
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,18 @@ async def reply_text(
8585
allow_sending_without_reply=allow_sending_without_reply,
8686
reply_markup=reply_markup,
8787
)
88+
89+
90+
class TextMessageContext(MessageContext):
91+
def __init__(
92+
self, dp: "Dispatcher", update: Update, handler_tag: str, **kwargs: Any
93+
) -> None:
94+
super().__init__(dp, update=update, handler_tag=handler_tag, **kwargs)
95+
96+
@final
97+
@property
98+
def text(self) -> str:
99+
"""Returns message text."""
100+
if self.update.text is not None:
101+
return self.update.text
102+
raise ValueError("Update has no message")

src/telegrambots/custom/dispatcher.py

+16-8
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
import logging
33
from typing import (
44
Any,
5-
Callable,
6-
Coroutine,
75
Mapping,
86
Optional,
97
cast,
@@ -20,6 +18,7 @@
2018
from .handlers._handlers.handler_template import HandlerTemplate
2119
from .processor import ProcessorTemplate, SequentialProcessor
2220
from .extensions.dispatcher import AddExtensions
21+
from .handlers import AbstractExceptionHandler, default_exception_handler
2322

2423
if TYPE_CHECKING:
2524
from .client import TelegramBot
@@ -38,9 +37,6 @@ def __init__(
3837
self,
3938
_bot: "TelegramBot",
4039
*,
41-
handle_error: Optional[
42-
Callable[["TelegramBot", Exception], Coroutine[Any, Any, None]]
43-
] = None,
4440
processor_type: Optional[type[ProcessorTemplate[Update]]] = None,
4541
) -> None:
4642

@@ -54,7 +50,7 @@ def __init__(
5450
self._bot = _bot
5551
self._handlers: dict[type[Any], dict[str, HandlerTemplate]] = {}
5652
self._continuously_handlers: list[tuple[ContinuouslyHandlerTemplate]] = []
57-
self._handle_error = handle_error
53+
self._handle_errors: list[AbstractExceptionHandler] = []
5854

5955
self._processor: ProcessorTemplate[Update]
6056
if processor_type is None:
@@ -124,6 +120,18 @@ def add_handler(self, tag: str, handler: HandlerTemplate):
124120
self._handlers[handler.update_type][tag] = handler
125121
dispatcher_logger.info(f"Added handler {handler.update_type.__name__}:{tag}")
126122

123+
def add_exception_handler(self, exception_handler: AbstractExceptionHandler):
124+
"""Adds an exception handler to the dispatcher.
125+
126+
Args:
127+
exception_handler (`AbstractExceptionHandler`): The exception handler to add.
128+
"""
129+
self._handle_errors.append(exception_handler)
130+
131+
def add_default_exception_handler(self):
132+
"""Adds the default exception handler to the dispatcher."""
133+
self.add_exception_handler(default_exception_handler)
134+
127135
@overload
128136
def add_continuously_handler(
129137
self,
@@ -251,5 +259,5 @@ async def _do_handling(
251259
return None
252260

253261
async def _try_handle_error(self, e: Exception):
254-
if self._handle_error is not None:
255-
await self._handle_error(self._bot, e)
262+
for handler in self._handle_errors:
263+
await handler.try_handle(self, e)

src/telegrambots/custom/extensions/context.py

+56-7
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
CallbackQueryMessageId,
2525
CallbackQuerySenderId,
2626
)
27-
from ..filters import Filter
27+
from ..filters import Filter, messages as mf
2828

2929
if TYPE_CHECKING:
3030
from .. import ContextTemplate
@@ -63,6 +63,7 @@ def callback_query(
6363
keys: Sequence[AbstractKeyResolver[CallbackQuery, Any]],
6464
filter: Filter[CallbackQuery],
6565
tag: Optional[str] = None,
66+
other_continue_with: Optional[list[str]] = None,
6667
*args: Any,
6768
**kwargs: Any,
6869
):
@@ -72,7 +73,10 @@ def decorator(
7273
_tag = tag or _function.__name__
7374
if not self._context.dp.handler_tag_exists(_tag, CallbackQuery):
7475
self._context.dp.add.handlers.callback_query(
75-
_tag, _function, filter, [self._context.handler_tag]
76+
_tag,
77+
_function,
78+
filter,
79+
[self._context.handler_tag] + (other_continue_with or []),
7680
)
7781
self._context.continue_with.callback_query(_tag, keys, *args, **kwargs)
7882

@@ -83,6 +87,7 @@ def callback_query_form(
8387
user_id: int,
8488
filter: Filter[CallbackQuery],
8589
tag: Optional[str] = None,
90+
other_continue_with: Optional[list[str]] = None,
8691
*args: Any,
8792
**kwargs: Any,
8893
):
@@ -92,7 +97,10 @@ def decorator(
9297
_tag = tag or _function.__name__
9398
if not self._context.dp.handler_tag_exists(_tag, CallbackQuery):
9499
self._context.dp.add.handlers.callback_query(
95-
_tag, _function, filter, [self._context.handler_tag]
100+
_tag,
101+
_function,
102+
filter,
103+
[self._context.handler_tag] + (other_continue_with or []),
96104
)
97105
self._context.continue_with.callback_query(
98106
_tag, [CallbackQuerySenderId(user_id)], *args, **kwargs
@@ -106,6 +114,7 @@ def callback_query_same_message_form(
106114
user_id: int,
107115
filter: Filter[CallbackQuery],
108116
tag: Optional[str] = None,
117+
other_continue_with: Optional[list[str]] = None,
109118
*args: Any,
110119
**kwargs: Any,
111120
):
@@ -115,7 +124,10 @@ def decorator(
115124
_tag = tag or _function.__name__
116125
if not self._context.dp.handler_tag_exists(_tag, CallbackQuery):
117126
self._context.dp.add.handlers.callback_query(
118-
_tag, _function, filter, [self._context.handler_tag]
127+
_tag,
128+
_function,
129+
filter,
130+
[self._context.handler_tag] + (other_continue_with or []),
119131
)
120132
self._context.continue_with.callback_query(
121133
_tag,
@@ -131,6 +143,7 @@ def callback_query_same_message(
131143
message_id: int,
132144
filter: Filter[CallbackQuery],
133145
tag: Optional[str] = None,
146+
other_continue_with: Optional[list[str]] = None,
134147
*args: Any,
135148
**kwargs: Any,
136149
):
@@ -140,7 +153,10 @@ def decorator(
140153
_tag = tag or _function.__name__
141154
if not self._context.dp.handler_tag_exists(_tag, CallbackQuery):
142155
self._context.dp.add.handlers.callback_query(
143-
_tag, _function, filter, [self._context.handler_tag]
156+
_tag,
157+
_function,
158+
filter,
159+
[self._context.handler_tag] + (other_continue_with or []),
144160
)
145161
self._context.continue_with.callback_query(
146162
_tag,
@@ -156,6 +172,7 @@ def message(
156172
keys: list[AbstractKeyResolver[Message, Any]],
157173
filter: "Filter[Message]",
158174
tag: Optional[str] = None,
175+
other_continue_with: Optional[list[str]] = None,
159176
*args: Any,
160177
**kwargs: Any,
161178
):
@@ -165,7 +182,10 @@ def decorator(
165182
_tag = tag or _function.__name__
166183
if not self._context.dp.handler_tag_exists(_tag, Message):
167184
self._context.dp.add.handlers.message(
168-
_tag, _function, filter, [self._context.handler_tag]
185+
_tag,
186+
_function,
187+
filter,
188+
[self._context.handler_tag] + (other_continue_with or []),
169189
)
170190
self._context.continue_with.message(_tag, keys, *args, **kwargs)
171191

@@ -176,6 +196,7 @@ def message_from(
176196
user_id: int,
177197
filter: "Filter[Message]",
178198
tag: Optional[str] = None,
199+
other_continue_with: Optional[list[str]] = None,
179200
*args: Any,
180201
**kwargs: Any,
181202
):
@@ -185,7 +206,35 @@ def decorator(
185206
_tag = tag or _function.__name__
186207
if not self._context.dp.handler_tag_exists(_tag, Message):
187208
self._context.dp.add.handlers.message(
188-
_tag, _function, filter, [self._context.handler_tag]
209+
_tag,
210+
_function,
211+
filter,
212+
[self._context.handler_tag] + (other_continue_with or []),
213+
)
214+
self._context.continue_with.message(
215+
_tag, [MessageSenderId(user_id)], *args, **kwargs
216+
)
217+
218+
return decorator
219+
220+
def text_input_from(
221+
self,
222+
user_id: int,
223+
tag: Optional[str] = None,
224+
other_continue_with: Optional[list[str]] = None,
225+
*args: Any,
226+
**kwargs: Any,
227+
):
228+
def decorator(
229+
_function: Callable[["MessageContext"], Coroutine[Any, Any, None]]
230+
):
231+
_tag = tag or _function.__name__
232+
if not self._context.dp.handler_tag_exists(_tag, Message):
233+
self._context.dp.add.handlers.message(
234+
_tag,
235+
_function, # type: ignore
236+
mf.text_message,
237+
[self._context.handler_tag] + (other_continue_with or []),
189238
)
190239
self._context.continue_with.message(
191240
_tag, [MessageSenderId(user_id)], *args, **kwargs
+13-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
from ._handlers.handler_template import Handler
22
from ._handlers.update_handler import CallbackQueryHandler, MessageHandler
3+
from .exceptions.exception_handler import (
4+
AbstractExceptionHandler,
5+
ExceptionHandler,
6+
default_exception_handler,
7+
)
38

49

5-
__all__ = ["Handler", "CallbackQueryHandler", "MessageHandler"]
10+
__all__ = [
11+
"Handler",
12+
"CallbackQueryHandler",
13+
"MessageHandler",
14+
"AbstractExceptionHandler",
15+
"ExceptionHandler",
16+
"default_exception_handler",
17+
]

src/telegrambots/custom/handlers/exceptions/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from abc import ABC, abstractmethod
2+
from typing import TYPE_CHECKING, Any, Callable, Coroutine
3+
4+
if TYPE_CHECKING:
5+
from ...dispatcher import Dispatcher
6+
7+
8+
class AbstractExceptionHandler(ABC):
9+
def __init__(self, type: type[Exception], exact_exception: bool = False) -> None:
10+
super().__init__()
11+
self._exact = exact_exception
12+
self._type = type
13+
14+
@abstractmethod
15+
async def _handle_exception(self, dp: "Dispatcher", e: Exception):
16+
...
17+
18+
def should_handle(self, e: Exception):
19+
if self._exact:
20+
return isinstance(e, self._type)
21+
else:
22+
return issubclass(type(e), self._type)
23+
24+
async def try_handle(self, dp: "Dispatcher", e: Exception):
25+
if self.should_handle(e):
26+
await self._handle_exception(dp, e)
27+
28+
29+
class ExceptionHandler(AbstractExceptionHandler):
30+
def __init__(
31+
self,
32+
handle: Callable[["Dispatcher", Exception], Coroutine[Any, Any, None]],
33+
type: type[Exception],
34+
exact_exception: bool = False,
35+
) -> None:
36+
super().__init__(type, exact_exception)
37+
self._handle = handle
38+
39+
async def _handle_exception(self, dp: "Dispatcher", e: Exception):
40+
return await self._handle(dp, e)
41+
42+
43+
async def __handle_exception(dp: "Dispatcher", e: Exception):
44+
print(e)
45+
46+
47+
default_exception_handler = ExceptionHandler(__handle_exception, Exception)

0 commit comments

Comments
 (0)