From 30aaf8d0f10377978dce33fa6e050dc717121eb2 Mon Sep 17 00:00:00 2001 From: byehack Date: Sun, 2 Oct 2022 03:27:06 +0330 Subject: [PATCH 1/8] Support ContinueHandling --- telebot/__init__.py | 17 +++++++++++------ telebot/async_telebot.py | 15 +++++++-------- telebot/handler_backends.py | 14 ++++++-------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 12ecf73..72d244c 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -37,7 +37,10 @@ logger.addHandler(console_output_handler) logger.setLevel(logging.ERROR) from telebot import apihelper, util, types -from telebot.handler_backends import HandlerBackend, MemoryHandlerBackend, FileHandlerBackend, BaseMiddleware, CancelUpdate, SkipHandler, State +from telebot.handler_backends import ( + HandlerBackend, MemoryHandlerBackend, FileHandlerBackend, BaseMiddleware, + CancelUpdate, SkipHandler, State, ContinueHandling +) from telebot.custom_filters import SimpleCustomFilter, AdvancedCustomFilter @@ -6111,13 +6114,14 @@ class TeleBot: if not process_handler: continue for i in inspect.signature(handler['function']).parameters: params.append(i) + result = None if len(params) == 1: - handler['function'](message) + result = handler['function'](message) elif "data" in params: if len(params) == 2: - handler['function'](message, data) + result = handler['function'](message, data) elif len(params) == 3: - handler['function'](message, data=data, bot=self) + result = handler['function'](message, data=data, bot=self) else: logger.error("It is not allowed to pass data and values inside data to the handler. Check your handler: {}".format(handler['function'])) return @@ -6132,8 +6136,9 @@ class TeleBot: if len(data_copy) > len(params) - 1: # remove the message parameter logger.error("You are passing more parameters than the handler needs. Check your handler: {}".format(handler['function'])) return - handler["function"](message, **data_copy) - break + result = handler["function"](message, **data_copy) + if not isinstance(result, ContinueHandling): + break except Exception as e: handler_error = e if self.exception_handler: diff --git a/telebot/async_telebot.py b/telebot/async_telebot.py index 0aa1097..d2552b7 100644 --- a/telebot/async_telebot.py +++ b/telebot/async_telebot.py @@ -14,7 +14,7 @@ import telebot.types # storages from telebot.asyncio_storage import StateMemoryStorage, StatePickleStorage, StateStorageBase -from telebot.asyncio_handler_backends import BaseMiddleware, CancelUpdate, SkipHandler, State +from telebot.asyncio_handler_backends import BaseMiddleware, CancelUpdate, SkipHandler, State, ContinueHandling from inspect import signature @@ -493,16 +493,14 @@ class AsyncTeleBot: if not process_update: continue for i in signature(handler['function']).parameters: params.append(i) + result = None if len(params) == 1: - await handler['function'](message) - break + result = await handler['function'](message) elif "data" in params: if len(params) == 2: - await handler['function'](message, data) - break + result = await handler['function'](message, data) elif len(params) == 3: - await handler['function'](message, data=data, bot=self) - break + result = await handler['function'](message, data=data, bot=self) else: logger.error("It is not allowed to pass data and values inside data to the handler. Check your handler: {}".format(handler['function'])) return @@ -517,7 +515,8 @@ class AsyncTeleBot: if len(data_copy) > len(params) - 1: # remove the message parameter logger.error("You are passing more data than the handler needs. Check your handler: {}".format(handler['function'])) return - await handler["function"](message, **data_copy) + result = await handler["function"](message, **data_copy) + if not isinstance(result, ContinueHandling): break except Exception as e: if self.exception_handler: diff --git a/telebot/handler_backends.py b/telebot/handler_backends.py index 42c5804..70ab67d 100644 --- a/telebot/handler_backends.py +++ b/telebot/handler_backends.py @@ -174,7 +174,6 @@ class State: def __str__(self) -> str: return self.name - class StatesGroup: """ @@ -192,9 +191,6 @@ class StatesGroup: value.name = ':'.join((cls.__name__, name)) value.group = cls - - - class BaseMiddleware: """ @@ -254,8 +250,6 @@ class SkipHandler: but will skip execution of handler. """ - def __init__(self) -> None: - pass class CancelUpdate: """ @@ -266,5 +260,9 @@ class CancelUpdate: of post_process in middlewares. """ - def __init__(self) -> None: - pass \ No newline at end of file +class ContinueHandling: + """ + Class for continue updates in handlers. + Just return instance of this class + in handlers to continue process. + """ From 4798c26188a7f3c6f4609c0384b1afb1e6cac30b Mon Sep 17 00:00:00 2001 From: byehack Date: Sun, 2 Oct 2022 12:05:20 +0330 Subject: [PATCH 2/8] improve code quality --- telebot/__init__.py | 142 ++++++++++++++++++------------------ telebot/handler_backends.py | 8 ++ 2 files changed, 80 insertions(+), 70 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 72d244c..30c3716 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -6080,82 +6080,84 @@ class TeleBot: for handler in handlers: if self._test_message_handler(handler, message): if handler.get('pass_bot', False): - handler['function'](message, bot = self) + result = handler['function'](message, bot=self) else: - handler['function'](message) - break - else: - data = {} - params =[] - handler_error = None - skip_handlers = False - - if middlewares: - for middleware in middlewares: - if middleware.update_sensitive: - if hasattr(middleware, f'pre_process_{update_type}'): - result = getattr(middleware, f'pre_process_{update_type}')(message, data) - else: - logger.error('Middleware {} does not have pre_process_{} method. pre_process function execution was skipped.'.format(middleware.__class__.__name__, update_type)) - result = None - else: - result = middleware.pre_process(message, data) - # We will break this loop if CancelUpdate is returned - # Also, we will not run other middlewares - if isinstance(result, CancelUpdate): - return - elif isinstance(result, SkipHandler): - skip_handlers = True - - if handlers and not(skip_handlers): - try: - for handler in handlers: - process_handler = self._test_message_handler(handler, message) - if not process_handler: continue - for i in inspect.signature(handler['function']).parameters: - params.append(i) - result = None - if len(params) == 1: result = handler['function'](message) - elif "data" in params: - if len(params) == 2: - result = handler['function'](message, data) - elif len(params) == 3: - result = handler['function'](message, data=data, bot=self) - else: - logger.error("It is not allowed to pass data and values inside data to the handler. Check your handler: {}".format(handler['function'])) - return - else: - data_copy = data.copy() - for key in list(data_copy): - # remove data from data_copy if handler does not accept it - if key not in params: - del data_copy[key] - if handler.get('pass_bot'): - data_copy["bot"] = self - if len(data_copy) > len(params) - 1: # remove the message parameter - logger.error("You are passing more parameters than the handler needs. Check your handler: {}".format(handler['function'])) - return - result = handler["function"](message, **data_copy) if not isinstance(result, ContinueHandling): break - except Exception as e: - handler_error = e - if self.exception_handler: - self.exception_handler.handle(e) - else: - logger.error(str(e)) - logger.debug("Exception traceback:\n%s", traceback.format_exc()) + return - if middlewares: - for middleware in middlewares: - if middleware.update_sensitive: - if hasattr(middleware, f'post_process_{update_type}'): - getattr(middleware, f'post_process_{update_type}')(message, data, handler_error) - else: - logger.error("Middleware: {} does not have post_process_{} method. Post process function was not executed.".format(middleware.__class__.__name__, update_type)) + data = {} + params =[] + handler_error = None + skip_handlers = False + + if middlewares: + for middleware in middlewares: + if middleware.update_sensitive: + if hasattr(middleware, f'pre_process_{update_type}'): + result = getattr(middleware, f'pre_process_{update_type}')(message, data) else: - middleware.post_process(message, data, handler_error) + logger.error('Middleware {} does not have pre_process_{} method. pre_process function execution was skipped.'.format(middleware.__class__.__name__, update_type)) + result = None + else: + result = middleware.pre_process(message, data) + # We will break this loop if CancelUpdate is returned + # Also, we will not run other middlewares + if isinstance(result, CancelUpdate): + return + elif isinstance(result, SkipHandler): + skip_handlers = True + + if handlers and not skip_handlers: + try: + for handler in handlers: + process_handler = self._test_message_handler(handler, message) + if not process_handler: continue + for i in inspect.signature(handler['function']).parameters: + params.append(i) + result = None + if len(params) == 1: + result = handler['function'](message) + elif "data" in params: + if len(params) == 2: + result = handler['function'](message, data) + elif len(params) == 3: + result = handler['function'](message, data=data, bot=self) + else: + logger.error("It is not allowed to pass data and values inside data to the handler. Check your handler: {}".format(handler['function'])) + return + else: + data_copy = data.copy() + for key in list(data_copy): + # remove data from data_copy if handler does not accept it + if key not in params: + del data_copy[key] + if handler.get('pass_bot'): + data_copy["bot"] = self + if len(data_copy) > len(params) - 1: # remove the message parameter + logger.error("You are passing more parameters than the handler needs. Check your handler: {}".format(handler['function'])) + return + result = handler["function"](message, **data_copy) + if not isinstance(result, ContinueHandling): + break + except Exception as e: + handler_error = e + if self.exception_handler: + self.exception_handler.handle(e) + else: + logger.error(str(e)) + logger.debug("Exception traceback:\n%s", traceback.format_exc()) + + if middlewares: + for middleware in middlewares: + if middleware.update_sensitive: + if hasattr(middleware, f'post_process_{update_type}'): + getattr(middleware, f'post_process_{update_type}')(message, data, handler_error) + else: + logger.error("Middleware: {} does not have post_process_{} method. Post process function was not executed.".format(middleware.__class__.__name__, update_type)) + else: + middleware.post_process(message, data, handler_error) def _notify_command_handlers(self, handlers, new_messages, update_type): """ diff --git a/telebot/handler_backends.py b/telebot/handler_backends.py index 70ab67d..b4acc86 100644 --- a/telebot/handler_backends.py +++ b/telebot/handler_backends.py @@ -249,6 +249,8 @@ class SkipHandler: Update will go to post_process, but will skip execution of handler. """ + def __init__(self) -> None: + pass class CancelUpdate: @@ -259,6 +261,9 @@ class CancelUpdate: Update will skip handler and execution of post_process in middlewares. """ + def __init__(self) -> None: + pass + class ContinueHandling: """ @@ -266,3 +271,6 @@ class ContinueHandling: Just return instance of this class in handlers to continue process. """ + def __init__(self) -> None: + pass + From 97bca49c00ed7774a7cdd1f690c815f86907d8b3 Mon Sep 17 00:00:00 2001 From: byehack Date: Sun, 9 Oct 2022 02:28:05 +0330 Subject: [PATCH 3/8] ContinueHandling on asyncio_handler_backends --- telebot/asyncio_handler_backends.py | 15 ++++++++++++--- telebot/handler_backends.py | 1 - 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/telebot/asyncio_handler_backends.py b/telebot/asyncio_handler_backends.py index 34cc19a..a67a2a4 100644 --- a/telebot/asyncio_handler_backends.py +++ b/telebot/asyncio_handler_backends.py @@ -3,8 +3,6 @@ File with all middleware classes, states. """ - - class BaseMiddleware: """ Base class for middleware. @@ -96,6 +94,7 @@ class SkipHandler: def __init__(self) -> None: pass + class CancelUpdate: """ Class for canceling updates. @@ -106,4 +105,14 @@ class CancelUpdate: """ def __init__(self) -> None: - pass \ No newline at end of file + pass + + +class ContinueHandling: + """ + Class for continue updates in handlers. + Just return instance of this class + in handlers to continue process. + """ + def __init__(self) -> None: + pass diff --git a/telebot/handler_backends.py b/telebot/handler_backends.py index b4acc86..ca4cc35 100644 --- a/telebot/handler_backends.py +++ b/telebot/handler_backends.py @@ -273,4 +273,3 @@ class ContinueHandling: """ def __init__(self) -> None: pass - From 982e642c738f78fef1094ae22460f6a9e12f5adb Mon Sep 17 00:00:00 2001 From: _run Date: Tue, 11 Oct 2022 19:05:55 +0400 Subject: [PATCH 4/8] Update telebot/handler_backends.py --- telebot/handler_backends.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/telebot/handler_backends.py b/telebot/handler_backends.py index ca4cc35..be2714c 100644 --- a/telebot/handler_backends.py +++ b/telebot/handler_backends.py @@ -270,6 +270,19 @@ class ContinueHandling: Class for continue updates in handlers. Just return instance of this class in handlers to continue process. + + .. code-block:: python3 + :caption: Example of using ContinueHandling + + @bot.message_handler(commands=['start']) + def start(message): + bot.send_message(message.chat.id, 'Hello World!') + return ContinueHandling() + + @bot.message_handler(commands=['start']) + def start2(message): + bot.send_message(message.chat.id, 'Hello World2!') + """ def __init__(self) -> None: pass From 0fecf4620194208ca3ce488b290e184af8be40ab Mon Sep 17 00:00:00 2001 From: _run Date: Tue, 11 Oct 2022 19:09:59 +0400 Subject: [PATCH 5/8] Create continue_handling.py --- examples/continue_handling.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 examples/continue_handling.py diff --git a/examples/continue_handling.py b/examples/continue_handling.py new file mode 100644 index 0000000..68e26f1 --- /dev/null +++ b/examples/continue_handling.py @@ -0,0 +1,23 @@ +from telebot import TeleBot +from telebot.handler_backends import ContinueHandling + + +bot = TeleBot('TOKEN') + +@bot.message_handler(commands=['start']) +def start(message): + bot.send_message(message.chat.id, 'Hello World!') + return ContinueHandling() + +@bot.message_handler(commands=['start']) +def start2(message): + """ + This handler comes after the first one, but it will never be called. + But you can call it by returning ContinueHandling() in the first handler. + + If you return ContinueHandling() in the first handler, the next + registered handler with appropriate filters will be called. + """ + bot.send_message(message.chat.id, 'Hello World2!') + +bot.infinity_polling() From 5d16b8bd4a80b83998a2ad9371853dad149b4355 Mon Sep 17 00:00:00 2001 From: _run Date: Tue, 11 Oct 2022 19:13:10 +0400 Subject: [PATCH 6/8] Create continue_handling.py --- .../asynchronous_telebot/continue_handling.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 examples/asynchronous_telebot/continue_handling.py diff --git a/examples/asynchronous_telebot/continue_handling.py b/examples/asynchronous_telebot/continue_handling.py new file mode 100644 index 0000000..4781cc4 --- /dev/null +++ b/examples/asynchronous_telebot/continue_handling.py @@ -0,0 +1,27 @@ +from telebot.async_telebot import AsyncTeleBot +from telebot.asyncio_handler_backends import ContinueHandling + + +bot = AsyncTeleBot('TOKEN') + +@bot.message_handler(commands=['start']) +async def start(message): + await bot.send_message(message.chat.id, 'Hello World!') + return ContinueHandling() + +@bot.message_handler(commands=['start']) +async def start2(message): + """ + This handler comes after the first one, but it will never be called. + But you can call it by returning ContinueHandling() in the first handler. + + If you return ContinueHandling() in the first handler, the next + registered handler with appropriate filters will be called. + """ + await bot.send_message(message.chat.id, 'Hello World2!') + +import asyncio +asyncio.run(bot.polling()) # just a reminder that infinity polling +# wraps polling into try/except block just as sync version, +# but you can use any of them because neither of them stops if you +# pass non_stop=True From 81f090cce6e163c69e53a70a7eab84d87fb6374a Mon Sep 17 00:00:00 2001 From: _run Date: Tue, 11 Oct 2022 19:15:01 +0400 Subject: [PATCH 7/8] Update asyncio_handler_backends.py --- telebot/asyncio_handler_backends.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/telebot/asyncio_handler_backends.py b/telebot/asyncio_handler_backends.py index a67a2a4..6a1f2ca 100644 --- a/telebot/asyncio_handler_backends.py +++ b/telebot/asyncio_handler_backends.py @@ -113,6 +113,19 @@ class ContinueHandling: Class for continue updates in handlers. Just return instance of this class in handlers to continue process. + + .. code-block:: python3 + :caption: Example of using ContinueHandling + + @bot.message_handler(commands=['start']) + def start(message): + bot.send_message(message.chat.id, 'Hello World!') + return ContinueHandling() + + @bot.message_handler(commands=['start']) + def start2(message): + bot.send_message(message.chat.id, 'Hello World2!') + """ def __init__(self) -> None: pass From c45af810f98f9454ee5c03692ddd9c9c4f77890a Mon Sep 17 00:00:00 2001 From: _run Date: Tue, 11 Oct 2022 19:15:38 +0400 Subject: [PATCH 8/8] Updated docstrings for ContinueHandling --- telebot/asyncio_handler_backends.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/telebot/asyncio_handler_backends.py b/telebot/asyncio_handler_backends.py index 6a1f2ca..96e13ee 100644 --- a/telebot/asyncio_handler_backends.py +++ b/telebot/asyncio_handler_backends.py @@ -118,13 +118,13 @@ class ContinueHandling: :caption: Example of using ContinueHandling @bot.message_handler(commands=['start']) - def start(message): - bot.send_message(message.chat.id, 'Hello World!') + async def start(message): + await bot.send_message(message.chat.id, 'Hello World!') return ContinueHandling() @bot.message_handler(commands=['start']) - def start2(message): - bot.send_message(message.chat.id, 'Hello World2!') + async def start2(message): + await bot.send_message(message.chat.id, 'Hello World2!') """ def __init__(self) -> None: