Merge pull request #1722 from Badiboy/master

Handlers and Middlewares processing union
This commit is contained in:
Badiboy 2022-09-24 22:16:33 +03:00 committed by GitHub
commit 7c9b01b10a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 134 additions and 105 deletions

View File

@ -497,6 +497,7 @@ class TeleBot:
webhook_url = "{}://{}:{}/{}".format(protocol, listen, port, url_path) webhook_url = "{}://{}:{}/{}".format(protocol, listen, port, url_path)
if certificate and certificate_key: if certificate and certificate_key:
# noinspection PyTypeChecker
ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_ctx.load_cert_chain(certificate, certificate_key) ssl_ctx.load_cert_chain(certificate, certificate_key)
@ -1115,11 +1116,6 @@ class TeleBot:
def _exec_task(self, task, *args, **kwargs): def _exec_task(self, task, *args, **kwargs):
if kwargs:
if kwargs.pop('task_type', "") == 'handler':
if kwargs.pop('pass_bot', False):
kwargs['bot'] = self
if self.threaded: if self.threaded:
self.worker_pool.put(task, *args, **kwargs) self.worker_pool.put(task, *args, **kwargs)
else: else:
@ -4894,8 +4890,14 @@ class TeleBot:
if not isinstance(regexp, str): if not isinstance(regexp, str):
logger.error(f"{method_name}: Regexp filter should be string. Not able to use the supplied type.") logger.error(f"{method_name}: Regexp filter should be string. Not able to use the supplied type.")
def message_handler(self, commands: Optional[List[str]]=None, regexp: Optional[str]=None, func: Optional[Callable]=None, def message_handler(
content_types: Optional[List[str]]=None, chat_types: Optional[List[str]]=None, **kwargs): self,
commands: Optional[List[str]]=None,
regexp: Optional[str]=None,
func: Optional[Callable]=None,
content_types: Optional[List[str]]=None,
chat_types: Optional[List[str]]=None,
**kwargs):
""" """
Handles New incoming message of any kind - text, photo, sticker, etc. Handles New incoming message of any kind - text, photo, sticker, etc.
As a parameter to the decorator function, it passes :class:`telebot.types.Message` object. As a parameter to the decorator function, it passes :class:`telebot.types.Message` object.
@ -5979,7 +5981,7 @@ class TeleBot:
return False return False
# middleware check-up method # middleware check-up method
def _check_middleware(self, update_type): def _get_middlewares(self, update_type):
""" """
Check middleware Check middleware
@ -5993,12 +5995,25 @@ class TeleBot:
def _run_middlewares_and_handler(self, message, handlers, middlewares, update_type): def _run_middlewares_and_handler(self, message, handlers, middlewares, update_type):
""" """
This class is made to run handler and middleware in queue. This method is made to run handlers and middlewares in queue.
:param handler: handler that should be executed. :param message: received message (update part) to process with handlers and/or middlewares
:param middleware: middleware that should be executed. :param handlers: all created handlers (not filtered)
:param middlewares: middlewares that should be executed (already filtered)
:param update_type: handler/update type (Update field name)
:return: :return:
""" """
if not self.use_class_middlewares:
if handlers:
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 = {} data = {}
params =[] params =[]
handler_error = None handler_error = None
@ -6056,7 +6071,7 @@ class TeleBot:
if self.exception_handler: if self.exception_handler:
self.exception_handler.handle(e) self.exception_handler.handle(e)
else: else:
logging.error(str(e)) logger.error(str(e))
logger.debug("Exception traceback:\n%s", traceback.format_exc()) logger.debug("Exception traceback:\n%s", traceback.format_exc())
if middlewares: if middlewares:
@ -6073,20 +6088,22 @@ class TeleBot:
""" """
Notifies command handlers. Notifies command handlers.
:param handlers: :param handlers: all created handlers
:param new_messages: :param new_messages: received messages to proceed
:param update_type: handler/update type (Update fields)
:return: :return:
""" """
if not(handlers) and not(self.use_class_middlewares): if not(handlers) and not(self.use_class_middlewares):
return return
for message in new_messages: if self.use_class_middlewares:
if not self.use_class_middlewares: middlewares = self._get_middlewares(update_type)
for message_handler in handlers:
if self._test_message_handler(message_handler, message):
self._exec_task(message_handler['function'], message, pass_bot=message_handler['pass_bot'], task_type='handler')
break
else: else:
middleware = self._check_middleware(update_type) middlewares = None
self._exec_task(self._run_middlewares_and_handler, message, handlers=handlers, middlewares=middleware, update_type=update_type) for message in new_messages:
return self._exec_task(
self._run_middlewares_and_handler,
message,
handlers=handlers,
middlewares=middlewares,
update_type=update_type)

View File

@ -359,7 +359,8 @@ class AsyncTeleBot:
await self.close_session() await self.close_session()
logger.warning('Polling is stopped.') logger.warning('Polling is stopped.')
def _loop_create_task(self, coro): @staticmethod
def _loop_create_task(coro):
return asyncio.create_task(coro) return asyncio.create_task(coro)
async def _process_updates(self, handlers, messages, update_type): async def _process_updates(self, handlers, messages, update_type):
@ -371,12 +372,22 @@ class AsyncTeleBot:
:return: :return:
""" """
tasks = [] tasks = []
middlewares = await self._get_middlewares(update_type)
for message in messages: for message in messages:
middleware = await self.process_middlewares(update_type) tasks.append(self._run_middlewares_and_handlers(message, handlers, middlewares, update_type))
tasks.append(self._run_middlewares_and_handlers(handlers, message, middleware, update_type))
await asyncio.gather(*tasks) await asyncio.gather(*tasks)
async def _run_middlewares_and_handlers(self, handlers, message, middlewares, update_type): async def _run_middlewares_and_handlers(self, message, handlers, middlewares, update_type):
"""
This method is made to run handlers and middlewares in queue.
:param message: received message (update part) to process with handlers and/or middlewares
:param handlers: all created handlers (not filtered)
:param middlewares: middlewares that should be executed (already filtered)
:param update_type: handler/update type (Update field name)
:return:
"""
handler_error = None handler_error = None
data = {} data = {}
skip_handlers = False skip_handlers = False
@ -446,7 +457,7 @@ class AsyncTeleBot:
else: else:
logger.error('Middleware {} does not have post_process_{} method. post_process function execution was skipped.'.format(middleware.__class__.__name__, update_type)) logger.error('Middleware {} does not have post_process_{} method. post_process function execution was skipped.'.format(middleware.__class__.__name__, update_type))
else: await middleware.post_process(message, data, handler_error) else: await middleware.post_process(message, data, handler_error)
# update handling
async def process_new_updates(self, updates: List[types.Update]): async def process_new_updates(self, updates: List[types.Update]):
""" """
Process new updates. Process new updates.
@ -635,7 +646,7 @@ class AsyncTeleBot:
""" """
await self._process_updates(self.chat_join_request_handlers, chat_join_request, 'chat_join_request') await self._process_updates(self.chat_join_request_handlers, chat_join_request, 'chat_join_request')
async def process_middlewares(self, update_type): async def _get_middlewares(self, update_type):
""" """
:meta private: :meta private:
""" """

View File

@ -1,6 +1,5 @@
""" """
This file is used by TeleBot.run_webhooks() function. This file is used by TeleBot.run_webhooks() function.
Fastapi is required to run this script. Fastapi is required to run this script.
""" """
@ -15,15 +14,11 @@ try:
except ImportError: except ImportError:
fastapi_installed = False fastapi_installed = False
from telebot.types import Update from telebot.types import Update
from typing import Optional from typing import Optional
class SyncWebhookListener: class SyncWebhookListener:
def __init__(self, bot, def __init__(self, bot,
secret_token: str, host: Optional[str]="127.0.0.1", secret_token: str, host: Optional[str]="127.0.0.1",
@ -33,13 +28,13 @@ class SyncWebhookListener:
debug: Optional[bool]=False debug: Optional[bool]=False
) -> None: ) -> None:
""" """
Aynchronous implementation of webhook listener Synchronous implementation of webhook listener
for asynchronous version of telebot. for synchronous version of telebot.
Not supposed to be used manually by user. Not supposed to be used manually by user.
Use AsyncTeleBot.run_webhooks() instead. Use TeleBot.run_webhooks() instead.
:param bot: AsyncTeleBot instance. :param bot: TeleBot instance.
:type bot: telebot.async_telebot.AsyncTeleBot :type bot: telebot.TeleBot
:param secret_token: Telegram secret token :param secret_token: Telegram secret token
:type secret_token: str :type secret_token: str
@ -77,7 +72,8 @@ class SyncWebhookListener:
self._prepare_endpoint_urls() self._prepare_endpoint_urls()
def _check_dependencies(self): @staticmethod
def _check_dependencies():
if not fastapi_installed: if not fastapi_installed:
raise ImportError('Fastapi or uvicorn is not installed. Please install it via pip.') raise ImportError('Fastapi or uvicorn is not installed. Please install it via pip.')

View File

@ -42,6 +42,9 @@ class StateStorageBase:
def get_state(self, chat_id, user_id): def get_state(self, chat_id, user_id):
raise NotImplementedError raise NotImplementedError
def get_interactive_data(self, chat_id, user_id):
raise NotImplementedError
def save(self, chat_id, user_id, data): def save(self, chat_id, user_id, data):
raise NotImplementedError raise NotImplementedError

View File

@ -3,6 +3,7 @@ from telebot.storage.base_storage import StateStorageBase, StateContext
class StateMemoryStorage(StateStorageBase): class StateMemoryStorage(StateStorageBase):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__()
self.data = {} self.data = {}
# #
# {chat_id: {user_id: {'state': None, 'data': {}}, ...}, ...} # {chat_id: {user_id: {'state': None, 'data': {}}, ...}, ...}

View File

@ -5,8 +5,8 @@ import pickle
class StatePickleStorage(StateStorageBase): class StatePickleStorage(StateStorageBase):
# noinspection PyMissingConstructor
def __init__(self, file_path="./.state-save/states.pkl") -> None: def __init__(self, file_path="./.state-save/states.pkl") -> None:
super().__init__()
self.file_path = file_path self.file_path = file_path
self.create_dir() self.create_dir()
self.data = self.read() self.data = self.read()

View File

@ -16,6 +16,7 @@ class StateRedisStorage(StateStorageBase):
TeleBot(storage=StateRedisStorage()) TeleBot(storage=StateRedisStorage())
""" """
def __init__(self, host='localhost', port=6379, db=0, password=None, prefix='telebot_'): def __init__(self, host='localhost', port=6379, db=0, password=None, prefix='telebot_'):
super().__init__()
self.redis = ConnectionPool(host=host, port=port, db=db, password=password) self.redis = ConnectionPool(host=host, port=port, db=db, password=password)
#self.con = Redis(connection_pool=self.redis) -> use this when necessary #self.con = Redis(connection_pool=self.redis) -> use this when necessary
# #