python-telegram-bot/telegram/ext/dispatcher.py

549 lines
22 KiB
Python
Raw Normal View History

#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
2020-02-02 22:08:54 +01:00
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the Dispatcher class."""
import logging
import warnings
import weakref
from functools import wraps
from threading import Thread, Lock, Event, current_thread, BoundedSemaphore
2016-01-06 15:35:55 +01:00
from time import sleep
from uuid import uuid4
from collections import defaultdict
from queue import Queue, Empty
from telegram import TelegramError, Update
from telegram.ext.handler import Handler
from telegram.ext.callbackcontext import CallbackContext
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram.utils.promise import Promise
from telegram.ext import BasePersistence
DEFAULT_GROUP = 0
2015-11-06 00:24:01 +01:00
def run_async(func):
"""
Function decorator that will run the function in a new thread.
2017-09-01 08:43:08 +02:00
Will run :attr:`telegram.ext.Dispatcher.run_async`.
Using this decorator is only possible when only a single Dispatcher exist in the system.
Warning:
If you're using @run_async you cannot rely on adding custom attributes to
:class:`telegram.ext.CallbackContext`. See its docs for more info.
2017-09-01 08:43:08 +02:00
"""
@wraps(func)
2016-06-01 20:11:00 +02:00
def async_func(*args, **kwargs):
return Dispatcher.get_instance().run_async(func, *args, **kwargs)
return async_func
class DispatcherHandlerStop(Exception):
"""
Raise this in handler to prevent execution any other handler (even in different group).
In order to use this exception in a :class:`telegram.ext.ConversationHandler`, pass the
optional ``state`` parameter instead of returning the next state:
.. code-block:: python
def callback(update, context):
...
raise DispatcherHandlerStop(next_state)
Attributes:
state (:obj:`object`): Optional. The next state of the conversation.
Args:
state (:obj:`object`, optional): The next state of the conversation.
"""
def __init__(self, state=None):
super().__init__()
self.state = state
class Dispatcher:
2017-09-01 08:43:08 +02:00
"""This class dispatches all kinds of updates to its registered handlers.
Attributes:
bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers.
update_queue (:obj:`Queue`): The synchronized queue that will contain the updates.
job_queue (:class:`telegram.ext.JobQueue`): Optional. The :class:`telegram.ext.JobQueue`
instance to pass onto handler callbacks.
workers (:obj:`int`): Number of maximum concurrent worker threads for the ``@run_async``
decorator.
user_data (:obj:`defaultdict`): A dictionary handlers can use to store data for the user.
chat_data (:obj:`defaultdict`): A dictionary handlers can use to store data for the chat.
bot_data (:obj:`dict`): A dictionary handlers can use to store data for the bot.
persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to
Documentation Improvements (#2008) * Minor doc updates, following official API docs * Fix spelling in Defaults docstrings * Clarify Changelog of v12.7 about aware dates * Fix typo in CHANGES.rst (#2024) * Fix PicklePersistence.flush() with only bot_data (#2017) * Update pylint in pre-commit to fix CI (#2018) * Add Filters.via_bot (#2009) * feat: via_bot filter also fixing a small mistake in the empty parameter of the user filter and improve docs slightly * fix: forgot to set via_bot to None * fix: redoing subclassing to copy paste solution * Cosmetic changes Co-authored-by: Hinrich Mahler <hinrich.mahler@freenet.de> * Update CHANGES.rst Fixed Typo Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de> Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com> * Update downloads badge, add info on IRC Channel to Getting Help section * Remove RegexHandler from ConversationHandlers Docs (#1973) Replaced RegexHandler with MessageHandler, since the former is deprecated * Fix Filters.via_bot docstrings * Add notes on Markdown v1 being legacy mode * Fixed typo in the Regex doc.. (#2036) * Typo: Spelling * Minor cleanup from #2043 * Document CommandHandler ignoring channel posts * Doc fixes for a few telegram.ext classes * Doc fixes for most `telegram` classes. * pep-8 forgot the hard wrap is at 99 chars, not 100! fixed a few spelling mistakes too. * Address review and made rendering of booleans consistent True, False, None are now rendered with ``bool`` wherever they weren't in telegram and telegram.ext classes. * Few doc fixes for inline* classes As usual, docs were cross-checked with official tg api docs. * Doc fixes for telegram/files classes As usual, docs were cross-checked with official tg api docs. * Doc fixes for telegram.Game Mostly just added hyperlinks. And fixed message length doc. As usual, docs were cross-checked with official tg api docs. * Very minor doc fix for passportfile.py and passportelementerrors.py Didn't bother changing too much since this seems to be a custom implementation. * Doc fixes for telegram.payments As usual, cross-checked with official bot api docs. * Address review 2 Few tiny other fixes too. * Changed from ``True/False/None`` to :obj:`True/False/None` project-wide. Few tiny other doc fixes too. Co-authored-by: Robert Geislinger <mitachundkrach@gmail.com> Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Co-authored-by: GauthamramRavichandran <30320759+GauthamramRavichandran@users.noreply.github.com> Co-authored-by: Mahesh19 <maheshvagicherla99438@gmail.com> Co-authored-by: hoppingturtles <ilovebhagwan@gmail.com>
2020-08-24 19:35:57 +02:00
store data that should be persistent over restarts.
Args:
bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers.
update_queue (:obj:`Queue`): The synchronized queue that will contain the updates.
job_queue (:class:`telegram.ext.JobQueue`, optional): The :class:`telegram.ext.JobQueue`
instance to pass onto handler callbacks.
workers (:obj:`int`, optional): Number of maximum concurrent worker threads for the
Documentation Improvements (#2008) * Minor doc updates, following official API docs * Fix spelling in Defaults docstrings * Clarify Changelog of v12.7 about aware dates * Fix typo in CHANGES.rst (#2024) * Fix PicklePersistence.flush() with only bot_data (#2017) * Update pylint in pre-commit to fix CI (#2018) * Add Filters.via_bot (#2009) * feat: via_bot filter also fixing a small mistake in the empty parameter of the user filter and improve docs slightly * fix: forgot to set via_bot to None * fix: redoing subclassing to copy paste solution * Cosmetic changes Co-authored-by: Hinrich Mahler <hinrich.mahler@freenet.de> * Update CHANGES.rst Fixed Typo Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de> Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com> * Update downloads badge, add info on IRC Channel to Getting Help section * Remove RegexHandler from ConversationHandlers Docs (#1973) Replaced RegexHandler with MessageHandler, since the former is deprecated * Fix Filters.via_bot docstrings * Add notes on Markdown v1 being legacy mode * Fixed typo in the Regex doc.. (#2036) * Typo: Spelling * Minor cleanup from #2043 * Document CommandHandler ignoring channel posts * Doc fixes for a few telegram.ext classes * Doc fixes for most `telegram` classes. * pep-8 forgot the hard wrap is at 99 chars, not 100! fixed a few spelling mistakes too. * Address review and made rendering of booleans consistent True, False, None are now rendered with ``bool`` wherever they weren't in telegram and telegram.ext classes. * Few doc fixes for inline* classes As usual, docs were cross-checked with official tg api docs. * Doc fixes for telegram/files classes As usual, docs were cross-checked with official tg api docs. * Doc fixes for telegram.Game Mostly just added hyperlinks. And fixed message length doc. As usual, docs were cross-checked with official tg api docs. * Very minor doc fix for passportfile.py and passportelementerrors.py Didn't bother changing too much since this seems to be a custom implementation. * Doc fixes for telegram.payments As usual, cross-checked with official bot api docs. * Address review 2 Few tiny other fixes too. * Changed from ``True/False/None`` to :obj:`True/False/None` project-wide. Few tiny other doc fixes too. Co-authored-by: Robert Geislinger <mitachundkrach@gmail.com> Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Co-authored-by: GauthamramRavichandran <30320759+GauthamramRavichandran@users.noreply.github.com> Co-authored-by: Mahesh19 <maheshvagicherla99438@gmail.com> Co-authored-by: hoppingturtles <ilovebhagwan@gmail.com>
2020-08-24 19:35:57 +02:00
``@run_async`` decorator. Defaults to 4.
persistence (:class:`telegram.ext.BasePersistence`, optional): The persistence class to
Documentation Improvements (#2008) * Minor doc updates, following official API docs * Fix spelling in Defaults docstrings * Clarify Changelog of v12.7 about aware dates * Fix typo in CHANGES.rst (#2024) * Fix PicklePersistence.flush() with only bot_data (#2017) * Update pylint in pre-commit to fix CI (#2018) * Add Filters.via_bot (#2009) * feat: via_bot filter also fixing a small mistake in the empty parameter of the user filter and improve docs slightly * fix: forgot to set via_bot to None * fix: redoing subclassing to copy paste solution * Cosmetic changes Co-authored-by: Hinrich Mahler <hinrich.mahler@freenet.de> * Update CHANGES.rst Fixed Typo Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de> Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com> * Update downloads badge, add info on IRC Channel to Getting Help section * Remove RegexHandler from ConversationHandlers Docs (#1973) Replaced RegexHandler with MessageHandler, since the former is deprecated * Fix Filters.via_bot docstrings * Add notes on Markdown v1 being legacy mode * Fixed typo in the Regex doc.. (#2036) * Typo: Spelling * Minor cleanup from #2043 * Document CommandHandler ignoring channel posts * Doc fixes for a few telegram.ext classes * Doc fixes for most `telegram` classes. * pep-8 forgot the hard wrap is at 99 chars, not 100! fixed a few spelling mistakes too. * Address review and made rendering of booleans consistent True, False, None are now rendered with ``bool`` wherever they weren't in telegram and telegram.ext classes. * Few doc fixes for inline* classes As usual, docs were cross-checked with official tg api docs. * Doc fixes for telegram/files classes As usual, docs were cross-checked with official tg api docs. * Doc fixes for telegram.Game Mostly just added hyperlinks. And fixed message length doc. As usual, docs were cross-checked with official tg api docs. * Very minor doc fix for passportfile.py and passportelementerrors.py Didn't bother changing too much since this seems to be a custom implementation. * Doc fixes for telegram.payments As usual, cross-checked with official bot api docs. * Address review 2 Few tiny other fixes too. * Changed from ``True/False/None`` to :obj:`True/False/None` project-wide. Few tiny other doc fixes too. Co-authored-by: Robert Geislinger <mitachundkrach@gmail.com> Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Co-authored-by: GauthamramRavichandran <30320759+GauthamramRavichandran@users.noreply.github.com> Co-authored-by: Mahesh19 <maheshvagicherla99438@gmail.com> Co-authored-by: hoppingturtles <ilovebhagwan@gmail.com>
2020-08-24 19:35:57 +02:00
store data that should be persistent over restarts.
use_context (:obj:`bool`, optional): If set to :obj:`True` Use the context based
callback API.
During the deprecation period of the old API the default is :obj:`False`.
**New users**: Set this to :obj:`True`.
2017-09-01 08:43:08 +02:00
"""
__singleton_lock = Lock()
__singleton_semaphore = BoundedSemaphore()
__singleton = None
logger = logging.getLogger(__name__)
2016-05-15 03:46:40 +02:00
def __init__(self,
bot,
update_queue,
workers=4,
exception_event=None,
job_queue=None,
persistence=None,
use_context=False):
self.bot = bot
self.update_queue = update_queue
self.job_queue = job_queue
self.workers = workers
self.use_context = use_context
if not use_context:
Update Filters, CommandHandler and MessageHandler (#1221) * update_filter attribute on filters Makes it possible to have filters work on an update instead of message, while keeping behavior for current filters * add update_type filter * Messagehandler rework - remove allow_edited (deprecated for a while) - set deprecated defaults to None - Raise deprecation warning when they're used - add sensible defaults for filters. - rework tests * Commandhandler rework * Remove deprecation test from new handler * Some tweaks per CR - rename update_types -> updates - added some clarification to docstrings * run webhook set test only on 3.6 on appveyor * update_filter attribute on filters Makes it possible to have filters work on an update instead of message, while keeping behavior for current filters * add update_type filter * Messagehandler rework - remove allow_edited (deprecated for a while) - set deprecated defaults to None - Raise deprecation warning when they're used - add sensible defaults for filters. - rework tests * Commandhandler rework * Remove deprecation test from new handler * Some tweaks per CR - rename update_types -> updates - added some clarification to docstrings * run webhook set test only on 3.6 on appveyor * Changes per CR * Update travis to build v12 * small doc update * try to make ci build version branches * doc for BaseFilter * Modify regexfilter and mergedfilter Now returns a list of match objects for every regexfilter * Change callbackcontext (+ docs) * integrate in CommandHandler and PrefixHandler * integrate in MessageHandler * cbqhandler, iqhandler and srhandler * make regexhandler a shell over MessageHandler And raise deprecationWarning on creation * clean up code and add some comments * Rework based on internal group feedback - use data_filter instead of regex_filter on BaseFilter - have these filters return a dict that is then updated onto CallbackContext instead of using a list is before - Add a .match property on CallbackContext that returns .matches[0] or None * Fix and add test for callbackcontext.match * Lots of documentation fixes and improvements [ci skip]
2019-02-13 12:07:25 +01:00
warnings.warn('Old Handler API is deprecated - see https://git.io/fxJuV for details',
TelegramDeprecationWarning, stacklevel=3)
self.user_data = defaultdict(dict)
self.chat_data = defaultdict(dict)
self.bot_data = {}
if persistence:
if not isinstance(persistence, BasePersistence):
raise TypeError("persistence should be based on telegram.ext.BasePersistence")
self.persistence = persistence
if self.persistence.store_user_data:
self.user_data = self.persistence.get_user_data()
if not isinstance(self.user_data, defaultdict):
raise ValueError("user_data must be of type defaultdict")
if self.persistence.store_chat_data:
self.chat_data = self.persistence.get_chat_data()
if not isinstance(self.chat_data, defaultdict):
raise ValueError("chat_data must be of type defaultdict")
if self.persistence.store_bot_data:
self.bot_data = self.persistence.get_bot_data()
if not isinstance(self.bot_data, dict):
raise ValueError("bot_data must be of type dict")
else:
self.persistence = None
self.handlers = {}
"""Dict[:obj:`int`, List[:class:`telegram.ext.Handler`]]: Holds the handlers per group."""
self.groups = []
"""List[:obj:`int`]: A list with all groups."""
self.error_handlers = []
"""List[:obj:`callable`]: A list of errorHandlers."""
self.running = False
""":obj:`bool`: Indicates if this dispatcher is running."""
self.__stop_event = Event()
self.__exception_event = exception_event or Event()
self.__async_queue = Queue()
self.__async_threads = set()
# For backward compatibility, we allow a "singleton" mode for the dispatcher. When there's
# only one instance of Dispatcher, it will be possible to use the `run_async` decorator.
with self.__singleton_lock:
if self.__singleton_semaphore.acquire(blocking=0):
self._set_singleton(self)
else:
self._set_singleton(None)
@property
def exception_event(self):
return self.__exception_event
def _init_async_threads(self, base_name, workers):
base_name = '{}_'.format(base_name) if base_name else ''
for i in range(workers):
thread = Thread(target=self._pooled, name='Bot:{}:worker:{}{}'.format(self.bot.id,
base_name, i))
self.__async_threads.add(thread)
thread.start()
@classmethod
def _set_singleton(cls, val):
cls.logger.debug('Setting singleton dispatcher as %s', val)
cls.__singleton = weakref.ref(val) if val else None
@classmethod
def get_instance(cls):
2017-09-01 08:43:08 +02:00
"""Get the singleton instance of this class.
Returns:
:class:`telegram.ext.Dispatcher`
Raises:
RuntimeError
2017-09-01 08:43:08 +02:00
"""
if cls.__singleton is not None:
Bot API 4.0 (#1168) Telegram Passport (#1174): - Add full support for telegram passport. - New types: PassportData, PassportFile, EncryptedPassportElement, EncryptedCredentials, PassportElementError, PassportElementErrorDataField, PassportElementErrorFrontSide, PassportElementErrorReverseSide, PassportElementErrorSelfie, PassportElementErrorFile and PassportElementErrorFiles. - New bot method: set_passport_data_errors - New filter: Filters.passport_data - Field passport_data field on Message - PassportData is automagically decrypted when you specify your private key when creating Updater or Bot. - PassportFiles is also automagically decrypted as you download/retrieve them. - See new passportbot.py example for details on how to use, or go to our telegram passport wiki page for more info - NOTE: Passport decryption requires new dependency `cryptography`. Inputfile rework (#1184): - Change how Inputfile is handled internally - This allows support for specifying the thumbnails of photos and videos using the thumb= argument in the different send_ methods. - Also allows Bot.send_media_group to actually finally send more than one media. - Add thumb to Audio, Video and Videonote - Add Bot.edit_message_media together with InputMediaAnimation, InputMediaAudio, and inputMediaDocument. Other Bot API 4.0 changes: - Add forusquare_type to Venue, InlineQueryResultVenue, InputVenueMessageContent, and Bot.send_venue. (#1170) - Add vCard support by adding vcard field to Contact, InlineQueryResultContact, InputContactMessageContent, and Bot.send_contact. (#1166) - Support new message entities: CASHTAG and PHONE_NUMBER. (#1179) - Cashtag seems to be things like $USD and $GBP, but it seems telegram doesn't currently send them to bots. - Phone number also seems to have limited support for now - Add Bot.send_animation, add width, height, and duration to Animation, and add Filters.animation. (#1172) Co-authored-by: Jasmin Bom <jsmnbom@gmail.com> Co-authored-by: code1mountain <32801117+code1mountain@users.noreply.github.com> Co-authored-by: Eldinnie <pieter.schutz+github@gmail.com> Co-authored-by: mathefreak1 <mathefreak@hi2.in>
2018-08-29 14:18:58 +02:00
return cls.__singleton() # pylint: disable=not-callable
else:
raise RuntimeError('{} not initialized or multiple instances exist'.format(
cls.__name__))
def _pooled(self):
thr_name = current_thread().getName()
while 1:
promise = self.__async_queue.get()
# If unpacking fails, the thread pool is being closed from Updater._join_async_threads
if not isinstance(promise, Promise):
self.logger.debug("Closing run_async thread %s/%d", thr_name,
len(self.__async_threads))
break
2017-02-26 22:27:03 +01:00
promise.run()
if isinstance(promise.exception, DispatcherHandlerStop):
self.logger.warning(
'DispatcherHandlerStop is not supported with async functions; func: %s',
promise.pooled_function.__name__)
def run_async(self, func, *args, **kwargs):
2017-09-01 08:43:08 +02:00
"""Queue a function (with given args/kwargs) to be run asynchronously.
Warning:
If you're using @run_async you cannot rely on adding custom attributes to
:class:`telegram.ext.CallbackContext`. See its docs for more info.
Args:
func (:obj:`callable`): The function to run in the thread.
*args (:obj:`tuple`, optional): Arguments to `func`.
**kwargs (:obj:`dict`, optional): Keyword arguments to `func`.
Returns:
Promise
"""
# TODO: handle exception in async threads
# set a threading.Event to notify caller thread
promise = Promise(func, args, kwargs)
self.__async_queue.put(promise)
return promise
def start(self, ready=None):
2017-09-01 08:43:08 +02:00
"""Thread target of thread 'dispatcher'.
2017-09-01 08:43:08 +02:00
Runs in background and processes the update queue.
Args:
ready (:obj:`threading.Event`, optional): If specified, the event will be set once the
dispatcher is ready.
2017-09-01 08:43:08 +02:00
"""
if self.running:
self.logger.warning('already running')
if ready is not None:
ready.set()
return
if self.__exception_event.is_set():
msg = 'reusing dispatcher after exception event is forbidden'
self.logger.error(msg)
raise TelegramError(msg)
self._init_async_threads(uuid4(), self.workers)
self.running = True
self.logger.debug('Dispatcher started')
if ready is not None:
ready.set()
while 1:
try:
# Pop update from update queue.
update = self.update_queue.get(True, 1)
except Empty:
if self.__stop_event.is_set():
self.logger.debug('orderly stopping')
break
2016-04-14 22:23:02 +02:00
elif self.__exception_event.is_set():
2016-05-15 03:46:40 +02:00
self.logger.critical('stopping due to exception in another thread')
break
continue
self.logger.debug('Processing Update: %s' % update)
self.process_update(update)
self.update_queue.task_done()
self.running = False
self.logger.debug('Dispatcher thread stopped')
def stop(self):
2017-09-01 08:43:08 +02:00
"""Stops the thread."""
if self.running:
self.__stop_event.set()
while self.running:
sleep(0.1)
self.__stop_event.clear()
# async threads must be join()ed only after the dispatcher thread was joined,
# otherwise we can still have new async threads dispatched
threads = list(self.__async_threads)
total = len(threads)
# Stop all threads in the thread pool by put()ting one non-tuple per thread
for i in range(total):
self.__async_queue.put(None)
for i, thr in enumerate(threads):
self.logger.debug('Waiting for async thread {}/{} to end'.format(i + 1, total))
thr.join()
self.__async_threads.remove(thr)
self.logger.debug('async thread {}/{} has ended'.format(i + 1, total))
@property
def has_running_threads(self):
return self.running or bool(self.__async_threads)
def process_update(self, update):
2017-09-01 08:43:08 +02:00
"""Processes a single update.
Args:
update (:obj:`str` | :class:`telegram.Update` | :class:`telegram.TelegramError`):
The update to process.
2017-09-01 08:43:08 +02:00
"""
# An error happened while polling
if isinstance(update, TelegramError):
try:
self.dispatch_error(None, update)
except Exception:
self.logger.exception('An uncaught error was raised while handling the error')
return
context = None
for group in self.groups:
try:
for handler in self.handlers[group]:
check = handler.check_update(update)
if check is not None and check is not False:
if not context and self.use_context:
context = CallbackContext.from_update(update, self)
handler.handle_update(update, self, check, context)
self.update_persistence(update=update)
break
# Stop processing with any other handler.
except DispatcherHandlerStop:
self.logger.debug('Stopping further handlers due to DispatcherHandlerStop')
self.update_persistence(update=update)
break
# Dispatch any error.
except Exception as e:
try:
self.dispatch_error(update, e)
except DispatcherHandlerStop:
self.logger.debug('Error handler stopped further handlers')
break
# Errors should not stop the thread.
except Exception:
self.logger.exception('An error was raised while processing the update and an '
'uncaught error was raised while handling the error '
'with an error_handler')
def add_handler(self, handler, group=DEFAULT_GROUP):
"""Register a handler.
2016-04-25 09:45:55 +02:00
TL;DR: Order and priority counts. 0 or 1 handlers per group will be used. End handling of
update with :class:`telegram.ext.DispatcherHandlerStop`.
2016-04-25 09:45:55 +02:00
A handler must be an instance of a subclass of :class:`telegram.ext.Handler`. All handlers
are organized in groups with a numeric value. The default group is 0. All groups will be
evaluated for handling an update, but only 0 or 1 handler per group will be used. If
:class:`telegram.ext.DispatcherHandlerStop` is raised from one of the handlers, no further
handlers (regardless of the group) will be called.
2016-04-25 09:45:55 +02:00
The priority/order of handlers is determined as follows:
* Priority of the group (lower group number == higher priority)
* The first handler in a group which should handle an update (see
2017-09-01 10:38:04 +02:00
:attr:`telegram.ext.Handler.check_update`) will be used. Other handlers from the
group will not be used. The order in which handlers were added to the group defines the
priority.
Args:
handler (:class:`telegram.ext.Handler`): A Handler instance.
group (:obj:`int`, optional): The group identifier. Default is 0.
"""
# Unfortunately due to circular imports this has to be here
from .conversationhandler import ConversationHandler
if not isinstance(handler, Handler):
raise TypeError('handler is not an instance of {}'.format(Handler.__name__))
if not isinstance(group, int):
raise TypeError('group is not int')
if isinstance(handler, ConversationHandler) and handler.persistent:
if not self.persistence:
raise ValueError(
"Conversationhandler {} can not be persistent if dispatcher has no "
"persistence".format(handler.name))
handler.persistence = self.persistence
handler.conversations = self.persistence.get_conversations(handler.name)
if group not in self.handlers:
self.handlers[group] = list()
self.groups.append(group)
self.groups = sorted(self.groups)
self.handlers[group].append(handler)
def remove_handler(self, handler, group=DEFAULT_GROUP):
2017-09-01 08:43:08 +02:00
"""Remove a handler from the specified group.
Args:
handler (:class:`telegram.ext.Handler`): A Handler instance.
group (:obj:`object`, optional): The group identifier. Default is 0.
2017-09-01 08:43:08 +02:00
"""
if handler in self.handlers[group]:
self.handlers[group].remove(handler)
if not self.handlers[group]:
del self.handlers[group]
self.groups.remove(group)
def update_persistence(self, update=None):
"""Update :attr:`user_data`, :attr:`chat_data` and :attr:`bot_data` in :attr:`persistence`.
Args:
2020-04-10 23:43:58 +02:00
update (:class:`telegram.Update`, optional): The update to process. If passed, only the
corresponding ``user_data`` and ``chat_data`` will be updated.
"""
if self.persistence:
chat_ids = self.chat_data.keys()
user_ids = self.user_data.keys()
if isinstance(update, Update):
if update.effective_chat:
chat_ids = [update.effective_chat.id]
else:
chat_ids = []
if update.effective_user:
user_ids = [update.effective_user.id]
else:
user_ids = []
if self.persistence.store_bot_data:
try:
self.persistence.update_bot_data(self.bot_data)
except Exception as e:
try:
self.dispatch_error(update, e)
except Exception:
message = 'Saving bot data raised an error and an ' \
'uncaught error was raised while handling ' \
'the error with an error_handler'
self.logger.exception(message)
if self.persistence.store_chat_data:
for chat_id in chat_ids:
try:
self.persistence.update_chat_data(chat_id, self.chat_data[chat_id])
except Exception as e:
try:
self.dispatch_error(update, e)
except Exception:
message = 'Saving chat data raised an error and an ' \
'uncaught error was raised while handling ' \
'the error with an error_handler'
self.logger.exception(message)
if self.persistence.store_user_data:
for user_id in user_ids:
try:
self.persistence.update_user_data(user_id, self.user_data[user_id])
except Exception as e:
try:
self.dispatch_error(update, e)
except Exception:
message = 'Saving user data raised an error and an ' \
'uncaught error was raised while handling ' \
'the error with an error_handler'
self.logger.exception(message)
def add_error_handler(self, callback):
"""Registers an error handler in the Dispatcher. This handler will receive every error
which happens in your bot.
Warning: The errors handled within these handlers won't show up in the logger, so you
need to make sure that you reraise the error.
Args:
callback (:obj:`callable`): The callback function for this error handler. Will be
called when an error is raised. Callback signature for context based API:
``def callback(update: Update, context: CallbackContext)``
The error that happened will be present in context.error.
Note:
Update Filters, CommandHandler and MessageHandler (#1221) * update_filter attribute on filters Makes it possible to have filters work on an update instead of message, while keeping behavior for current filters * add update_type filter * Messagehandler rework - remove allow_edited (deprecated for a while) - set deprecated defaults to None - Raise deprecation warning when they're used - add sensible defaults for filters. - rework tests * Commandhandler rework * Remove deprecation test from new handler * Some tweaks per CR - rename update_types -> updates - added some clarification to docstrings * run webhook set test only on 3.6 on appveyor * update_filter attribute on filters Makes it possible to have filters work on an update instead of message, while keeping behavior for current filters * add update_type filter * Messagehandler rework - remove allow_edited (deprecated for a while) - set deprecated defaults to None - Raise deprecation warning when they're used - add sensible defaults for filters. - rework tests * Commandhandler rework * Remove deprecation test from new handler * Some tweaks per CR - rename update_types -> updates - added some clarification to docstrings * run webhook set test only on 3.6 on appveyor * Changes per CR * Update travis to build v12 * small doc update * try to make ci build version branches * doc for BaseFilter * Modify regexfilter and mergedfilter Now returns a list of match objects for every regexfilter * Change callbackcontext (+ docs) * integrate in CommandHandler and PrefixHandler * integrate in MessageHandler * cbqhandler, iqhandler and srhandler * make regexhandler a shell over MessageHandler And raise deprecationWarning on creation * clean up code and add some comments * Rework based on internal group feedback - use data_filter instead of regex_filter on BaseFilter - have these filters return a dict that is then updated onto CallbackContext instead of using a list is before - Add a .match property on CallbackContext that returns .matches[0] or None * Fix and add test for callbackcontext.match * Lots of documentation fixes and improvements [ci skip]
2019-02-13 12:07:25 +01:00
See https://git.io/fxJuV for more info about switching to context based API.
2017-09-01 08:43:08 +02:00
"""
self.error_handlers.append(callback)
2016-01-04 17:31:06 +01:00
def remove_error_handler(self, callback):
2017-09-01 08:43:08 +02:00
"""Removes an error handler.
Args:
callback (:obj:`callable`): The error handler to remove.
2017-09-01 08:43:08 +02:00
"""
if callback in self.error_handlers:
self.error_handlers.remove(callback)
2016-01-04 17:31:06 +01:00
def dispatch_error(self, update, error):
2017-09-01 08:43:08 +02:00
"""Dispatches an error.
Args:
update (:obj:`str` | :class:`telegram.Update` | None): The update that caused the error
error (:obj:`Exception`): The error that was raised.
2017-09-01 08:43:08 +02:00
"""
if self.error_handlers:
for callback in self.error_handlers:
if self.use_context:
callback(update, CallbackContext.from_error(update, error, self))
else:
callback(self.bot, update, error)
else:
self.logger.exception(
Bot API 4.0 (#1168) Telegram Passport (#1174): - Add full support for telegram passport. - New types: PassportData, PassportFile, EncryptedPassportElement, EncryptedCredentials, PassportElementError, PassportElementErrorDataField, PassportElementErrorFrontSide, PassportElementErrorReverseSide, PassportElementErrorSelfie, PassportElementErrorFile and PassportElementErrorFiles. - New bot method: set_passport_data_errors - New filter: Filters.passport_data - Field passport_data field on Message - PassportData is automagically decrypted when you specify your private key when creating Updater or Bot. - PassportFiles is also automagically decrypted as you download/retrieve them. - See new passportbot.py example for details on how to use, or go to our telegram passport wiki page for more info - NOTE: Passport decryption requires new dependency `cryptography`. Inputfile rework (#1184): - Change how Inputfile is handled internally - This allows support for specifying the thumbnails of photos and videos using the thumb= argument in the different send_ methods. - Also allows Bot.send_media_group to actually finally send more than one media. - Add thumb to Audio, Video and Videonote - Add Bot.edit_message_media together with InputMediaAnimation, InputMediaAudio, and inputMediaDocument. Other Bot API 4.0 changes: - Add forusquare_type to Venue, InlineQueryResultVenue, InputVenueMessageContent, and Bot.send_venue. (#1170) - Add vCard support by adding vcard field to Contact, InlineQueryResultContact, InputContactMessageContent, and Bot.send_contact. (#1166) - Support new message entities: CASHTAG and PHONE_NUMBER. (#1179) - Cashtag seems to be things like $USD and $GBP, but it seems telegram doesn't currently send them to bots. - Phone number also seems to have limited support for now - Add Bot.send_animation, add width, height, and duration to Animation, and add Filters.animation. (#1172) Co-authored-by: Jasmin Bom <jsmnbom@gmail.com> Co-authored-by: code1mountain <32801117+code1mountain@users.noreply.github.com> Co-authored-by: Eldinnie <pieter.schutz+github@gmail.com> Co-authored-by: mathefreak1 <mathefreak@hi2.in>
2018-08-29 14:18:58 +02:00
'No error handlers are registered, logging exception.', exc_info=error)