python-telegram-bot/telegram/ext/_builders.py
Kenneth Cheo 5275c45199 Mark Internal Modules As Private (#2687)
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
2022-05-06 18:19:12 +02:00

1206 lines
50 KiB
Python

#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2021
# 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/].
#
# Some of the type hints are just ridiculously long ...
# flake8: noqa: E501
# pylint: disable=line-too-long
"""This module contains the Builder classes for the telegram.ext module."""
from queue import Queue
from threading import Event
from typing import (
TypeVar,
Generic,
TYPE_CHECKING,
Callable,
Any,
Dict,
Union,
Optional,
overload,
Type,
)
from telegram import Bot
from telegram.request import Request
from telegram._utils.types import ODVInput, DVInput
from telegram._utils.warnings import warn
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue, DEFAULT_FALSE
from telegram.ext import Dispatcher, JobQueue, Updater, ExtBot, ContextTypes, CallbackContext
from telegram.ext._utils.types import CCT, UD, CD, BD, BT, JQ, PT
if TYPE_CHECKING:
from telegram.ext import (
Defaults,
BasePersistence,
)
# Type hinting is a bit complicated here because we try to get to a sane level of
# leveraging generics and therefore need a number of type variables.
ODT = TypeVar('ODT', bound=Union[None, Dispatcher])
DT = TypeVar('DT', bound=Dispatcher)
InBT = TypeVar('InBT', bound=Bot)
InJQ = TypeVar('InJQ', bound=Union[None, JobQueue])
InPT = TypeVar('InPT', bound=Union[None, 'BasePersistence'])
InDT = TypeVar('InDT', bound=Union[None, Dispatcher])
InCCT = TypeVar('InCCT', bound='CallbackContext')
InUD = TypeVar('InUD')
InCD = TypeVar('InCD')
InBD = TypeVar('InBD')
BuilderType = TypeVar('BuilderType', bound='_BaseBuilder')
CT = TypeVar('CT', bound=Callable[..., Any])
if TYPE_CHECKING:
DEF_CCT = CallbackContext.DEFAULT_TYPE # type: ignore[misc]
InitBaseBuilder = _BaseBuilder[ # noqa: F821 # pylint: disable=used-before-assignment
Dispatcher[ExtBot, DEF_CCT, Dict, Dict, Dict, JobQueue, None],
ExtBot,
DEF_CCT,
Dict,
Dict,
Dict,
JobQueue,
None,
]
InitUpdaterBuilder = UpdaterBuilder[ # noqa: F821 # pylint: disable=used-before-assignment
Dispatcher[ExtBot, DEF_CCT, Dict, Dict, Dict, JobQueue, None],
ExtBot,
DEF_CCT,
Dict,
Dict,
Dict,
JobQueue,
None,
]
InitDispatcherBuilder = (
DispatcherBuilder[ # noqa: F821 # pylint: disable=used-before-assignment
Dispatcher[ExtBot, DEF_CCT, Dict, Dict, Dict, JobQueue, None],
ExtBot,
DEF_CCT,
Dict,
Dict,
Dict,
JobQueue,
None,
]
)
_BOT_CHECKS = [
('dispatcher', 'Dispatcher instance'),
('request', 'Request instance'),
('request_kwargs', 'request_kwargs'),
('base_file_url', 'base_file_url'),
('base_url', 'base_url'),
('token', 'token'),
('defaults', 'Defaults instance'),
('arbitrary_callback_data', 'arbitrary_callback_data'),
('private_key', 'private_key'),
]
_DISPATCHER_CHECKS = [
('bot', 'bot instance'),
('update_queue', 'update_queue'),
('workers', 'workers'),
('exception_event', 'exception_event'),
('job_queue', 'JobQueue instance'),
('persistence', 'persistence instance'),
('context_types', 'ContextTypes instance'),
('dispatcher_class', 'Dispatcher Class'),
] + _BOT_CHECKS
_DISPATCHER_CHECKS.remove(('dispatcher', 'Dispatcher instance'))
_TWO_ARGS_REQ = "The parameter `{}` may only be set, if no {} was set."
# Base class for all builders. We do this mainly to reduce code duplication, because e.g.
# the UpdaterBuilder has all method that the DispatcherBuilder has
class _BaseBuilder(Generic[ODT, BT, CCT, UD, CD, BD, JQ, PT]):
# pylint reports false positives here:
# pylint: disable=unused-private-member
__slots__ = (
'_token',
'_base_url',
'_base_file_url',
'_request_kwargs',
'_request',
'_private_key',
'_private_key_password',
'_defaults',
'_arbitrary_callback_data',
'_bot',
'_update_queue',
'_workers',
'_exception_event',
'_job_queue',
'_persistence',
'_context_types',
'_dispatcher',
'_user_signal_handler',
'_dispatcher_class',
'_dispatcher_kwargs',
'_updater_class',
'_updater_kwargs',
)
def __init__(self: 'InitBaseBuilder'):
self._token: DVInput[str] = DefaultValue('')
self._base_url: DVInput[str] = DefaultValue('https://api.telegram.org/bot')
self._base_file_url: DVInput[str] = DefaultValue('https://api.telegram.org/file/bot')
self._request_kwargs: DVInput[Dict[str, Any]] = DefaultValue({})
self._request: ODVInput['Request'] = DEFAULT_NONE
self._private_key: ODVInput[bytes] = DEFAULT_NONE
self._private_key_password: ODVInput[bytes] = DEFAULT_NONE
self._defaults: ODVInput['Defaults'] = DEFAULT_NONE
self._arbitrary_callback_data: DVInput[Union[bool, int]] = DEFAULT_FALSE
self._bot: Bot = DEFAULT_NONE # type: ignore[assignment]
self._update_queue: DVInput[Queue] = DefaultValue(Queue())
self._workers: DVInput[int] = DefaultValue(4)
self._exception_event: DVInput[Event] = DefaultValue(Event())
self._job_queue: ODVInput['JobQueue'] = DefaultValue(JobQueue())
self._persistence: ODVInput['BasePersistence'] = DEFAULT_NONE
self._context_types: DVInput[ContextTypes] = DefaultValue(ContextTypes())
self._dispatcher: ODVInput['Dispatcher'] = DEFAULT_NONE
self._user_signal_handler: Optional[Callable[[int, object], Any]] = None
self._dispatcher_class: DVInput[Type[Dispatcher]] = DefaultValue(Dispatcher)
self._dispatcher_kwargs: Dict[str, object] = {}
self._updater_class: Type[Updater] = Updater
self._updater_kwargs: Dict[str, object] = {}
@staticmethod
def _get_connection_pool_size(workers: DVInput[int]) -> int:
# For the standard use case (Updater + Dispatcher + Bot)
# we need a connection pool the size of:
# * for each of the workers
# * 1 for Dispatcher
# * 1 for Updater (even if webhook is used, we can spare a connection)
# * 1 for JobQueue
# * 1 for main thread
return DefaultValue.get_value(workers) + 4
def _build_ext_bot(self) -> ExtBot:
if isinstance(self._token, DefaultValue):
raise RuntimeError('No bot token was set.')
if not isinstance(self._request, DefaultValue):
request = self._request
else:
request_kwargs = DefaultValue.get_value(self._request_kwargs)
if (
'con_pool_size'
not in request_kwargs # pylint: disable=unsupported-membership-test
):
request_kwargs[ # pylint: disable=unsupported-assignment-operation
'con_pool_size'
] = self._get_connection_pool_size(self._workers)
request = Request(**request_kwargs) # pylint: disable=not-a-mapping
return ExtBot(
token=self._token,
base_url=DefaultValue.get_value(self._base_url),
base_file_url=DefaultValue.get_value(self._base_file_url),
private_key=DefaultValue.get_value(self._private_key),
private_key_password=DefaultValue.get_value(self._private_key_password),
defaults=DefaultValue.get_value(self._defaults),
arbitrary_callback_data=DefaultValue.get_value(self._arbitrary_callback_data),
request=request,
)
def _build_dispatcher(
self: '_BaseBuilder[ODT, BT, CCT, UD, CD, BD, JQ, PT]', stack_level: int = 3
) -> Dispatcher[BT, CCT, UD, CD, BD, JQ, PT]:
job_queue = DefaultValue.get_value(self._job_queue)
dispatcher: Dispatcher[
BT, CCT, UD, CD, BD, JQ, PT
] = DefaultValue.get_value( # type: ignore[call-arg] # pylint: disable=not-callable
self._dispatcher_class
)(
bot=self._bot if self._bot is not DEFAULT_NONE else self._build_ext_bot(),
update_queue=DefaultValue.get_value(self._update_queue),
workers=DefaultValue.get_value(self._workers),
exception_event=DefaultValue.get_value(self._exception_event),
job_queue=job_queue,
persistence=DefaultValue.get_value(self._persistence),
context_types=DefaultValue.get_value(self._context_types),
stack_level=stack_level + 1,
**self._dispatcher_kwargs,
)
if job_queue is not None:
job_queue.set_dispatcher(dispatcher)
con_pool_size = self._get_connection_pool_size(self._workers)
actual_size = dispatcher.bot.request.con_pool_size
if actual_size < con_pool_size:
warn(
f'The Connection pool of Request object is smaller ({actual_size}) than the '
f'recommended value of {con_pool_size}.',
stacklevel=stack_level,
)
return dispatcher
def _build_updater(
self: '_BaseBuilder[ODT, BT, Any, Any, Any, Any, Any, Any]',
) -> Updater[BT, ODT]:
if isinstance(self._dispatcher, DefaultValue):
dispatcher = self._build_dispatcher(stack_level=4)
return self._updater_class(
dispatcher=dispatcher,
user_signal_handler=self._user_signal_handler,
exception_event=dispatcher.exception_event,
**self._updater_kwargs, # type: ignore[arg-type]
)
if self._dispatcher:
exception_event = self._dispatcher.exception_event
bot = self._dispatcher.bot
else:
exception_event = DefaultValue.get_value(self._exception_event)
bot = self._bot or self._build_ext_bot()
return self._updater_class( # type: ignore[call-arg]
dispatcher=self._dispatcher,
bot=bot,
update_queue=DefaultValue.get_value(self._update_queue),
user_signal_handler=self._user_signal_handler,
exception_event=exception_event,
**self._updater_kwargs,
)
@property
def _dispatcher_check(self) -> bool:
return self._dispatcher not in (DEFAULT_NONE, None)
def _set_dispatcher_class(
self: BuilderType, dispatcher_class: Type[Dispatcher], kwargs: Dict[str, object] = None
) -> BuilderType:
if self._dispatcher is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format('dispatcher_class', 'Dispatcher instance'))
self._dispatcher_class = dispatcher_class
self._dispatcher_kwargs = kwargs or {}
return self
def _set_updater_class(
self: BuilderType, updater_class: Type[Updater], kwargs: Dict[str, object] = None
) -> BuilderType:
self._updater_class = updater_class
self._updater_kwargs = kwargs or {}
return self
def _set_token(self: BuilderType, token: str) -> BuilderType:
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format('token', 'bot instance'))
if self._dispatcher_check:
raise RuntimeError(_TWO_ARGS_REQ.format('token', 'Dispatcher instance'))
self._token = token
return self
def _set_base_url(self: BuilderType, base_url: str) -> BuilderType:
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format('base_url', 'bot instance'))
if self._dispatcher_check:
raise RuntimeError(_TWO_ARGS_REQ.format('base_url', 'Dispatcher instance'))
self._base_url = base_url
return self
def _set_base_file_url(self: BuilderType, base_file_url: str) -> BuilderType:
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format('base_file_url', 'bot instance'))
if self._dispatcher_check:
raise RuntimeError(_TWO_ARGS_REQ.format('base_file_url', 'Dispatcher instance'))
self._base_file_url = base_file_url
return self
def _set_request_kwargs(self: BuilderType, request_kwargs: Dict[str, Any]) -> BuilderType:
if self._request is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format('request_kwargs', 'Request instance'))
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format('request_kwargs', 'bot instance'))
if self._dispatcher_check:
raise RuntimeError(_TWO_ARGS_REQ.format('request_kwargs', 'Dispatcher instance'))
self._request_kwargs = request_kwargs
return self
def _set_request(self: BuilderType, request: Request) -> BuilderType:
if not isinstance(self._request_kwargs, DefaultValue):
raise RuntimeError(_TWO_ARGS_REQ.format('request', 'request_kwargs'))
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format('request', 'bot instance'))
if self._dispatcher_check:
raise RuntimeError(_TWO_ARGS_REQ.format('request', 'Dispatcher instance'))
self._request = request
return self
def _set_private_key(
self: BuilderType, private_key: bytes, password: bytes = None
) -> BuilderType:
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format('private_key', 'bot instance'))
if self._dispatcher_check:
raise RuntimeError(_TWO_ARGS_REQ.format('private_key', 'Dispatcher instance'))
self._private_key = private_key
self._private_key_password = password
return self
def _set_defaults(self: BuilderType, defaults: 'Defaults') -> BuilderType:
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format('defaults', 'bot instance'))
if self._dispatcher_check:
raise RuntimeError(_TWO_ARGS_REQ.format('defaults', 'Dispatcher instance'))
self._defaults = defaults
return self
def _set_arbitrary_callback_data(
self: BuilderType, arbitrary_callback_data: Union[bool, int]
) -> BuilderType:
if self._bot is not DEFAULT_NONE:
raise RuntimeError(_TWO_ARGS_REQ.format('arbitrary_callback_data', 'bot instance'))
if self._dispatcher_check:
raise RuntimeError(
_TWO_ARGS_REQ.format('arbitrary_callback_data', 'Dispatcher instance')
)
self._arbitrary_callback_data = arbitrary_callback_data
return self
def _set_bot(
self: '_BaseBuilder[Dispatcher[BT, CCT, UD, CD, BD, JQ, PT], BT, CCT, UD, CD, BD, '
'JQ, PT]',
bot: InBT,
) -> '_BaseBuilder[Dispatcher[InBT, CCT, UD, CD, BD, JQ, PT], InBT, CCT, UD, CD, BD, JQ, PT]':
for attr, error in _BOT_CHECKS:
if (
not isinstance(getattr(self, f'_{attr}'), DefaultValue)
if attr != 'dispatcher'
else self._dispatcher_check
):
raise RuntimeError(_TWO_ARGS_REQ.format('bot', error))
self._bot = bot
return self # type: ignore[return-value]
def _set_update_queue(self: BuilderType, update_queue: Queue) -> BuilderType:
if self._dispatcher_check:
raise RuntimeError(_TWO_ARGS_REQ.format('update_queue', 'Dispatcher instance'))
self._update_queue = update_queue
return self
def _set_workers(self: BuilderType, workers: int) -> BuilderType:
if self._dispatcher_check:
raise RuntimeError(_TWO_ARGS_REQ.format('workers', 'Dispatcher instance'))
self._workers = workers
return self
def _set_exception_event(self: BuilderType, exception_event: Event) -> BuilderType:
if self._dispatcher_check:
raise RuntimeError(_TWO_ARGS_REQ.format('exception_event', 'Dispatcher instance'))
self._exception_event = exception_event
return self
def _set_job_queue(
self: '_BaseBuilder[Dispatcher[BT, CCT, UD, CD, BD, JQ, PT], BT, CCT, UD, CD, BD, JQ, PT]',
job_queue: InJQ,
) -> '_BaseBuilder[Dispatcher[BT, CCT, UD, CD, BD, InJQ, PT], BT, CCT, UD, CD, BD, InJQ, PT]':
if self._dispatcher_check:
raise RuntimeError(_TWO_ARGS_REQ.format('job_queue', 'Dispatcher instance'))
self._job_queue = job_queue
return self # type: ignore[return-value]
def _set_persistence(
self: '_BaseBuilder[Dispatcher[BT, CCT, UD, CD, BD, JQ, PT], BT, CCT, UD, CD, BD, JQ, PT]',
persistence: InPT,
) -> '_BaseBuilder[Dispatcher[BT, CCT, UD, CD, BD, JQ, InPT], BT, CCT, UD, CD, BD, JQ, InPT]':
if self._dispatcher_check:
raise RuntimeError(_TWO_ARGS_REQ.format('persistence', 'Dispatcher instance'))
self._persistence = persistence
return self # type: ignore[return-value]
def _set_context_types(
self: '_BaseBuilder[Dispatcher[BT, CCT, UD, CD, BD, JQ, PT], BT, CCT, UD, CD, BD, JQ, PT]',
context_types: 'ContextTypes[InCCT, InUD, InCD, InBD]',
) -> '_BaseBuilder[Dispatcher[BT, InCCT, InUD, InCD, InBD, JQ, PT], BT, InCCT, InUD, InCD, InBD, JQ, PT]':
if self._dispatcher_check:
raise RuntimeError(_TWO_ARGS_REQ.format('context_types', 'Dispatcher instance'))
self._context_types = context_types
return self # type: ignore[return-value]
@overload
def _set_dispatcher(
self: '_BaseBuilder[ODT, BT, CCT, UD, CD, BD, JQ, PT]', dispatcher: None
) -> '_BaseBuilder[None, BT, CCT, UD, CD, BD, JQ, PT]':
...
@overload
def _set_dispatcher(
self: BuilderType, dispatcher: Dispatcher[InBT, InCCT, InUD, InCD, InBD, InJQ, InPT]
) -> '_BaseBuilder[Dispatcher[InBT, InCCT, InUD, InCD, InBD, InJQ, InPT], InBT, InCCT, InUD, InCD, InBD, InJQ, InPT]':
...
def _set_dispatcher( # type: ignore[misc]
self: BuilderType,
dispatcher: Optional[Dispatcher[InBT, InCCT, InUD, InCD, InBD, InJQ, InPT]],
) -> '_BaseBuilder[Optional[Dispatcher[InBT, InCCT, InUD, InCD, InBD, InJQ, InPT]], InBT, InCCT, InUD, InCD, InBD, InJQ, InPT]':
for attr, error in _DISPATCHER_CHECKS:
if not isinstance(getattr(self, f'_{attr}'), DefaultValue):
raise RuntimeError(_TWO_ARGS_REQ.format('dispatcher', error))
self._dispatcher = dispatcher
return self
def _set_user_signal_handler(
self: BuilderType, user_signal_handler: Callable[[int, object], Any]
) -> BuilderType:
self._user_signal_handler = user_signal_handler
return self
class DispatcherBuilder(_BaseBuilder[ODT, BT, CCT, UD, CD, BD, JQ, PT]):
"""This class serves as initializer for :class:`telegram.ext.Dispatcher` via the so called
`builder pattern`_. To build a :class:`telegram.ext.Dispatcher`, one first initializes an
instance of this class. Arguments for the :class:`telegram.ext.Dispatcher` to build are then
added by subsequently calling the methods of the builder. Finally, the
:class:`telegram.ext.Dispatcher` is built by calling :meth:`build`. In the simplest case this
can look like the following example.
Example:
.. code:: python
dispatcher = DispatcherBuilder().token('TOKEN').build()
Please see the description of the individual methods for information on which arguments can be
set and what the defaults are when not called. When no default is mentioned, the argument will
not be used by default.
Note:
* Some arguments are mutually exclusive. E.g. after calling :meth:`token`, you can't set
a custom bot with :meth:`bot` and vice versa.
* Unless a custom :class:`telegram.Bot` instance is set via :meth:`bot`, :meth:`build` will
use :class:`telegram.ext.ExtBot` for the bot.
.. seealso::
:class:`telegram.ext.UpdaterBuilder`
.. _`builder pattern`: https://en.wikipedia.org/wiki/Builder_pattern.
"""
__slots__ = ()
# The init is just here for mypy
def __init__(self: 'InitDispatcherBuilder'):
super().__init__()
def build(
self: 'DispatcherBuilder[ODT, BT, CCT, UD, CD, BD, JQ, PT]',
) -> Dispatcher[BT, CCT, UD, CD, BD, JQ, PT]:
"""Builds a :class:`telegram.ext.Dispatcher` with the provided arguments.
Returns:
:class:`telegram.ext.Dispatcher`
"""
return self._build_dispatcher()
def dispatcher_class(
self: BuilderType, dispatcher_class: Type[Dispatcher], kwargs: Dict[str, object] = None
) -> BuilderType:
"""Sets a custom subclass to be used instead of :class:`telegram.ext.Dispatcher`. The
subclasses ``__init__`` should look like this
.. code:: python
def __init__(self, custom_arg_1, custom_arg_2, ..., **kwargs):
super().__init__(**kwargs)
self.custom_arg_1 = custom_arg_1
self.custom_arg_2 = custom_arg_2
Args:
dispatcher_class (:obj:`type`): A subclass of :class:`telegram.ext.Dispatcher`
kwargs (Dict[:obj:`str`, :obj:`object`], optional): Keyword arguments for the
initialization. Defaults to an empty dict.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_dispatcher_class(dispatcher_class, kwargs)
def token(self: BuilderType, token: str) -> BuilderType:
"""Sets the token to be used for :attr:`telegram.ext.Dispatcher.bot`.
Args:
token (:obj:`str`): The token.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_token(token)
def base_url(self: BuilderType, base_url: str) -> BuilderType:
"""Sets the base URL to be used for :attr:`telegram.ext.Dispatcher.bot`. If not called,
will default to ``'https://api.telegram.org/bot'``.
.. seealso:: :attr:`telegram.Bot.base_url`, `Local Bot API Server <https://github.com/\
python-telegram-bot/python-telegram-bot/wiki/Local-Bot-API-Server>`_,
:meth:`base_url`
Args:
base_url (:obj:`str`): The URL.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_base_url(base_url)
def base_file_url(self: BuilderType, base_file_url: str) -> BuilderType:
"""Sets the base file URL to be used for :attr:`telegram.ext.Dispatcher.bot`. If not
called, will default to ``'https://api.telegram.org/file/bot'``.
.. seealso:: :attr:`telegram.Bot.base_file_url`, `Local Bot API Server <https://github.com\
/python-telegram-bot/python-telegram-bot/wiki/Local-Bot-API-Server>`_,
:meth:`base_file_url`
Args:
base_file_url (:obj:`str`): The URL.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_base_file_url(base_file_url)
def request_kwargs(self: BuilderType, request_kwargs: Dict[str, Any]) -> BuilderType:
"""Sets keyword arguments that will be passed to the :class:`telegram.utils.Request` object
that is created when :attr:`telegram.ext.Dispatcher.bot` is created. If not called, no
keyword arguments will be passed.
.. seealso:: :meth:`request`
Args:
request_kwargs (Dict[:obj:`str`, :obj:`object`]): The keyword arguments.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_request_kwargs(request_kwargs)
def request(self: BuilderType, request: Request) -> BuilderType:
"""Sets a :class:`telegram.utils.Request` object to be used for
:attr:`telegram.ext.Dispatcher.bot`.
.. seealso:: :meth:`request_kwargs`
Args:
request (:class:`telegram.utils.Request`): The request object.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_request(request)
def private_key(self: BuilderType, private_key: bytes, password: bytes = None) -> BuilderType:
"""Sets the private key and corresponding password for decryption of telegram passport data
to be used for :attr:`telegram.ext.Dispatcher.bot`.
.. seealso:: `passportbot.py <https://github.com/python-telegram-bot/python-telegram-bot\
/tree/master/examples#passportbotpy>`_, `Telegram Passports <https://git.io/fAvYd>`_
Args:
private_key (:obj:`bytes`): The private key.
password (:obj:`bytes`): Optional. The corresponding password.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_private_key(private_key=private_key, password=password)
def defaults(self: BuilderType, defaults: 'Defaults') -> BuilderType:
"""Sets the :class:`telegram.ext.Defaults` object to be used for
:attr:`telegram.ext.Dispatcher.bot`.
.. seealso:: `Adding Defaults <https://git.io/J0FGR>`_
Args:
defaults (:class:`telegram.ext.Defaults`): The defaults.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_defaults(defaults)
def arbitrary_callback_data(
self: BuilderType, arbitrary_callback_data: Union[bool, int]
) -> BuilderType:
"""Specifies whether :attr:`telegram.ext.Dispatcher.bot` should allow arbitrary objects as
callback data for :class:`telegram.InlineKeyboardButton` and how many keyboards should be
cached in memory. If not called, only strings can be used as callback data and no data will
be stored in memory.
.. seealso:: `Arbitrary callback_data <https://git.io/JGBDI>`_,
`arbitrarycallbackdatabot.py <https://git.io/J0FBv>`_
Args:
arbitrary_callback_data (:obj:`bool` | :obj:`int`): If :obj:`True` is passed, the
default cache size of 1024 will be used. Pass an integer to specify a different
cache size.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_arbitrary_callback_data(arbitrary_callback_data)
def bot(
self: 'DispatcherBuilder[Dispatcher[BT, CCT, UD, CD, BD, JQ, PT], BT, CCT, UD, CD, BD, '
'JQ, PT]',
bot: InBT,
) -> 'DispatcherBuilder[Dispatcher[InBT, CCT, UD, CD, BD, JQ, PT], InBT, CCT, UD, CD, BD, JQ, PT]':
"""Sets a :class:`telegram.Bot` instance to be used for
:attr:`telegram.ext.Dispatcher.bot`. Instances of subclasses like
:class:`telegram.ext.ExtBot` are also valid.
Args:
bot (:class:`telegram.Bot`): The bot.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_bot(bot) # type: ignore[return-value]
def update_queue(self: BuilderType, update_queue: Queue) -> BuilderType:
"""Sets a :class:`queue.Queue` instance to be used for
:attr:`telegram.ext.Dispatcher.update_queue`, i.e. the queue that the dispatcher will fetch
updates from. If not called, a queue will be instantiated.
.. seealso:: :attr:`telegram.ext.Updater.update_queue`,
:meth:`telegram.ext.UpdaterBuilder.update_queue`
Args:
update_queue (:class:`queue.Queue`): The queue.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_update_queue(update_queue)
def workers(self: BuilderType, workers: int) -> BuilderType:
"""Sets the number of worker threads to be used for
:meth:`telegram.ext.Dispatcher.run_async`, i.e. the number of callbacks that can be run
asynchronously at the same time.
.. seealso:: :attr:`telegram.ext.Handler.run_sync`,
:attr:`telegram.ext.Defaults.run_async`
Args:
workers (:obj:`int`): The number of worker threads.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_workers(workers)
def exception_event(self: BuilderType, exception_event: Event) -> BuilderType:
"""Sets a :class:`threading.Event` instance to be used for
:attr:`telegram.ext.Dispatcher.exception_event`. When this event is set, the dispatcher
will stop processing updates. If not called, an event will be instantiated.
If the dispatcher is passed to :meth:`telegram.ext.UpdaterBuilder.dispatcher`, then this
event will also be used for :attr:`telegram.ext.Updater.exception_event`.
.. seealso:: :attr:`telegram.ext.Updater.exception_event`,
:meth:`telegram.ext.UpdaterBuilder.exception_event`
Args:
exception_event (:class:`threading.Event`): The event.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_exception_event(exception_event)
def job_queue(
self: 'DispatcherBuilder[Dispatcher[BT, CCT, UD, CD, BD, JQ, PT], BT, CCT, UD, CD, BD, JQ, PT]',
job_queue: InJQ,
) -> 'DispatcherBuilder[Dispatcher[BT, CCT, UD, CD, BD, InJQ, PT], BT, CCT, UD, CD, BD, InJQ, PT]':
"""Sets a :class:`telegram.ext.JobQueue` instance to be used for
:attr:`telegram.ext.Dispatcher.job_queue`. If not called, a job queue will be instantiated.
.. seealso:: `JobQueue <https://git.io/J0FCN>`_, `timerbot.py <https://git.io/J0FWf>`_
Note:
* :meth:`telegram.ext.JobQueue.set_dispatcher` will be called automatically by
:meth:`build`.
* The job queue will be automatically started and stopped by
:meth:`telegram.ext.Dispatcher.start` and :meth:`telegram.ext.Dispatcher.stop`,
respectively.
* When passing :obj:`None`,
:attr:`telegram.ext.ConversationHandler.conversation_timeout` can not be used, as
this uses :attr:`telegram.ext.Dispatcher.job_queue` internally.
Args:
job_queue (:class:`telegram.ext.JobQueue`, optional): The job queue. Pass :obj:`None`
if you don't want to use a job queue.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_job_queue(job_queue) # type: ignore[return-value]
def persistence(
self: 'DispatcherBuilder[Dispatcher[BT, CCT, UD, CD, BD, JQ, PT], BT, CCT, UD, CD, BD, JQ, PT]',
persistence: InPT,
) -> 'DispatcherBuilder[Dispatcher[BT, CCT, UD, CD, BD, JQ, InPT], BT, CCT, UD, CD, BD, JQ, InPT]':
"""Sets a :class:`telegram.ext.BasePersistence` instance to be used for
:attr:`telegram.ext.Dispatcher.persistence`.
.. seealso:: `Making your bot persistent <https://git.io/J0FWM>`_,
`persistentconversationbot.py <https://git.io/J0FW7>`_
Warning:
If a :class:`telegram.ext.ContextTypes` instance is set via :meth:`context_types`,
the persistence instance must use the same types!
Args:
persistence (:class:`telegram.ext.BasePersistence`, optional): The persistence
instance.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_persistence(persistence) # type: ignore[return-value]
def context_types(
self: 'DispatcherBuilder[Dispatcher[BT, CCT, UD, CD, BD, JQ, PT], BT, CCT, UD, CD, BD, JQ, PT]',
context_types: 'ContextTypes[InCCT, InUD, InCD, InBD]',
) -> 'DispatcherBuilder[Dispatcher[BT, InCCT, InUD, InCD, InBD, JQ, PT], BT, InCCT, InUD, InCD, InBD, JQ, PT]':
"""Sets a :class:`telegram.ext.ContextTypes` instance to be used for
:attr:`telegram.ext.Dispatcher.context_types`.
.. seealso:: `contexttypesbot.py <https://git.io/J0F8d>`_
Args:
context_types (:class:`telegram.ext.ContextTypes`, optional): The context types.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_context_types(context_types) # type: ignore[return-value]
class UpdaterBuilder(_BaseBuilder[ODT, BT, CCT, UD, CD, BD, JQ, PT]):
"""This class serves as initializer for :class:`telegram.ext.Updater` via the so called
`builder pattern`_. To build an :class:`telegram.ext.Updater`, one first initializes an
instance of this class. Arguments for the :class:`telegram.ext.Updater` to build are then
added by subsequently calling the methods of the builder. Finally, the
:class:`telegram.ext.Updater` is built by calling :meth:`build`. In the simplest case this
can look like the following example.
Example:
.. code:: python
dispatcher = UpdaterBuilder().token('TOKEN').build()
Please see the description of the individual methods for information on which arguments can be
set and what the defaults are when not called. When no default is mentioned, the argument will
not be used by default.
Note:
* Some arguments are mutually exclusive. E.g. after calling :meth:`token`, you can't set
a custom bot with :meth:`bot` and vice versa.
* Unless a custom :class:`telegram.Bot` instance is set via :meth:`bot`, :meth:`build` will
use :class:`telegram.ext.ExtBot` for the bot.
.. seealso::
:class:`telegram.ext.DispatcherBuilder`
.. _`builder pattern`: https://en.wikipedia.org/wiki/Builder_pattern.
"""
__slots__ = ()
# The init is just here for mypy
def __init__(self: 'InitUpdaterBuilder'):
super().__init__()
def build(
self: 'UpdaterBuilder[ODT, BT, Any, Any, Any, Any, Any, Any]',
) -> Updater[BT, ODT]:
"""Builds a :class:`telegram.ext.Updater` with the provided arguments.
Returns:
:class:`telegram.ext.Updater`
"""
return self._build_updater()
def dispatcher_class(
self: BuilderType, dispatcher_class: Type[Dispatcher], kwargs: Dict[str, object] = None
) -> BuilderType:
"""Sets a custom subclass to be used instead of :class:`telegram.ext.Dispatcher`. The
subclasses ``__init__`` should look like this
.. code:: python
def __init__(self, custom_arg_1, custom_arg_2, ..., **kwargs):
super().__init__(**kwargs)
self.custom_arg_1 = custom_arg_1
self.custom_arg_2 = custom_arg_2
Args:
dispatcher_class (:obj:`type`): A subclass of :class:`telegram.ext.Dispatcher`
kwargs (Dict[:obj:`str`, :obj:`object`], optional): Keyword arguments for the
initialization. Defaults to an empty dict.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_dispatcher_class(dispatcher_class, kwargs)
def updater_class(
self: BuilderType, updater_class: Type[Updater], kwargs: Dict[str, object] = None
) -> BuilderType:
"""Sets a custom subclass to be used instead of :class:`telegram.ext.Updater`. The
subclasses ``__init__`` should look like this
.. code:: python
def __init__(self, custom_arg_1, custom_arg_2, ..., **kwargs):
super().__init__(**kwargs)
self.custom_arg_1 = custom_arg_1
self.custom_arg_2 = custom_arg_2
Args:
updater_class (:obj:`type`): A subclass of :class:`telegram.ext.Updater`
kwargs (Dict[:obj:`str`, :obj:`object`], optional): Keyword arguments for the
initialization. Defaults to an empty dict.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_updater_class(updater_class, kwargs)
def token(self: BuilderType, token: str) -> BuilderType:
"""Sets the token to be used for :attr:`telegram.ext.Updater.bot`.
Args:
token (:obj:`str`): The token.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_token(token)
def base_url(self: BuilderType, base_url: str) -> BuilderType:
"""Sets the base URL to be used for :attr:`telegram.ext.Updater.bot`. If not called,
will default to ``'https://api.telegram.org/bot'``.
.. seealso:: :attr:`telegram.Bot.base_url`, `Local Bot API Server <https://github.com/\
python-telegram-bot/python-telegram-bot/wiki/Local-Bot-API-Server>`_,
:meth:`base_url`
Args:
base_url (:obj:`str`): The URL.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_base_url(base_url)
def base_file_url(self: BuilderType, base_file_url: str) -> BuilderType:
"""Sets the base file URL to be used for :attr:`telegram.ext.Updater.bot`. If not
called, will default to ``'https://api.telegram.org/file/bot'``.
.. seealso:: :attr:`telegram.Bot.base_file_url`, `Local Bot API Server <https://github.com\
/python-telegram-bot/python-telegram-bot/wiki/Local-Bot-API-Server>`_,
:meth:`base_file_url`
Args:
base_file_url (:obj:`str`): The URL.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_base_file_url(base_file_url)
def request_kwargs(self: BuilderType, request_kwargs: Dict[str, Any]) -> BuilderType:
"""Sets keyword arguments that will be passed to the :class:`telegram.utils.Request` object
that is created when :attr:`telegram.ext.Updater.bot` is created. If not called, no
keyword arguments will be passed.
.. seealso:: :meth:`request`
Args:
request_kwargs (Dict[:obj:`str`, :obj:`object`]): The keyword arguments.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_request_kwargs(request_kwargs)
def request(self: BuilderType, request: Request) -> BuilderType:
"""Sets a :class:`telegram.utils.Request` object to be used for
:attr:`telegram.ext.Updater.bot`.
.. seealso:: :meth:`request_kwargs`
Args:
request (:class:`telegram.utils.Request`): The request object.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_request(request)
def private_key(self: BuilderType, private_key: bytes, password: bytes = None) -> BuilderType:
"""Sets the private key and corresponding password for decryption of telegram passport data
to be used for :attr:`telegram.ext.Updater.bot`.
.. seealso:: `passportbot.py <https://github.com/python-telegram-bot/python-telegram-bot\
/tree/master/examples#passportbotpy>`_, `Telegram Passports <https://git.io/fAvYd>`_
Args:
private_key (:obj:`bytes`): The private key.
password (:obj:`bytes`): Optional. The corresponding password.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_private_key(private_key=private_key, password=password)
def defaults(self: BuilderType, defaults: 'Defaults') -> BuilderType:
"""Sets the :class:`telegram.ext.Defaults` object to be used for
:attr:`telegram.ext.Updater.bot`.
.. seealso:: `Adding Defaults <https://git.io/J0FGR>`_
Args:
defaults (:class:`telegram.ext.Defaults`): The defaults.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_defaults(defaults)
def arbitrary_callback_data(
self: BuilderType, arbitrary_callback_data: Union[bool, int]
) -> BuilderType:
"""Specifies whether :attr:`telegram.ext.Updater.bot` should allow arbitrary objects as
callback data for :class:`telegram.InlineKeyboardButton` and how many keyboards should be
cached in memory. If not called, only strings can be used as callback data and no data will
be stored in memory.
.. seealso:: `Arbitrary callback_data <https://git.io/JGBDI>`_,
`arbitrarycallbackdatabot.py <https://git.io/J0FBv>`_
Args:
arbitrary_callback_data (:obj:`bool` | :obj:`int`): If :obj:`True` is passed, the
default cache size of 1024 will be used. Pass an integer to specify a different
cache size.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_arbitrary_callback_data(arbitrary_callback_data)
def bot(
self: 'UpdaterBuilder[Dispatcher[BT, CCT, UD, CD, BD, JQ, PT], BT, CCT, UD, CD, BD, '
'JQ, PT]',
bot: InBT,
) -> 'UpdaterBuilder[Dispatcher[InBT, CCT, UD, CD, BD, JQ, PT], InBT, CCT, UD, CD, BD, JQ, PT]':
"""Sets a :class:`telegram.Bot` instance to be used for
:attr:`telegram.ext.Updater.bot`. Instances of subclasses like
:class:`telegram.ext.ExtBot` are also valid.
Args:
bot (:class:`telegram.Bot`): The bot.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_bot(bot) # type: ignore[return-value]
def update_queue(self: BuilderType, update_queue: Queue) -> BuilderType:
"""Sets a :class:`queue.Queue` instance to be used for
:attr:`telegram.ext.Updater.update_queue`, i.e. the queue that the fetched updates will
be queued into. If not called, a queue will be instantiated.
If :meth:`dispatcher` is not called, this queue will also be used for
:attr:`telegram.ext.Dispatcher.update_queue`.
.. seealso:: :attr:`telegram.ext.Dispatcher.update_queue`,
:meth:`telegram.ext.DispatcherBuilder.update_queue`
Args:
update_queue (:class:`queue.Queue`): The queue.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_update_queue(update_queue)
def workers(self: BuilderType, workers: int) -> BuilderType:
"""Sets the number of worker threads to be used for
:meth:`telegram.ext.Dispatcher.run_async`, i.e. the number of callbacks that can be run
asynchronously at the same time.
.. seealso:: :attr:`telegram.ext.Handler.run_sync`,
:attr:`telegram.ext.Defaults.run_async`
Args:
workers (:obj:`int`): The number of worker threads.
Returns:
:class:`DispatcherBuilder`: The same builder with the updated argument.
"""
return self._set_workers(workers)
def exception_event(self: BuilderType, exception_event: Event) -> BuilderType:
"""Sets a :class:`threading.Event` instance to be used by the
:class:`telegram.ext.Updater`. When an unhandled exception happens while fetching updates,
this event will be set and the ``Updater`` will stop fetching for updates. If not called,
an event will be instantiated.
If :meth:`dispatcher` is not called, this event will also be used for
:attr:`telegram.ext.Dispatcher.exception_event`.
.. seealso:: :attr:`telegram.ext.Dispatcher.exception_event`,
:meth:`telegram.ext.DispatcherBuilder.exception_event`
Args:
exception_event (:class:`threading.Event`): The event.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_exception_event(exception_event)
def job_queue(
self: 'UpdaterBuilder[Dispatcher[BT, CCT, UD, CD, BD, JQ, PT], BT, CCT, UD, CD, BD, JQ, PT]',
job_queue: InJQ,
) -> 'UpdaterBuilder[Dispatcher[BT, CCT, UD, CD, BD, InJQ, PT], BT, CCT, UD, CD, BD, InJQ, PT]':
"""Sets a :class:`telegram.ext.JobQueue` instance to be used for the
:attr:`telegram.ext.Updater.dispatcher`. If not called, a job queue will be instantiated.
.. seealso:: `JobQueue <https://git.io/J0FCN>`_, `timerbot.py <https://git.io/J0FWf>`_,
:attr:`telegram.ext.Dispatcher.job_queue`
Note:
* :meth:`telegram.ext.JobQueue.set_dispatcher` will be called automatically by
:meth:`build`.
* The job queue will be automatically started/stopped by starting/stopping the
``Updater``, which automatically calls :meth:`telegram.ext.Dispatcher.start`
and :meth:`telegram.ext.Dispatcher.stop`, respectively.
* When passing :obj:`None`,
:attr:`telegram.ext.ConversationHandler.conversation_timeout` can not be used, as
this uses :attr:`telegram.ext.Dispatcher.job_queue` internally.
Args:
job_queue (:class:`telegram.ext.JobQueue`, optional): The job queue. Pass :obj:`None`
if you don't want to use a job queue.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_job_queue(job_queue) # type: ignore[return-value]
def persistence(
self: 'UpdaterBuilder[Dispatcher[BT, CCT, UD, CD, BD, JQ, PT], BT, CCT, UD, CD, BD, JQ, PT]',
persistence: InPT,
) -> 'UpdaterBuilder[Dispatcher[BT, CCT, UD, CD, BD, JQ, InPT], BT, CCT, UD, CD, BD, JQ, InPT]':
"""Sets a :class:`telegram.ext.BasePersistence` instance to be used for the
:attr:`telegram.ext.Updater.dispatcher`.
.. seealso:: `Making your bot persistent <https://git.io/J0FWM>`_,
`persistentconversationbot.py <https://git.io/J0FW7>`_,
:attr:`telegram.ext.Dispatcher.persistence`
Warning:
If a :class:`telegram.ext.ContextTypes` instance is set via :meth:`context_types`,
the persistence instance must use the same types!
Args:
persistence (:class:`telegram.ext.BasePersistence`, optional): The persistence
instance.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_persistence(persistence) # type: ignore[return-value]
def context_types(
self: 'UpdaterBuilder[Dispatcher[BT, CCT, UD, CD, BD, JQ, PT], BT, CCT, UD, CD, BD, JQ, PT]',
context_types: 'ContextTypes[InCCT, InUD, InCD, InBD]',
) -> 'UpdaterBuilder[Dispatcher[BT, InCCT, InUD, InCD, InBD, JQ, PT], BT, InCCT, InUD, InCD, InBD, JQ, PT]':
"""Sets a :class:`telegram.ext.ContextTypes` instance to be used for the
:attr:`telegram.ext.Updater.dispatcher`.
.. seealso:: `contexttypesbot.py <https://git.io/J0F8d>`_,
:attr:`telegram.ext.Dispatcher.context_types`.
Args:
context_types (:class:`telegram.ext.ContextTypes`, optional): The context types.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_context_types(context_types) # type: ignore[return-value]
@overload
def dispatcher(
self: 'UpdaterBuilder[ODT, BT, CCT, UD, CD, BD, JQ, PT]', dispatcher: None
) -> 'UpdaterBuilder[None, BT, CCT, UD, CD, BD, JQ, PT]':
...
@overload
def dispatcher(
self: BuilderType, dispatcher: Dispatcher[InBT, InCCT, InUD, InCD, InBD, InJQ, InPT]
) -> 'UpdaterBuilder[Dispatcher[InBT, InCCT, InUD, InCD, InBD, InJQ, InPT], InBT, InCCT, InUD, InCD, InBD, InJQ, InPT]':
...
def dispatcher( # type: ignore[misc]
self: BuilderType,
dispatcher: Optional[Dispatcher[InBT, InCCT, InUD, InCD, InBD, InJQ, InPT]],
) -> 'UpdaterBuilder[Optional[Dispatcher[InBT, InCCT, InUD, InCD, InBD, InJQ, InPT]], InBT, InCCT, InUD, InCD, InBD, InJQ, InPT]':
"""Sets a :class:`telegram.ext.Dispatcher` instance to be used for
:attr:`telegram.ext.Updater.dispatcher`. If not called, a queue will be instantiated.
The dispatchers :attr:`telegram.ext.Dispatcher.bot`,
:attr:`telegram.ext.Dispatcher.update_queue` and
:attr:`telegram.ext.Dispatcher.exception_event` will be used for the respective arguments
of the updater.
If not called, a dispatcher will be instantiated.
Args:
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_dispatcher(dispatcher) # type: ignore[return-value]
def user_signal_handler(
self: BuilderType, user_signal_handler: Callable[[int, object], Any]
) -> BuilderType:
"""Sets a callback to be used for :attr:`telegram.ext.Updater.user_signal_handler`.
The callback will be called when :meth:`telegram.ext.Updater.idle()` receives a signal.
It will be called with the two arguments ``signum, frame`` as for the
:meth:`signal.signal` of the standard library.
Note:
Signal handlers are an advanced feature that come with some culprits and are not thread
safe. This should therefore only be used for tasks like closing threads or database
connections on shutdown. Note that for many tasks a viable alternative is to simply
put your code *after* calling :meth:`telegram.ext.Updater.idle`. In this case it will
be executed after the updater has shut down.
Args:
user_signal_handler (Callable[signum, frame]): The signal handler.
Returns:
:class:`UpdaterBuilder`: The same builder with the updated argument.
"""
return self._set_user_signal_handler(user_signal_handler)