Skip to content

Commit

Permalink
Merge pull request #1742 from byehack/ContinueHandling
Browse files Browse the repository at this point in the history
Support ContinueHandling
  • Loading branch information
Badiboy authored Oct 11, 2022
2 parents e0ee087 + c45af81 commit 31c3a2b
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 87 deletions.
27 changes: 27 additions & 0 deletions examples/asynchronous_telebot/continue_handling.py
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions examples/continue_handling.py
Original file line number Diff line number Diff line change
@@ -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()
147 changes: 77 additions & 70 deletions telebot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@
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


Expand Down Expand Up @@ -6078,80 +6081,84 @@ def _run_middlewares_and_handler(self, message, handlers, middlewares, update_ty
for handler in handlers:
if self._test_message_handler(handler, message):
if handler.get('pass_bot', False):
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)
result = handler['function'](message, bot=self)
else:
logger.error('Middleware {} does not have pre_process_{} method. pre_process function execution was skipped.'.format(middleware.__class__.__name__, update_type))
result = None
result = handler['function'](message)
if not isinstance(result, ContinueHandling):
break
return

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:
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)
if len(params) == 1:
handler['function'](message)
elif "data" in params:
if len(params) == 2:
handler['function'](message, data)
elif len(params) == 3:
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
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:
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
handler["function"](message, **data_copy)
break
except Exception as e:
handler_error = e
if self.exception_handler:
self.exception_handler.handle(e)
logger.error("It is not allowed to pass data and values inside data to the handler. Check your handler: {}".format(handler['function']))
return
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))
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:
middleware.post_process(message, data, handler_error)
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):
"""
Expand Down
15 changes: 7 additions & 8 deletions telebot/async_telebot.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

# 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

Expand Down Expand Up @@ -497,16 +497,14 @@ async def _run_middlewares_and_handlers(self, message, handlers, middlewares, up
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
Expand All @@ -521,7 +519,8 @@ async def _run_middlewares_and_handlers(self, message, handlers, middlewares, up
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:
Expand Down
28 changes: 25 additions & 3 deletions telebot/asyncio_handler_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
"""




class BaseMiddleware:
"""
Base class for middleware.
Expand Down Expand Up @@ -96,6 +94,7 @@ class SkipHandler:
def __init__(self) -> None:
pass


class CancelUpdate:
"""
Class for canceling updates.
Expand All @@ -106,4 +105,27 @@ class CancelUpdate:
"""

def __init__(self) -> None:
pass
pass


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'])
async def start(message):
await bot.send_message(message.chat.id, 'Hello World!')
return ContinueHandling()
@bot.message_handler(commands=['start'])
async def start2(message):
await bot.send_message(message.chat.id, 'Hello World2!')
"""
def __init__(self) -> None:
pass
30 changes: 24 additions & 6 deletions telebot/handler_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ def __init__(self) -> None:
def __str__(self) -> str:
return self.name



class StatesGroup:
"""
Expand All @@ -192,9 +191,6 @@ def __init_subclass__(cls) -> None:
value.name = ':'.join((cls.__name__, name))
value.group = cls





class BaseMiddleware:
"""
Expand Down Expand Up @@ -253,10 +249,10 @@ class SkipHandler:
Update will go to post_process,
but will skip execution of handler.
"""

def __init__(self) -> None:
pass


class CancelUpdate:
"""
Class for canceling updates.
Expand All @@ -265,6 +261,28 @@ class CancelUpdate:
Update will skip handler and execution
of post_process in middlewares.
"""
def __init__(self) -> None:
pass


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
pass

0 comments on commit 31c3a2b

Please sign in to comment.