Type Hinting (#1920)

This commit is contained in:
Hinrich Mahler 2020-10-06 19:28:40 +02:00 committed by Bibo-Joshi
parent 103b115486
commit 5fd7606084
151 changed files with 3694 additions and 2700 deletions

View file

@ -68,7 +68,9 @@ Here's how to make a one-off code change.
- You can refer to relevant issues in the commit message by writing, e.g., "#105".
- Your code should adhere to the `PEP 8 Style Guide`_, with the exception that we have a maximum line length of 99.
- Provide static typing with signature annotations. The documentation of `MyPy`_ will be a good start, the cheat sheet is `here`_. We also have some custom type aliases in ``telegram.utils.helpers.typing``.
- Document your code. This project uses `sphinx`_ to generate static HTML docs. To build them, first make sure you have the required dependencies:
.. code-block:: bash
@ -251,3 +253,5 @@ break the API classes. For example:
.. _`Google Python Style Guide`: http://google.github.io/styleguide/pyguide.html
.. _`Google Python Style Docstrings`: https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html
.. _AUTHORS.rst: ../AUTHORS.rst
.. _`MyPy`: https://mypy.readthedocs.io/en/stable/index.html
.. _`here`: https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html

1
.gitignore vendored
View file

@ -46,6 +46,7 @@ htmlcov/
.coverage.*
.cache
.pytest_cache
.mypy_cache
nosetests.xml
coverage.xml
*,cover

View file

@ -7,7 +7,7 @@ repos:
args:
- --diff
- repo: https://gitlab.com/pycqa/flake8
rev: 3.7.1
rev: 3.8.1
hooks:
- id: flake8
- repo: git://github.com/pre-commit/mirrors-pylint
@ -18,3 +18,8 @@ repos:
args:
- --errors-only
- --disable=import-error
- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v0.770'
hooks:
- id: mypy
files: ^telegram/.*\.py$

View file

@ -6,6 +6,7 @@ PYTEST := pytest
PEP257 := pep257
PEP8 := flake8
YAPF := yapf
MYPY := mypy
PIP := pip
clean:
@ -28,6 +29,9 @@ yapf:
lint:
$(PYLINT) -E telegram --disable=no-name-in-module,import-error
mypy:
$(MYPY) -p telegram
test:
$(PYTEST) -v
@ -41,6 +45,7 @@ help:
@echo "- pep8 Check style with flake8"
@echo "- lint Check style with pylint"
@echo "- yapf Check style with yapf"
@echo "- mypy Check type hinting with mypy"
@echo "- test Run tests using pytest"
@echo
@echo "Available variables:"
@ -49,4 +54,5 @@ help:
@echo "- PEP257 default: $(PEP257)"
@echo "- PEP8 default: $(PEP8)"
@echo "- YAPF default: $(YAPF)"
@echo "- MYPY default: $(MYPY)"
@echo "- PIP default: $(PIP)"

View file

@ -6,3 +6,4 @@ telegram.utils package
telegram.utils.helpers
telegram.utils.promise
telegram.utils.request
telegram.utils.types

View file

@ -0,0 +1,6 @@
telegram.utils.types Module
===========================
.. automodule:: telegram.utils.types
:members:
:show-inheritance:

View file

@ -3,6 +3,7 @@ pep257
pylint
flaky
yapf
mypy==0.770
pre-commit
beautifulsoup4
pytest==4.2.0

View file

@ -40,3 +40,22 @@ omit =
telegram/__main__.py
telegram/vendor/*
[coverage:report]
exclude_lines =
if TYPE_CHECKING:
[mypy]
warn_unused_ignores = True
warn_unused_configs = True
disallow_untyped_defs = True
disallow_incomplete_defs = True
disallow_untyped_decorators = True
show_error_codes = True
[mypy-telegram.vendor.*]
ignore_errors = True
# Disable strict optional for telegram objects with class methods
# We don't want to clutter the code with 'if self.bot is None: raise RuntimeError()'
[mypy-telegram.callbackquery,telegram.chat,telegram.message,telegram.user,telegram.files.*,telegram.inline.inlinequery,telegram.payment.precheckoutquery,telegram.payment.shippingquery,telegram.passport.passportdata,telegram.passport.credentials,telegram.passport.passportfile,telegram.ext.filters]
strict_optional = False

View file

@ -104,7 +104,6 @@ from .games.gamehighscore import GameHighScore
from .update import Update
from .files.inputmedia import (InputMedia, InputMediaVideo, InputMediaPhoto, InputMediaAnimation,
InputMediaAudio, InputMediaDocument)
from .bot import Bot
from .constants import (MAX_MESSAGE_LENGTH, MAX_CAPTION_LENGTH, SUPPORTED_WEBHOOK_PORTS,
MAX_FILESIZE_DOWNLOAD, MAX_FILESIZE_UPLOAD,
MAX_MESSAGES_PER_SECOND_PER_CHAT, MAX_MESSAGES_PER_SECOND,
@ -124,6 +123,7 @@ from .passport.credentials import (Credentials,
SecureData,
FileCredentials,
TelegramDecryptionError)
from .bot import Bot
from .version import __version__ # noqa: F401
__author__ = 'devs@python-telegram-bot.org'

View file

@ -21,11 +21,12 @@ import subprocess
import certifi
from typing import Optional
from . import __version__ as telegram_ver
def _git_revision():
def _git_revision() -> Optional[str]:
try:
output = subprocess.check_output(["git", "describe", "--long", "--tags"],
stderr=subprocess.STDOUT)
@ -34,15 +35,15 @@ def _git_revision():
return output.decode().strip()
def print_ver_info():
def print_ver_info() -> None:
git_revision = _git_revision()
print('python-telegram-bot {}'.format(telegram_ver) + (' ({})'.format(git_revision)
if git_revision else ''))
print('certifi {}'.format(certifi.__version__))
print('certifi {}'.format(certifi.__version__)) # type: ignore[attr-defined]
print('Python {}'.format(sys.version.replace('\n', ' ')))
def main():
def main() -> None:
print_ver_info()

View file

@ -17,36 +17,64 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""Base class for Telegram Objects."""
try:
import ujson as json
except ImportError:
import json
import json # type: ignore[no-redef]
import warnings
from telegram.utils.types import JSONDict
from typing import Tuple, Any, Optional, Type, TypeVar, TYPE_CHECKING, List
if TYPE_CHECKING:
from telegram import Bot
TO = TypeVar('TO', bound='TelegramObject', covariant=True)
class TelegramObject:
"""Base class for most telegram objects."""
_id_attrs = ()
# def __init__(self, *args: Any, **kwargs: Any):
# pass
def __str__(self):
_id_attrs: Tuple[Any, ...] = ()
def __str__(self) -> str:
return str(self.to_dict())
def __getitem__(self, item):
def __getitem__(self, item: str) -> Any:
return self.__dict__[item]
@staticmethod
def parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]:
if not data:
return None
return data.copy()
@classmethod
def de_json(cls, data, bot):
def de_json(cls: Type[TO], data: Optional[JSONDict], bot: 'Bot') -> Optional[TO]:
data = cls.parse_data(data)
if not data:
return None
data = data.copy()
if cls == TelegramObject:
return cls()
else:
return cls(bot=bot, **data) # type: ignore[call-arg]
return data
@classmethod
def de_list(cls: Type[TO],
data: Optional[List[JSONDict]],
bot: 'Bot') -> List[Optional[TO]]:
if not data:
return []
def to_json(self):
return [cls.de_json(d, bot) for d in data]
def to_json(self) -> str:
"""
Returns:
:obj:`str`
@ -55,7 +83,7 @@ class TelegramObject:
return json.dumps(self.to_dict())
def to_dict(self):
def to_dict(self) -> JSONDict:
data = dict()
for key in iter(self.__dict__):
@ -73,7 +101,7 @@ class TelegramObject:
data['from'] = data.pop('from_user', None)
return data
def __eq__(self, other):
def __eq__(self, other: object) -> bool:
if isinstance(other, self.__class__):
if self._id_attrs == ():
warnings.warn("Objects of type {} can not be meaningfully tested for "
@ -84,7 +112,7 @@ class TelegramObject:
return self._id_attrs == other._id_attrs
return super().__eq__(other) # pylint: disable=no-member
def __hash__(self):
def __hash__(self) -> int:
if self._id_attrs:
return hash((self.__class__, self._id_attrs)) # pylint: disable=no-member
return super().__hash__()

File diff suppressed because it is too large Load diff

View file

@ -19,6 +19,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Bot Command."""
from telegram import TelegramObject
from typing import Any
class BotCommand(TelegramObject):
@ -37,15 +38,8 @@ class BotCommand(TelegramObject):
English letters, digits and underscores.
description (:obj:`str`): Description of the command, 3-256 characters.
"""
def __init__(self, command, description, **kwargs):
def __init__(self, command: str, description: str, **kwargs: Any):
self.command = command
self.description = description
self._id_attrs = (self.command, self.description)
@classmethod
def de_json(cls, data, bot):
if not data:
return None
return cls(**data)

View file

@ -17,9 +17,14 @@
# 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 an object that represents a Telegram CallbackQuery"""
from telegram import TelegramObject, Message, User
from telegram.utils.types import JSONDict
from typing import Optional, Any, Union, TYPE_CHECKING, List
if TYPE_CHECKING:
from telegram import Bot, InlineKeyboardMarkup, GameHighScore
class CallbackQuery(TelegramObject):
"""
@ -74,15 +79,15 @@ class CallbackQuery(TelegramObject):
"""
def __init__(self,
id,
from_user,
chat_instance,
message=None,
data=None,
inline_message_id=None,
game_short_name=None,
bot=None,
**kwargs):
id: str,
from_user: User,
chat_instance: str,
message: Message = None,
data: str = None,
inline_message_id: str = None,
game_short_name: str = None,
bot: 'Bot' = None,
**kwargs: Any):
# Required
self.id = id
self.from_user = from_user
@ -98,18 +103,18 @@ class CallbackQuery(TelegramObject):
self._id_attrs = (self.id,)
@classmethod
def de_json(cls, data, bot):
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['CallbackQuery']:
data = cls.parse_data(data)
if not data:
return None
data = super().de_json(data, bot)
data['from_user'] = User.de_json(data.get('from'), bot)
data['message'] = Message.de_json(data.get('message'), bot)
return cls(bot=bot, **data)
def answer(self, *args, **kwargs):
def answer(self, *args: Any, **kwargs: Any) -> bool:
"""Shortcut for::
bot.answer_callback_query(update.callback_query.id, *args, **kwargs)
@ -118,9 +123,9 @@ class CallbackQuery(TelegramObject):
:obj:`bool`: On success, :obj:`True` is returned.
"""
return self.bot.answerCallbackQuery(self.id, *args, **kwargs)
return self.bot.answer_callback_query(self.id, *args, **kwargs)
def edit_message_text(self, text, *args, **kwargs):
def edit_message_text(self, text: str, *args: Any, **kwargs: Any) -> Union[Message, bool]:
"""Shortcut for either::
bot.edit_message_text(text, chat_id=update.callback_query.message.chat_id,
@ -144,7 +149,8 @@ class CallbackQuery(TelegramObject):
return self.bot.edit_message_text(text, chat_id=self.message.chat_id,
message_id=self.message.message_id, *args, **kwargs)
def edit_message_caption(self, caption, *args, **kwargs):
def edit_message_caption(self, caption: str, *args: Any,
**kwargs: Any) -> Union[Message, bool]:
"""Shortcut for either::
bot.edit_message_caption(caption=caption,
@ -172,7 +178,8 @@ class CallbackQuery(TelegramObject):
message_id=self.message.message_id,
*args, **kwargs)
def edit_message_reply_markup(self, reply_markup, *args, **kwargs):
def edit_message_reply_markup(self, reply_markup: 'InlineKeyboardMarkup', *args: Any,
**kwargs: Any) -> Union[Message, bool]:
"""Shortcut for either::
bot.edit_message_reply_markup(chat_id=update.callback_query.message.chat_id,
@ -201,7 +208,7 @@ class CallbackQuery(TelegramObject):
message_id=self.message.message_id,
*args, **kwargs)
def edit_message_media(self, *args, **kwargs):
def edit_message_media(self, *args: Any, **kwargs: Any) -> Union[Message, bool]:
"""Shortcut for either::
bot.edit_message_media(chat_id=update.callback_query.message.chat_id,
@ -228,7 +235,7 @@ class CallbackQuery(TelegramObject):
message_id=self.message.message_id,
*args, **kwargs)
def edit_message_live_location(self, *args, **kwargs):
def edit_message_live_location(self, *args: Any, **kwargs: Any) -> Union[Message, bool]:
"""Shortcut for either::
bot.edit_message_live_location(chat_id=update.callback_query.message.chat_id,
@ -257,7 +264,7 @@ class CallbackQuery(TelegramObject):
message_id=self.message.message_id,
*args, **kwargs)
def stop_message_live_location(self, *args, **kwargs):
def stop_message_live_location(self, *args: Any, **kwargs: Any) -> Union[Message, bool]:
"""Shortcut for either::
bot.stop_message_live_location(chat_id=update.callback_query.message.chat_id,
@ -286,7 +293,7 @@ class CallbackQuery(TelegramObject):
message_id=self.message.message_id,
*args, **kwargs)
def set_game_score(self, *args, **kwargs):
def set_game_score(self, *args: Any, **kwargs: Any) -> Union[Message, bool]:
"""Shortcut for either::
bot.set_game_score(chat_id=update.callback_query.message.chat_id,
@ -313,7 +320,7 @@ class CallbackQuery(TelegramObject):
message_id=self.message.message_id,
*args, **kwargs)
def get_game_high_scores(self, *args, **kwargs):
def get_game_high_scores(self, *args: Any, **kwargs: Any) -> List['GameHighScore']:
"""Shortcut for either::
bot.get_game_high_scores(chat_id=update.callback_query.message.chat_id,
@ -328,8 +335,7 @@ class CallbackQuery(TelegramObject):
*args, **kwargs)
Returns:
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
edited Message is returned, otherwise :obj:`True` is returned.
List[:class:`telegram.GameHighScore`]
"""
if self.inline_message_id:

View file

@ -22,6 +22,11 @@
from telegram import TelegramObject, ChatPhoto
from .chatpermissions import ChatPermissions
from telegram.utils.types import JSONDict
from typing import Any, Optional, List, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot, Message, ChatMember
class Chat(TelegramObject):
"""This object represents a chat.
@ -41,7 +46,7 @@ class Chat(TelegramObject):
invite_link (:obj:`str`): Optional. Chat invite link, for supergroups and channel chats.
pinned_message (:class:`telegram.Message`): Optional. Pinned message, for supergroups.
Returned only in :meth:`telegram.Bot.get_chat`.
permissions (:class:`telegram.ChatPermission`): Optional. Default chat member permissions,
permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions,
for groups and supergroups. Returned only in :meth:`telegram.Bot.get_chat`.
slow_mode_delay (:obj:`int`): Optional. For supergroups, the minimum allowed delay between
consecutive messages sent by each unprivileged user. Returned only in
@ -72,7 +77,7 @@ class Chat(TelegramObject):
in :meth:`telegram.Bot.get_chat`.
pinned_message (:class:`telegram.Message`, optional): Pinned message, for groups,
supergroups and channels. Returned only in :meth:`telegram.Bot.get_chat`.
permissions (:class:`telegram.ChatPermission`): Optional. Default chat member permissions,
permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions,
for groups and supergroups. Returned only in :meth:`telegram.Bot.get_chat`.
slow_mode_delay (:obj:`int`, optional): For supergroups, the minimum allowed delay between
consecutive messages sent by each unprivileged user.
@ -86,32 +91,32 @@ class Chat(TelegramObject):
"""
PRIVATE = 'private'
PRIVATE: str = 'private'
""":obj:`str`: 'private'"""
GROUP = 'group'
GROUP: str = 'group'
""":obj:`str`: 'group'"""
SUPERGROUP = 'supergroup'
SUPERGROUP: str = 'supergroup'
""":obj:`str`: 'supergroup'"""
CHANNEL = 'channel'
CHANNEL: str = 'channel'
""":obj:`str`: 'channel'"""
def __init__(self,
id,
type,
title=None,
username=None,
first_name=None,
last_name=None,
bot=None,
photo=None,
description=None,
invite_link=None,
pinned_message=None,
permissions=None,
sticker_set_name=None,
can_set_sticker_set=None,
slow_mode_delay=None,
**kwargs):
id: int,
type: str,
title: str = None,
username: str = None,
first_name: str = None,
last_name: str = None,
bot: 'Bot' = None,
photo: ChatPhoto = None,
description: str = None,
invite_link: str = None,
pinned_message: 'Message' = None,
permissions: ChatPermissions = None,
sticker_set_name: str = None,
can_set_sticker_set: bool = None,
slow_mode_delay: int = None,
**kwargs: Any):
# Required
self.id = int(id)
self.type = type
@ -135,7 +140,7 @@ class Chat(TelegramObject):
self._id_attrs = (self.id,)
@property
def link(self):
def link(self) -> Optional[str]:
""":obj:`str`: Convenience property. If the chat has a :attr:`username`, returns a t.me
link of the chat."""
if self.username:
@ -143,7 +148,9 @@ class Chat(TelegramObject):
return None
@classmethod
def de_json(cls, data, bot):
def de_json(cls, data: JSONDict, bot: 'Bot') -> Optional['Chat']:
data = cls.parse_data(data)
if not data:
return None
@ -154,7 +161,7 @@ class Chat(TelegramObject):
return cls(bot=bot, **data)
def leave(self, *args, **kwargs):
def leave(self, *args: Any, **kwargs: Any) -> bool:
"""Shortcut for::
bot.leave_chat(update.effective_chat.id, *args, **kwargs)
@ -165,7 +172,7 @@ class Chat(TelegramObject):
"""
return self.bot.leave_chat(self.id, *args, **kwargs)
def get_administrators(self, *args, **kwargs):
def get_administrators(self, *args: Any, **kwargs: Any) -> List['ChatMember']:
"""Shortcut for::
bot.get_chat_administrators(update.effective_chat.id, *args, **kwargs)
@ -179,7 +186,7 @@ class Chat(TelegramObject):
"""
return self.bot.get_chat_administrators(self.id, *args, **kwargs)
def get_members_count(self, *args, **kwargs):
def get_members_count(self, *args: Any, **kwargs: Any) -> int:
"""Shortcut for::
bot.get_chat_members_count(update.effective_chat.id, *args, **kwargs)
@ -190,7 +197,7 @@ class Chat(TelegramObject):
"""
return self.bot.get_chat_members_count(self.id, *args, **kwargs)
def get_member(self, *args, **kwargs):
def get_member(self, *args: Any, **kwargs: Any) -> 'ChatMember':
"""Shortcut for::
bot.get_chat_member(update.effective_chat.id, *args, **kwargs)
@ -201,7 +208,7 @@ class Chat(TelegramObject):
"""
return self.bot.get_chat_member(self.id, *args, **kwargs)
def kick_member(self, *args, **kwargs):
def kick_member(self, *args: Any, **kwargs: Any) -> bool:
"""Shortcut for::
bot.kick_chat_member(update.effective_chat.id, *args, **kwargs)
@ -217,7 +224,7 @@ class Chat(TelegramObject):
"""
return self.bot.kick_chat_member(self.id, *args, **kwargs)
def unban_member(self, *args, **kwargs):
def unban_member(self, *args: Any, **kwargs: Any) -> bool:
"""Shortcut for::
bot.unban_chat_member(update.effective_chat.id, *args, **kwargs)
@ -228,18 +235,18 @@ class Chat(TelegramObject):
"""
return self.bot.unban_chat_member(self.id, *args, **kwargs)
def set_permissions(self, *args, **kwargs):
def set_permissions(self, *args: Any, **kwargs: Any) -> bool:
"""Shortcut for::
bot.set_chat_permissions(update.effective_chat.id, *args, **kwargs)
Returns:
:obj:`bool`: If the action was sent successfully.
:obj:`bool`: If the action was sent successfully.
"""
return self.bot.set_chat_permissions(self.id, *args, **kwargs)
def set_administrator_custom_title(self, *args, **kwargs):
def set_administrator_custom_title(self, *args: Any, **kwargs: Any) -> bool:
"""Shortcut for::
bot.set_chat_administrator_custom_title(update.effective_chat.id, *args, **kwargs)
@ -250,7 +257,7 @@ class Chat(TelegramObject):
"""
return self.bot.set_chat_administrator_custom_title(self.id, *args, **kwargs)
def send_message(self, *args, **kwargs):
def send_message(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_message(update.effective_chat.id, *args, **kwargs)
@ -261,7 +268,7 @@ class Chat(TelegramObject):
"""
return self.bot.send_message(self.id, *args, **kwargs)
def send_media_group(self, *args, **kwargs):
def send_media_group(self, *args: Any, **kwargs: Any) -> List['Message']:
"""Shortcut for::
bot.send_media_group(update.effective_chat.id, *args, **kwargs)
@ -272,7 +279,7 @@ class Chat(TelegramObject):
"""
return self.bot.send_media_group(self.id, *args, **kwargs)
def send_chat_action(self, *args, **kwargs):
def send_chat_action(self, *args: Any, **kwargs: Any) -> bool:
"""Shortcut for::
bot.send_chat_action(update.effective_chat.id, *args, **kwargs)
@ -286,7 +293,7 @@ class Chat(TelegramObject):
send_action = send_chat_action
"""Alias for :attr:`send_chat_action`"""
def send_photo(self, *args, **kwargs):
def send_photo(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_photo(update.effective_chat.id, *args, **kwargs)
@ -297,7 +304,7 @@ class Chat(TelegramObject):
"""
return self.bot.send_photo(self.id, *args, **kwargs)
def send_contact(self, *args, **kwargs):
def send_contact(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_contact(update.effective_chat.id, *args, **kwargs)
@ -308,7 +315,7 @@ class Chat(TelegramObject):
"""
return self.bot.send_contact(self.id, *args, **kwargs)
def send_audio(self, *args, **kwargs):
def send_audio(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_audio(update.effective_chat.id, *args, **kwargs)
@ -319,7 +326,7 @@ class Chat(TelegramObject):
"""
return self.bot.send_audio(self.id, *args, **kwargs)
def send_document(self, *args, **kwargs):
def send_document(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_document(update.effective_chat.id, *args, **kwargs)
@ -330,7 +337,7 @@ class Chat(TelegramObject):
"""
return self.bot.send_document(self.id, *args, **kwargs)
def send_dice(self, *args, **kwargs):
def send_dice(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_dice(update.effective_chat.id, *args, **kwargs)
@ -341,7 +348,7 @@ class Chat(TelegramObject):
"""
return self.bot.send_dice(self.id, *args, **kwargs)
def send_game(self, *args, **kwargs):
def send_game(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_game(update.effective_chat.id, *args, **kwargs)
@ -352,7 +359,7 @@ class Chat(TelegramObject):
"""
return self.bot.send_game(self.id, *args, **kwargs)
def send_invoice(self, *args, **kwargs):
def send_invoice(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_invoice(update.effective_chat.id, *args, **kwargs)
@ -363,7 +370,7 @@ class Chat(TelegramObject):
"""
return self.bot.send_invoice(self.id, *args, **kwargs)
def send_location(self, *args, **kwargs):
def send_location(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_location(update.effective_chat.id, *args, **kwargs)
@ -374,7 +381,7 @@ class Chat(TelegramObject):
"""
return self.bot.send_location(self.id, *args, **kwargs)
def send_animation(self, *args, **kwargs):
def send_animation(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_animation(update.effective_chat.id, *args, **kwargs)
@ -385,7 +392,7 @@ class Chat(TelegramObject):
"""
return self.bot.send_animation(self.id, *args, **kwargs)
def send_sticker(self, *args, **kwargs):
def send_sticker(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_sticker(update.effective_chat.id, *args, **kwargs)
@ -396,7 +403,7 @@ class Chat(TelegramObject):
"""
return self.bot.send_sticker(self.id, *args, **kwargs)
def send_venue(self, *args, **kwargs):
def send_venue(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_venue(update.effective_chat.id, *args, **kwargs)
@ -407,7 +414,7 @@ class Chat(TelegramObject):
"""
return self.bot.send_venue(self.id, *args, **kwargs)
def send_video(self, *args, **kwargs):
def send_video(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_video(update.effective_chat.id, *args, **kwargs)
@ -418,7 +425,7 @@ class Chat(TelegramObject):
"""
return self.bot.send_video(self.id, *args, **kwargs)
def send_video_note(self, *args, **kwargs):
def send_video_note(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_video_note(update.effective_chat.id, *args, **kwargs)
@ -429,7 +436,7 @@ class Chat(TelegramObject):
"""
return self.bot.send_video_note(self.id, *args, **kwargs)
def send_voice(self, *args, **kwargs):
def send_voice(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_voice(update.effective_chat.id, *args, **kwargs)
@ -440,7 +447,7 @@ class Chat(TelegramObject):
"""
return self.bot.send_voice(self.id, *args, **kwargs)
def send_poll(self, *args, **kwargs):
def send_poll(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_poll(update.effective_chat.id, *args, **kwargs)

View file

@ -23,23 +23,23 @@
class ChatAction:
"""Helper class to provide constants for different chat actions."""
FIND_LOCATION = 'find_location'
FIND_LOCATION: str = 'find_location'
""":obj:`str`: 'find_location'"""
RECORD_AUDIO = 'record_audio'
RECORD_AUDIO: str = 'record_audio'
""":obj:`str`: 'record_audio'"""
RECORD_VIDEO = 'record_video'
RECORD_VIDEO: str = 'record_video'
""":obj:`str`: 'record_video'"""
RECORD_VIDEO_NOTE = 'record_video_note'
RECORD_VIDEO_NOTE: str = 'record_video_note'
""":obj:`str`: 'record_video_note'"""
TYPING = 'typing'
TYPING: str = 'typing'
""":obj:`str`: 'typing'"""
UPLOAD_AUDIO = 'upload_audio'
UPLOAD_AUDIO: str = 'upload_audio'
""":obj:`str`: 'upload_audio'"""
UPLOAD_DOCUMENT = 'upload_document'
UPLOAD_DOCUMENT: str = 'upload_document'
""":obj:`str`: 'upload_document'"""
UPLOAD_PHOTO = 'upload_photo'
UPLOAD_PHOTO: str = 'upload_photo'
""":obj:`str`: 'upload_photo'"""
UPLOAD_VIDEO = 'upload_video'
UPLOAD_VIDEO: str = 'upload_video'
""":obj:`str`: 'upload_video'"""
UPLOAD_VIDEO_NOTE = 'upload_video_note'
UPLOAD_VIDEO_NOTE: str = 'upload_video_note'
""":obj:`str`: 'upload_video_note'"""

View file

@ -17,10 +17,16 @@
# 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 an object that represents a Telegram ChatMember."""
import datetime
from telegram import User, TelegramObject
from telegram.utils.helpers import to_timestamp, from_timestamp
from telegram.utils.types import JSONDict
from typing import Any, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot
class ChatMember(TelegramObject):
"""This object contains information about one member of a chat.
@ -104,26 +110,40 @@ class ChatMember(TelegramObject):
may add web page previews to his messages.
"""
ADMINISTRATOR = 'administrator'
ADMINISTRATOR: str = 'administrator'
""":obj:`str`: 'administrator'"""
CREATOR = 'creator'
CREATOR: str = 'creator'
""":obj:`str`: 'creator'"""
KICKED = 'kicked'
KICKED: str = 'kicked'
""":obj:`str`: 'kicked'"""
LEFT = 'left'
LEFT: str = 'left'
""":obj:`str`: 'left'"""
MEMBER = 'member'
MEMBER: str = 'member'
""":obj:`str`: 'member'"""
RESTRICTED = 'restricted'
RESTRICTED: str = 'restricted'
""":obj:`str`: 'restricted'"""
def __init__(self, user, status, until_date=None, can_be_edited=None,
can_change_info=None, can_post_messages=None, can_edit_messages=None,
can_delete_messages=None, can_invite_users=None,
can_restrict_members=None, can_pin_messages=None,
can_promote_members=None, can_send_messages=None,
can_send_media_messages=None, can_send_polls=None, can_send_other_messages=None,
can_add_web_page_previews=None, is_member=None, custom_title=None, **kwargs):
def __init__(self,
user: User,
status: str,
until_date: datetime.datetime = None,
can_be_edited: bool = None,
can_change_info: bool = None,
can_post_messages: bool = None,
can_edit_messages: bool = None,
can_delete_messages: bool = None,
can_invite_users: bool = None,
can_restrict_members: bool = None,
can_pin_messages: bool = None,
can_promote_members: bool = None,
can_send_messages: bool = None,
can_send_media_messages: bool = None,
can_send_polls: bool = None,
can_send_other_messages: bool = None,
can_add_web_page_previews: bool = None,
is_member: bool = None,
custom_title: str = None,
**kwargs: Any):
# Required
self.user = user
self.status = status
@ -148,18 +168,18 @@ class ChatMember(TelegramObject):
self._id_attrs = (self.user, self.status)
@classmethod
def de_json(cls, data, bot):
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatMember']:
data = cls.parse_data(data)
if not data:
return None
data = super().de_json(data, bot)
data['user'] = User.de_json(data.get('user'), bot)
data['until_date'] = from_timestamp(data.get('until_date', None))
return cls(**data)
def to_dict(self):
def to_dict(self) -> JSONDict:
data = super().to_dict()
data['until_date'] = to_timestamp(self.until_date)

View file

@ -19,6 +19,7 @@
"""This module contains an object that represents a Telegram ChatPermission."""
from telegram import TelegramObject
from typing import Any
class ChatPermissions(TelegramObject):
@ -76,9 +77,16 @@ class ChatPermissions(TelegramObject):
"""
def __init__(self, can_send_messages=None, can_send_media_messages=None, can_send_polls=None,
can_send_other_messages=None, can_add_web_page_previews=None,
can_change_info=None, can_invite_users=None, can_pin_messages=None, **kwargs):
def __init__(self,
can_send_messages: bool = None,
can_send_media_messages: bool = None,
can_send_polls: bool = None,
can_send_other_messages: bool = None,
can_add_web_page_previews: bool = None,
can_change_info: bool = None,
can_invite_users: bool = None,
can_pin_messages: bool = None,
**kwargs: Any):
# Required
self.can_send_messages = can_send_messages
self.can_send_media_messages = can_send_media_messages
@ -99,10 +107,3 @@ class ChatPermissions(TelegramObject):
self.can_invite_users,
self.can_pin_messages
)
@classmethod
def de_json(cls, data, bot):
if not data:
return None
return cls(**data)

View file

@ -20,6 +20,10 @@
"""This module contains an object that represents a Telegram ChosenInlineResult."""
from telegram import TelegramObject, User, Location
from telegram.utils.types import JSONDict
from typing import Any, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot
class ChosenInlineResult(TelegramObject):
@ -58,12 +62,12 @@ class ChosenInlineResult(TelegramObject):
"""
def __init__(self,
result_id,
from_user,
query,
location=None,
inline_message_id=None,
**kwargs):
result_id: str,
from_user: User,
query: str,
location: Location = None,
inline_message_id: str = None,
**kwargs: Any):
# Required
self.result_id = result_id
self.from_user = from_user
@ -75,11 +79,12 @@ class ChosenInlineResult(TelegramObject):
self._id_attrs = (self.result_id,)
@classmethod
def de_json(cls, data, bot):
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChosenInlineResult']:
data = cls.parse_data(data)
if not data:
return None
data = super().de_json(data, bot)
# Required
data['from_user'] = User.de_json(data.pop('from'), bot)
# Optionals

View file

@ -40,18 +40,19 @@ Attributes:
formatting styles)
"""
from typing import List
MAX_MESSAGE_LENGTH = 4096
MAX_CAPTION_LENGTH = 1024
MAX_MESSAGE_LENGTH: int = 4096
MAX_CAPTION_LENGTH: int = 1024
# constants above this line are tested
SUPPORTED_WEBHOOK_PORTS = [443, 80, 88, 8443]
MAX_FILESIZE_DOWNLOAD = int(20E6) # (20MB)
MAX_FILESIZE_UPLOAD = int(50E6) # (50MB)
MAX_PHOTOSIZE_UPLOAD = int(10E6) # (10MB)
MAX_MESSAGES_PER_SECOND_PER_CHAT = 1
MAX_MESSAGES_PER_SECOND = 30
MAX_MESSAGES_PER_MINUTE_PER_GROUP = 20
MAX_MESSAGE_ENTITIES = 100
MAX_INLINE_QUERY_RESULTS = 50
SUPPORTED_WEBHOOK_PORTS: List[int] = [443, 80, 88, 8443]
MAX_FILESIZE_DOWNLOAD: int = int(20E6) # (20MB)
MAX_FILESIZE_UPLOAD: int = int(50E6) # (50MB)
MAX_PHOTOSIZE_UPLOAD: int = int(10E6) # (10MB)
MAX_MESSAGES_PER_SECOND_PER_CHAT: int = 1
MAX_MESSAGES_PER_SECOND: int = 30
MAX_MESSAGES_PER_MINUTE_PER_GROUP: int = 20
MAX_MESSAGE_ENTITIES: int = 100
MAX_INLINE_QUERY_RESULTS: int = 50

View file

@ -19,6 +19,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Dice."""
from telegram import TelegramObject
from typing import Any, List
class Dice(TelegramObject):
@ -47,25 +48,18 @@ class Dice(TelegramObject):
value (:obj:`int`): Value of the dice. 1-6 for dice and darts, 1-5 for basketball.
emoji (:obj:`str`): Emoji on which the dice throw animation is based.
"""
def __init__(self, value, emoji, **kwargs):
def __init__(self, value: int, emoji: str, **kwargs: Any):
self.value = value
self.emoji = emoji
self._id_attrs = (self.value, self.emoji)
@classmethod
def de_json(cls, data, bot):
if not data:
return None
return cls(**data)
DICE = '🎲'
DICE: str = '🎲'
""":obj:`str`: '🎲'"""
DARTS = '🎯'
DARTS: str = '🎯'
""":obj:`str`: '🎯'"""
BASKETBALL = '🏀'
""":obj:`str`: '🏀'"""
ALL_EMOJI = [DICE, DARTS, BASKETBALL]
ALL_EMOJI: List[str] = [DICE, DARTS, BASKETBALL]
"""List[:obj:`str`]: List of all supported base emoji. Currently :attr:`DICE`,
:attr:`DARTS` and :attr:`BASKETBALL`."""

View file

@ -17,9 +17,10 @@
# 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 an object that represents Telegram errors."""
from typing import Tuple
def _lstrip_str(in_s, lstr):
def _lstrip_str(in_s: str, lstr: str) -> str:
"""
Args:
in_s (:obj:`str`): in string
@ -37,7 +38,7 @@ def _lstrip_str(in_s, lstr):
class TelegramError(Exception):
def __init__(self, message):
def __init__(self, message: str):
super().__init__()
msg = _lstrip_str(message, 'Error: ')
@ -48,10 +49,10 @@ class TelegramError(Exception):
msg = msg.capitalize()
self.message = msg
def __str__(self):
return '%s' % (self.message)
def __str__(self) -> str:
return '%s' % self.message
def __reduce__(self):
def __reduce__(self) -> Tuple[type, Tuple[str]]:
return self.__class__, (self.message,)
@ -60,10 +61,10 @@ class Unauthorized(TelegramError):
class InvalidToken(TelegramError):
def __init__(self):
def __init__(self) -> None:
super().__init__('Invalid token')
def __reduce__(self):
def __reduce__(self) -> Tuple[type, Tuple]: # type: ignore[override]
return self.__class__, ()
@ -76,10 +77,10 @@ class BadRequest(NetworkError):
class TimedOut(NetworkError):
def __init__(self):
def __init__(self) -> None:
super().__init__('Timed out')
def __reduce__(self):
def __reduce__(self) -> Tuple[type, Tuple]: # type: ignore[override]
return self.__class__, ()
@ -90,11 +91,11 @@ class ChatMigrated(TelegramError):
"""
def __init__(self, new_chat_id):
def __init__(self, new_chat_id: int):
super().__init__('Group migrated to supergroup. New chat id: {}'.format(new_chat_id))
self.new_chat_id = new_chat_id
def __reduce__(self):
def __reduce__(self) -> Tuple[type, Tuple[int]]: # type: ignore[override]
return self.__class__, (self.new_chat_id,)
@ -105,11 +106,11 @@ class RetryAfter(TelegramError):
"""
def __init__(self, retry_after):
def __init__(self, retry_after: int):
super().__init__('Flood control exceeded. Retry in {} seconds'.format(float(retry_after)))
self.retry_after = float(retry_after)
def __reduce__(self):
def __reduce__(self) -> Tuple[type, Tuple[float]]: # type: ignore[override]
return self.__class__, (self.retry_after,)
@ -122,8 +123,8 @@ class Conflict(TelegramError):
"""
def __init__(self, msg):
def __init__(self, msg: str):
super().__init__(msg)
def __reduce__(self):
def __reduce__(self) -> Tuple[type, Tuple[str]]:
return self.__class__, (self.message,)

View file

@ -24,6 +24,9 @@ from copy import copy
from telegram import Bot
from typing import DefaultDict, Dict, Any, Tuple, Optional, cast
from telegram.utils.types import ConversationDict
class BasePersistence(ABC):
"""Interface class for adding persistence to your bot.
@ -70,7 +73,7 @@ class BasePersistence(ABC):
persistence class. Default is :obj:`True` .
"""
def __new__(cls, *args, **kwargs):
def __new__(cls, *args: Any, **kwargs: Any) -> 'BasePersistence':
instance = super().__new__(cls)
get_user_data = instance.get_user_data
get_chat_data = instance.get_chat_data
@ -79,22 +82,22 @@ class BasePersistence(ABC):
update_chat_data = instance.update_chat_data
update_bot_data = instance.update_bot_data
def get_user_data_insert_bot():
def get_user_data_insert_bot() -> DefaultDict[int, Dict[Any, Any]]:
return instance.insert_bot(get_user_data())
def get_chat_data_insert_bot():
def get_chat_data_insert_bot() -> DefaultDict[int, Dict[Any, Any]]:
return instance.insert_bot(get_chat_data())
def get_bot_data_insert_bot():
def get_bot_data_insert_bot() -> Dict[Any, Any]:
return instance.insert_bot(get_bot_data())
def update_user_data_replace_bot(user_id, data):
def update_user_data_replace_bot(user_id: int, data: Dict) -> None:
return update_user_data(user_id, instance.replace_bot(data))
def update_chat_data_replace_bot(chat_id, data):
def update_chat_data_replace_bot(chat_id: int, data: Dict) -> None:
return update_chat_data(chat_id, instance.replace_bot(data))
def update_bot_data_replace_bot(data):
def update_bot_data_replace_bot(data: Dict) -> None:
return update_bot_data(instance.replace_bot(data))
instance.get_user_data = get_user_data_insert_bot
@ -105,13 +108,16 @@ class BasePersistence(ABC):
instance.update_bot_data = update_bot_data_replace_bot
return instance
def __init__(self, store_user_data=True, store_chat_data=True, store_bot_data=True):
def __init__(self,
store_user_data: bool = True,
store_chat_data: bool = True,
store_bot_data: bool = True):
self.store_user_data = store_user_data
self.store_chat_data = store_chat_data
self.store_bot_data = store_bot_data
self.bot = None
self.bot: Bot = None # type: ignore[assignment]
def set_bot(self, bot):
def set_bot(self, bot: Bot) -> None:
"""Set the Bot to be used by this persistence instance.
Args:
@ -120,7 +126,7 @@ class BasePersistence(ABC):
self.bot = bot
@classmethod
def replace_bot(cls, obj):
def replace_bot(cls, obj: object) -> object:
"""
Replaces all instances of :class:`telegram.Bot` that occur within the passed object with
:attr:`REPLACED_BOT`. Currently, this handles objects of type ``list``, ``tuple``, ``set``,
@ -140,6 +146,7 @@ class BasePersistence(ABC):
new_obj = copy(obj)
if isinstance(obj, (dict, defaultdict)):
new_obj = cast(dict, new_obj)
new_obj.clear()
for k, v in obj.items():
new_obj[cls.replace_bot(k)] = cls.replace_bot(v)
@ -156,7 +163,7 @@ class BasePersistence(ABC):
return obj
def insert_bot(self, obj):
def insert_bot(self, obj: object) -> object:
"""
Replaces all instances of :attr:`REPLACED_BOT` that occur within the passed object with
:attr:`bot`. Currently, this handles objects of type ``list``, ``tuple``, ``set``,
@ -178,6 +185,7 @@ class BasePersistence(ABC):
new_obj = copy(obj)
if isinstance(obj, (dict, defaultdict)):
new_obj = cast(dict, new_obj)
new_obj.clear()
for k, v in obj.items():
new_obj[self.insert_bot(k)] = self.insert_bot(v)
@ -194,7 +202,7 @@ class BasePersistence(ABC):
return obj
@abstractmethod
def get_user_data(self):
def get_user_data(self) -> DefaultDict[int, Dict[Any, Any]]:
""""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
persistence object. It should return the user_data if stored, or an empty
``defaultdict(dict)``.
@ -204,7 +212,7 @@ class BasePersistence(ABC):
"""
@abstractmethod
def get_chat_data(self):
def get_chat_data(self) -> DefaultDict[int, Dict[Any, Any]]:
""""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
persistence object. It should return the chat_data if stored, or an empty
``defaultdict(dict)``.
@ -214,7 +222,7 @@ class BasePersistence(ABC):
"""
@abstractmethod
def get_bot_data(self):
def get_bot_data(self) -> Dict[Any, Any]:
""""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
persistence object. It should return the bot_data if stored, or an empty
:obj:`dict`.
@ -224,7 +232,7 @@ class BasePersistence(ABC):
"""
@abstractmethod
def get_conversations(self, name):
def get_conversations(self, name: str) -> ConversationDict:
""""Will be called by :class:`telegram.ext.Dispatcher` when a
:class:`telegram.ext.ConversationHandler` is added if
:attr:`telegram.ext.ConversationHandler.persistent` is :obj:`True`.
@ -238,7 +246,9 @@ class BasePersistence(ABC):
"""
@abstractmethod
def update_conversation(self, name, key, new_state):
def update_conversation(self,
name: str, key: Tuple[int, ...],
new_state: Optional[object]) -> None:
"""Will be called when a :attr:`telegram.ext.ConversationHandler.update_state`
is called. This allows the storage of the new state in the persistence.
@ -249,7 +259,7 @@ class BasePersistence(ABC):
"""
@abstractmethod
def update_user_data(self, user_id, data):
def update_user_data(self, user_id: int, data: Dict) -> None:
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
handled an update.
@ -259,7 +269,7 @@ class BasePersistence(ABC):
"""
@abstractmethod
def update_chat_data(self, chat_id, data):
def update_chat_data(self, chat_id: int, data: Dict) -> None:
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
handled an update.
@ -269,7 +279,7 @@ class BasePersistence(ABC):
"""
@abstractmethod
def update_bot_data(self, data):
def update_bot_data(self, data: Dict) -> None:
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
handled an update.
@ -277,7 +287,7 @@ class BasePersistence(ABC):
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.bot_data` .
"""
def flush(self):
def flush(self) -> None:
"""Will be called by :class:`telegram.ext.Updater` upon receiving a stop signal. Gives the
persistence a chance to finish up saving or close a database connection gracefully. If this
is not of any importance just pass will be sufficient.

View file

@ -17,8 +17,13 @@
# 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 CallbackContext class."""
from queue import Queue
from typing import Dict, Any, Tuple, TYPE_CHECKING, Optional, Match, List, NoReturn, Union
from telegram import Update
if TYPE_CHECKING:
from telegram import Bot
from telegram.ext import Dispatcher, Job, JobQueue
class CallbackContext:
@ -80,7 +85,7 @@ class CallbackContext:
"""
def __init__(self, dispatcher):
def __init__(self, dispatcher: 'Dispatcher'):
"""
Args:
dispatcher (:class:`telegram.ext.Dispatcher`):
@ -90,49 +95,54 @@ class CallbackContext:
'dispatcher!')
self._dispatcher = dispatcher
self._bot_data = dispatcher.bot_data
self._chat_data = None
self._user_data = None
self.args = None
self.matches = None
self.error = None
self.job = None
self.async_args = None
self.async_kwargs = None
self._chat_data: Optional[Dict[Any, Any]] = None
self._user_data: Optional[Dict[Any, Any]] = None
self.args: Optional[List[str]] = None
self.matches: Optional[List[Match]] = None
self.error: Optional[Exception] = None
self.job: Optional['Job'] = None
self.async_args: Optional[Union[List, Tuple]] = None
self.async_kwargs: Optional[Dict[str, Any]] = None
@property
def dispatcher(self):
def dispatcher(self) -> 'Dispatcher':
""":class:`telegram.ext.Dispatcher`: The dispatcher associated with this context."""
return self._dispatcher
@property
def bot_data(self):
def bot_data(self) -> Dict:
return self._bot_data
@bot_data.setter
def bot_data(self, value):
def bot_data(self, value: Any) -> NoReturn:
raise AttributeError("You can not assign a new value to bot_data, see "
"https://git.io/fjxKe")
@property
def chat_data(self):
def chat_data(self) -> Optional[Dict]:
return self._chat_data
@chat_data.setter
def chat_data(self, value):
def chat_data(self, value: Any) -> NoReturn:
raise AttributeError("You can not assign a new value to chat_data, see "
"https://git.io/fjxKe")
@property
def user_data(self):
def user_data(self) -> Optional[Dict]:
return self._user_data
@user_data.setter
def user_data(self, value):
def user_data(self, value: Any) -> NoReturn:
raise AttributeError("You can not assign a new value to user_data, see "
"https://git.io/fjxKe")
@classmethod
def from_error(cls, update, error, dispatcher, async_args=None, async_kwargs=None):
def from_error(cls,
update: object,
error: Exception,
dispatcher: 'Dispatcher',
async_args: Union[List, Tuple] = None,
async_kwargs: Dict[str, Any] = None) -> 'CallbackContext':
self = cls.from_update(update, dispatcher)
self.error = error
self.async_args = async_args
@ -140,7 +150,7 @@ class CallbackContext:
return self
@classmethod
def from_update(cls, update, dispatcher):
def from_update(cls, update: object, dispatcher: 'Dispatcher') -> 'CallbackContext':
self = cls(dispatcher)
if update is not None and isinstance(update, Update):
@ -154,21 +164,21 @@ class CallbackContext:
return self
@classmethod
def from_job(cls, job, dispatcher):
def from_job(cls, job: 'Job', dispatcher: 'Dispatcher') -> 'CallbackContext':
self = cls(dispatcher)
self.job = job
return self
def update(self, data):
def update(self, data: Dict[str, Any]) -> None:
self.__dict__.update(data)
@property
def bot(self):
def bot(self) -> 'Bot':
""":class:`telegram.Bot`: The bot associated with this context."""
return self._dispatcher.bot
@property
def job_queue(self):
def job_queue(self) -> Optional['JobQueue']:
"""
:class:`telegram.ext.JobQueue`: The ``JobQueue`` used by the
:class:`telegram.ext.Dispatcher` and (usually) the :class:`telegram.ext.Updater`
@ -178,7 +188,7 @@ class CallbackContext:
return self._dispatcher.job_queue
@property
def update_queue(self):
def update_queue(self) -> Queue:
"""
:class:`queue.Queue`: The ``Queue`` instance used by the
:class:`telegram.ext.Dispatcher` and (usually) the :class:`telegram.ext.Updater`
@ -188,13 +198,13 @@ class CallbackContext:
return self._dispatcher.update_queue
@property
def match(self):
def match(self) -> Optional[Match[str]]:
"""
`Regex match type`: The first match from :attr:`matches`.
Useful if you are only filtering using a single regex filter.
Returns `None` if :attr:`matches` is empty.
"""
try:
return self.matches[0] # pylint: disable=unsubscriptable-object
return self.matches[0] # type: ignore[index] # pylint: disable=unsubscriptable-object
except (IndexError, TypeError):
return None

View file

@ -23,6 +23,15 @@ import re
from telegram import Update
from .handler import Handler
from telegram.utils.types import HandlerArg
from typing import Callable, TYPE_CHECKING, Any, Optional, Union, TypeVar, Pattern, Match, Dict, \
cast
if TYPE_CHECKING:
from telegram.ext import CallbackContext, Dispatcher
RT = TypeVar('RT')
class CallbackQueryHandler(Handler):
"""Handler class to handle Telegram callback queries. Optionally based on a regex.
@ -102,15 +111,15 @@ class CallbackQueryHandler(Handler):
"""
def __init__(self,
callback,
pass_update_queue=False,
pass_job_queue=False,
pattern=None,
pass_groups=False,
pass_groupdict=False,
pass_user_data=False,
pass_chat_data=False,
run_async=False):
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
pass_update_queue: bool = False,
pass_job_queue: bool = False,
pattern: Union[str, Pattern] = None,
pass_groups: bool = False,
pass_groupdict: bool = False,
pass_user_data: bool = False,
pass_chat_data: bool = False,
run_async: bool = False):
super().__init__(
callback,
pass_update_queue=pass_update_queue,
@ -126,7 +135,7 @@ class CallbackQueryHandler(Handler):
self.pass_groups = pass_groups
self.pass_groupdict = pass_groupdict
def check_update(self, update):
def check_update(self, update: HandlerArg) -> Optional[Union[bool, object]]:
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:
@ -144,16 +153,26 @@ class CallbackQueryHandler(Handler):
return match
else:
return True
return None
def collect_optional_args(self, dispatcher, update=None, check_result=None):
def collect_optional_args(self,
dispatcher: 'Dispatcher',
update: HandlerArg = None,
check_result: Union[bool, Match] = None) -> Dict[str, Any]:
optional_args = super().collect_optional_args(dispatcher, update, check_result)
if self.pattern:
check_result = cast(Match, check_result)
if self.pass_groups:
optional_args['groups'] = check_result.groups()
if self.pass_groupdict:
optional_args['groupdict'] = check_result.groupdict()
return optional_args
def collect_additional_context(self, context, update, dispatcher, check_result):
def collect_additional_context(self,
context: 'CallbackContext',
update: HandlerArg,
dispatcher: 'Dispatcher',
check_result: Union[bool, Match]) -> None:
if self.pattern:
check_result = cast(Match, check_result)
context.matches = [check_result]

View file

@ -21,6 +21,10 @@
from telegram import Update
from .handler import Handler
from telegram.utils.types import HandlerArg
from typing import Optional, Union, TypeVar
RT = TypeVar('RT')
class ChosenInlineResultHandler(Handler):
"""Handler class to handle Telegram updates that contain a chosen inline result.
@ -80,7 +84,7 @@ class ChosenInlineResultHandler(Handler):
"""
def check_update(self, update):
def check_update(self, update: HandlerArg) -> Optional[Union[bool, object]]:
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:

View file

@ -20,12 +20,19 @@
import re
import warnings
from telegram.ext import Filters
from telegram.ext import Filters, BaseFilter
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram import Update, MessageEntity
from .handler import Handler
from telegram.utils.types import HandlerArg
from typing import Callable, TYPE_CHECKING, Any, Optional, Union, TypeVar, Dict, List, Tuple
if TYPE_CHECKING:
from telegram.ext import CallbackContext, Dispatcher
RT = TypeVar('RT')
class CommandHandler(Handler):
"""Handler class to handle Telegram commands.
@ -124,16 +131,16 @@ class CommandHandler(Handler):
"""
def __init__(self,
command,
callback,
filters=None,
allow_edited=None,
pass_args=False,
pass_update_queue=False,
pass_job_queue=False,
pass_user_data=False,
pass_chat_data=False,
run_async=False):
command: Union[str, List[str]],
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
filters: BaseFilter = None,
allow_edited: bool = None,
pass_args: bool = False,
pass_update_queue: bool = False,
pass_job_queue: bool = False,
pass_user_data: bool = False,
pass_chat_data: bool = False,
run_async: bool = False):
super().__init__(
callback,
pass_update_queue=pass_update_queue,
@ -163,7 +170,10 @@ class CommandHandler(Handler):
self.filters &= ~Filters.update.edited_message
self.pass_args = pass_args
def check_update(self, update):
def check_update(
self,
update: HandlerArg) -> Optional[Union[bool, Tuple[List[str],
Optional[Union[bool, Dict]]]]]:
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:
@ -177,14 +187,14 @@ class CommandHandler(Handler):
message = update.effective_message
if (message.entities and message.entities[0].type == MessageEntity.BOT_COMMAND
and message.entities[0].offset == 0):
and message.entities[0].offset == 0 and message.text and message.bot):
command = message.text[1:message.entities[0].length]
args = message.text.split()[1:]
command = command.split('@')
command.append(message.bot.username)
command_parts = command.split('@')
command_parts.append(message.bot.username)
if not (command[0].lower() in self.command
and command[1].lower() == message.bot.username.lower()):
if not (command_parts[0].lower() in self.command
and command_parts[1].lower() == message.bot.username.lower()):
return None
filter_result = self.filters(update)
@ -192,17 +202,29 @@ class CommandHandler(Handler):
return args, filter_result
else:
return False
return None
def collect_optional_args(self, dispatcher, update=None, check_result=None):
def collect_optional_args(
self,
dispatcher: 'Dispatcher',
update: HandlerArg = None,
check_result: Optional[Union[bool, Tuple[List[str],
Optional[bool]]]] = None) -> Dict[str, Any]:
optional_args = super().collect_optional_args(dispatcher, update)
if self.pass_args:
if self.pass_args and isinstance(check_result, tuple):
optional_args['args'] = check_result[0]
return optional_args
def collect_additional_context(self, context, update, dispatcher, check_result):
context.args = check_result[0]
if isinstance(check_result[1], dict):
context.update(check_result[1])
def collect_additional_context(
self,
context: 'CallbackContext',
update: HandlerArg,
dispatcher: 'Dispatcher',
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]]) -> None:
if isinstance(check_result, tuple):
context.args = check_result[0]
if isinstance(check_result[1], dict):
context.update(check_result[1])
class PrefixHandler(CommandHandler):
@ -309,20 +331,20 @@ class PrefixHandler(CommandHandler):
"""
def __init__(self,
prefix,
command,
callback,
filters=None,
pass_args=False,
pass_update_queue=False,
pass_job_queue=False,
pass_user_data=False,
pass_chat_data=False,
run_async=False):
prefix: Union[str, List[str]],
command: Union[str, List[str]],
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
filters: BaseFilter = None,
pass_args: bool = False,
pass_update_queue: bool = False,
pass_job_queue: bool = False,
pass_user_data: bool = False,
pass_chat_data: bool = False,
run_async: bool = False):
self._prefix = list()
self._command = list()
self._commands = list()
self._prefix: List[str] = list()
self._command: List[str] = list()
self._commands: List[str] = list()
super().__init__(
'nocommand', callback, filters=filters, allow_edited=None, pass_args=pass_args,
@ -332,38 +354,39 @@ class PrefixHandler(CommandHandler):
pass_chat_data=pass_chat_data,
run_async=run_async)
self.prefix = prefix
self.command = command
self.prefix = prefix # type: ignore[assignment]
self.command = command # type: ignore[assignment]
self._build_commands()
@property
def prefix(self):
def prefix(self) -> List[str]:
return self._prefix
@prefix.setter
def prefix(self, prefix):
def prefix(self, prefix: Union[str, List[str]]) -> None:
if isinstance(prefix, str):
self._prefix = [prefix.lower()]
else:
self._prefix = prefix
self._build_commands()
@property
def command(self):
@property # type: ignore[override]
def command(self) -> List[str]: # type: ignore[override]
return self._command
@command.setter
def command(self, command):
def command(self, command: Union[str, List[str]]) -> None:
if isinstance(command, str):
self._command = [command.lower()]
else:
self._command = command
self._build_commands()
def _build_commands(self):
def _build_commands(self) -> None:
self._commands = [x.lower() + y.lower() for x in self.prefix for y in self.command]
def check_update(self, update):
def check_update(self, update: HandlerArg) -> Optional[Union[bool, Tuple[List[str],
Optional[Union[bool, Dict]]]]]:
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:
@ -385,8 +408,15 @@ class PrefixHandler(CommandHandler):
return text_list[1:], filter_result
else:
return False
return None
def collect_additional_context(self, context, update, dispatcher, check_result):
context.args = check_result[0]
if isinstance(check_result[1], dict):
context.update(check_result[1])
def collect_additional_context(
self,
context: 'CallbackContext',
update: HandlerArg,
dispatcher: 'Dispatcher',
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]]) -> None:
if isinstance(check_result, tuple):
context.args = check_result[0]
if isinstance(check_result[1], dict):
context.update(check_result[1])

View file

@ -24,12 +24,24 @@ from threading import Lock
from telegram import Update
from telegram.ext import (Handler, CallbackQueryHandler, InlineQueryHandler,
ChosenInlineResultHandler, CallbackContext, DispatcherHandlerStop)
ChosenInlineResultHandler, CallbackContext, BasePersistence,
DispatcherHandlerStop)
from telegram.utils.promise import Promise
from telegram.utils.types import ConversationDict, HandlerArg
from typing import Dict, Any, List, Optional, Tuple, TYPE_CHECKING, cast, NoReturn
if TYPE_CHECKING:
from telegram.ext import Dispatcher, Job
CheckUpdateType = Optional[Tuple[Tuple[int, ...], Handler, object]]
class _ConversationTimeoutContext:
def __init__(self, conversation_key, update, dispatcher, callback_context):
def __init__(self,
conversation_key: Tuple[int, ...],
update: Update,
dispatcher: 'Dispatcher',
callback_context: Optional[CallbackContext]):
self.conversation_key = conversation_key
self.update = update
self.dispatcher = dispatcher
@ -157,17 +169,17 @@ class ConversationHandler(Handler):
previous ``@run_sync`` decorated running handler to finish."""
def __init__(self,
entry_points,
states,
fallbacks,
allow_reentry=False,
per_chat=True,
per_user=True,
per_message=False,
conversation_timeout=None,
name=None,
persistent=False,
map_to_parent=None):
entry_points: List[Handler],
states: Dict[object, List[Handler]],
fallbacks: List[Handler],
allow_reentry: bool = False,
per_chat: bool = True,
per_user: bool = True,
per_message: bool = False,
conversation_timeout: int = None,
name: str = None,
persistent: bool = False,
map_to_parent: Dict[object, object] = None):
self.run_async = False
self._entry_points = entry_points
@ -182,15 +194,15 @@ class ConversationHandler(Handler):
self._name = name
if persistent and not self.name:
raise ValueError("Conversations can't be persistent when handler is unnamed.")
self.persistent = persistent
self._persistence = None
self.persistent: bool = persistent
self._persistence: Optional[BasePersistence] = None
""":obj:`telegram.ext.BasePersistence`: The persistence used to store conversations.
Set by dispatcher"""
self._map_to_parent = map_to_parent
self.timeout_jobs = dict()
self.timeout_jobs: Dict[Tuple[int, ...], 'Job'] = dict()
self._timeout_jobs_lock = Lock()
self._conversations = dict()
self._conversations: ConversationDict = dict()
self._conversations_lock = Lock()
self.logger = logging.getLogger(__name__)
@ -231,92 +243,92 @@ class ConversationHandler(Handler):
break
@property
def entry_points(self):
def entry_points(self) -> List[Handler]:
return self._entry_points
@entry_points.setter
def entry_points(self, value):
def entry_points(self, value: Any) -> NoReturn:
raise ValueError('You can not assign a new value to entry_points after initialization.')
@property
def states(self):
def states(self) -> Dict[object, List[Handler]]:
return self._states
@states.setter
def states(self, value):
def states(self, value: Any) -> NoReturn:
raise ValueError('You can not assign a new value to states after initialization.')
@property
def fallbacks(self):
def fallbacks(self) -> List[Handler]:
return self._fallbacks
@fallbacks.setter
def fallbacks(self, value):
def fallbacks(self, value: Any) -> NoReturn:
raise ValueError('You can not assign a new value to fallbacks after initialization.')
@property
def allow_reentry(self):
def allow_reentry(self) -> bool:
return self._allow_reentry
@allow_reentry.setter
def allow_reentry(self, value):
def allow_reentry(self, value: Any) -> NoReturn:
raise ValueError('You can not assign a new value to allow_reentry after initialization.')
@property
def per_user(self):
def per_user(self) -> bool:
return self._per_user
@per_user.setter
def per_user(self, value):
def per_user(self, value: Any) -> NoReturn:
raise ValueError('You can not assign a new value to per_user after initialization.')
@property
def per_chat(self):
def per_chat(self) -> bool:
return self._per_chat
@per_chat.setter
def per_chat(self, value):
def per_chat(self, value: Any) -> NoReturn:
raise ValueError('You can not assign a new value to per_chat after initialization.')
@property
def per_message(self):
def per_message(self) -> bool:
return self._per_message
@per_message.setter
def per_message(self, value):
def per_message(self, value: Any) -> NoReturn:
raise ValueError('You can not assign a new value to per_message after initialization.')
@property
def conversation_timeout(self):
def conversation_timeout(self) -> Optional[int]:
return self._conversation_timeout
@conversation_timeout.setter
def conversation_timeout(self, value):
def conversation_timeout(self, value: Any) -> NoReturn:
raise ValueError('You can not assign a new value to conversation_timeout after '
'initialization.')
@property
def name(self):
def name(self) -> Optional[str]:
return self._name
@name.setter
def name(self, value):
def name(self, value: Any) -> NoReturn:
raise ValueError('You can not assign a new value to name after initialization.')
@property
def map_to_parent(self):
def map_to_parent(self) -> Optional[Dict[object, object]]:
return self._map_to_parent
@map_to_parent.setter
def map_to_parent(self, value):
def map_to_parent(self, value: Any) -> NoReturn:
raise ValueError('You can not assign a new value to map_to_parent after initialization.')
@property
def persistence(self):
def persistence(self) -> Optional[BasePersistence]:
return self._persistence
@persistence.setter
def persistence(self, persistence):
def persistence(self, persistence: BasePersistence) -> None:
self._persistence = persistence
# Set persistence for nested conversations
for handlers in self.states.values():
@ -325,37 +337,37 @@ class ConversationHandler(Handler):
handler.persistence = self.persistence
@property
def conversations(self):
def conversations(self) -> ConversationDict:
return self._conversations
@conversations.setter
def conversations(self, value):
def conversations(self, value: ConversationDict) -> None:
self._conversations = value
# Set conversations for nested conversations
for handlers in self.states.values():
for handler in handlers:
if isinstance(handler, ConversationHandler):
if isinstance(handler, ConversationHandler) and self.persistence and handler.name:
handler.conversations = self.persistence.get_conversations(handler.name)
def _get_key(self, update):
def _get_key(self, update: Update) -> Tuple[int, ...]:
chat = update.effective_chat
user = update.effective_user
key = list()
if self.per_chat:
key.append(chat.id)
key.append(chat.id) # type: ignore[union-attr]
if self.per_user and user is not None:
key.append(user.id)
if self.per_message:
key.append(update.callback_query.inline_message_id
or update.callback_query.message.message_id)
key.append(update.callback_query.inline_message_id # type: ignore[union-attr]
or update.callback_query.message.message_id) # type: ignore[union-attr]
return tuple(key)
def check_update(self, update):
def check_update(self, update: HandlerArg) -> CheckUpdateType:
"""
Determines whether an update should be handled by this conversationhandler, and if so in
which state the conversation currently is.
@ -399,11 +411,11 @@ class ConversationHandler(Handler):
with self._conversations_lock:
state = self.conversations.get(key)
else:
handlers = self.states.get(self.WAITING, [])
for handler in handlers:
check = handler.check_update(update)
hdlrs = self.states.get(self.WAITING, [])
for hdlr in hdlrs:
check = hdlr.check_update(update)
if check is not None and check is not False:
return key, handler, check
return key, hdlr, check
return None
self.logger.debug('selecting conversation {} with state {}'.format(str(key), str(state)))
@ -443,9 +455,13 @@ class ConversationHandler(Handler):
else:
return None
return key, handler, check
return key, handler, check # type: ignore[return-value]
def handle_update(self, update, dispatcher, check_result, context=None):
def handle_update(self, # type: ignore[override]
update: HandlerArg,
dispatcher: 'Dispatcher',
check_result: CheckUpdateType,
context: CallbackContext = None) -> Optional[object]:
"""Send the update to the callback for the current state and Handler
Args:
@ -453,9 +469,12 @@ class ConversationHandler(Handler):
handler, and the handler's check result.
update (:class:`telegram.Update`): Incoming telegram update.
dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update.
context (:class:`telegram.ext.CallbackContext`, optional): The context as provided by
the dispatcher.
"""
conversation_key, handler, check_result = check_result
update = cast(Update, update) # for mypy
conversation_key, handler, check_result = check_result # type: ignore[assignment,misc]
raise_dp_handler_stop = False
with self._timeout_jobs_lock:
@ -464,18 +483,16 @@ class ConversationHandler(Handler):
if timeout_job is not None:
timeout_job.schedule_removal()
try:
new_state = handler.handle_update(update, dispatcher, check_result, context)
except DispatcherHandlerStop as e:
new_state = e.state
raise_dp_handler_stop = True
with self._timeout_jobs_lock:
if self.conversation_timeout and new_state != self.END:
if self.conversation_timeout and new_state != self.END and dispatcher.job_queue:
# Add the new timeout job
self.timeout_jobs[conversation_key] = dispatcher.job_queue.run_once(
self._trigger_timeout, self.conversation_timeout,
self._trigger_timeout, self.conversation_timeout, # type: ignore[arg-type]
context=_ConversationTimeoutContext(conversation_key, update,
dispatcher, context))
@ -491,30 +508,35 @@ class ConversationHandler(Handler):
# Don't pass the new state here. If we're in a nested conversation, the parent is
# expecting None as return value.
raise DispatcherHandlerStop()
return None
def update_state(self, new_state, key):
def update_state(self,
new_state: object,
key: Tuple[int, ...]) -> None:
if new_state == self.END:
with self._conversations_lock:
if key in self.conversations:
# If there is no key in conversations, nothing is done.
del self.conversations[key]
if self.persistent:
if self.persistent and self.persistence and self.name:
self.persistence.update_conversation(self.name, key, None)
elif isinstance(new_state, Promise):
with self._conversations_lock:
self.conversations[key] = (self.conversations.get(key), new_state)
if self.persistent:
if self.persistent and self.persistence and self.name:
self.persistence.update_conversation(self.name, key,
(self.conversations.get(key), new_state))
elif new_state is not None:
with self._conversations_lock:
self.conversations[key] = new_state
if self.persistent:
if self.persistent and self.persistence and self.name:
self.persistence.update_conversation(self.name, key, new_state)
def _trigger_timeout(self, context, job=None):
def _trigger_timeout(self,
context: _ConversationTimeoutContext,
job: 'Job' = None) -> None:
self.logger.debug('conversation timeout was triggered!')
# Backward compatibility with bots that do not use CallbackContext
@ -522,7 +544,7 @@ class ConversationHandler(Handler):
if isinstance(context, CallbackContext):
job = context.job
context = job.context
context = job.context # type:ignore[union-attr,assignment]
callback_context = context.callback_context
with self._timeout_jobs_lock:

View file

@ -18,8 +18,9 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the class Defaults, which allows to pass default values to Updater."""
import pytz
from typing import Union, Optional, Any, NoReturn
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
class Defaults:
@ -60,14 +61,14 @@ class Defaults:
``pytz`` module. Defaults to UTC.
"""
def __init__(self,
parse_mode=None,
disable_notification=None,
disable_web_page_preview=None,
parse_mode: str = None,
disable_notification: bool = None,
disable_web_page_preview: bool = None,
# Timeout needs special treatment, since the bot methods have two different
# default values for timeout (None and 20s)
timeout=DEFAULT_NONE,
quote=None,
tzinfo=pytz.utc):
timeout: Union[float, DefaultValue] = DEFAULT_NONE,
quote: bool = None,
tzinfo: pytz.BaseTzInfo = pytz.utc):
self._parse_mode = parse_mode
self._disable_notification = disable_notification
self._disable_web_page_preview = disable_web_page_preview
@ -76,60 +77,60 @@ class Defaults:
self._tzinfo = tzinfo
@property
def parse_mode(self):
def parse_mode(self) -> Optional[str]:
return self._parse_mode
@parse_mode.setter
def parse_mode(self, value):
def parse_mode(self, value: Any) -> NoReturn:
raise AttributeError("You can not assign a new value to defaults after because it would "
"not have any effect.")
@property
def disable_notification(self):
def disable_notification(self) -> Optional[bool]:
return self._disable_notification
@disable_notification.setter
def disable_notification(self, value):
def disable_notification(self, value: Any) -> NoReturn:
raise AttributeError("You can not assign a new value to defaults after because it would "
"not have any effect.")
@property
def disable_web_page_preview(self):
def disable_web_page_preview(self) -> Optional[bool]:
return self._disable_web_page_preview
@disable_web_page_preview.setter
def disable_web_page_preview(self, value):
def disable_web_page_preview(self, value: Any) -> NoReturn:
raise AttributeError("You can not assign a new value to defaults after because it would "
"not have any effect.")
@property
def timeout(self):
def timeout(self) -> Union[float, DefaultValue]:
return self._timeout
@timeout.setter
def timeout(self, value):
def timeout(self, value: Any) -> NoReturn:
raise AttributeError("You can not assign a new value to defaults after because it would "
"not have any effect.")
@property
def quote(self):
def quote(self) -> Optional[bool]:
return self._quote
@quote.setter
def quote(self, value):
def quote(self, value: Any) -> NoReturn:
raise AttributeError("You can not assign a new value to defaults after because it would "
"not have any effect.")
@property
def tzinfo(self):
def tzinfo(self) -> pytz.BaseTzInfo:
return self._tzinfo
@tzinfo.setter
def tzinfo(self, value):
def tzinfo(self, value: Any) -> NoReturn:
raise AttributeError("You can not assign a new value to defaults after because it would "
"not have any effect.")
def __hash__(self):
def __hash__(self) -> int:
return hash((self._parse_mode,
self._disable_notification,
self._disable_web_page_preview,
@ -137,10 +138,10 @@ class Defaults:
self._quote,
self._tzinfo))
def __eq__(self, other):
def __eq__(self, other: object) -> bool:
if isinstance(other, Defaults):
return self.__dict__ == other.__dict__
return False
def __ne__(self, other):
def __ne__(self, other: object) -> bool:
return not self == other

View file

@ -25,10 +25,13 @@ from telegram.utils.helpers import decode_user_chat_data_from_json,\
try:
import ujson as json
except ImportError:
import json
import json # type: ignore[no-redef]
from collections import defaultdict
from telegram.ext import BasePersistence
from typing import DefaultDict, Dict, Any, Tuple, Optional
from telegram.utils.types import ConversationDict
class DictPersistence(BasePersistence):
"""Using python's dicts and json for making your bot persistent.
@ -74,13 +77,13 @@ class DictPersistence(BasePersistence):
"""
def __init__(self,
store_user_data=True,
store_chat_data=True,
store_bot_data=True,
user_data_json='',
chat_data_json='',
bot_data_json='',
conversations_json=''):
store_user_data: bool = True,
store_chat_data: bool = True,
store_bot_data: bool = True,
user_data_json: str = '',
chat_data_json: str = '',
bot_data_json: str = '',
conversations_json: str = ''):
super().__init__(store_user_data=store_user_data,
store_chat_data=store_chat_data,
store_bot_data=store_bot_data)
@ -121,12 +124,12 @@ class DictPersistence(BasePersistence):
raise TypeError("Unable to deserialize conversations_json. Not valid JSON")
@property
def user_data(self):
def user_data(self) -> Optional[DefaultDict[int, Dict]]:
""":obj:`dict`: The user_data as a dict."""
return self._user_data
@property
def user_data_json(self):
def user_data_json(self) -> str:
""":obj:`str`: The user_data serialized as a JSON-string."""
if self._user_data_json:
return self._user_data_json
@ -134,12 +137,12 @@ class DictPersistence(BasePersistence):
return json.dumps(self.user_data)
@property
def chat_data(self):
def chat_data(self) -> Optional[DefaultDict[int, Dict]]:
""":obj:`dict`: The chat_data as a dict."""
return self._chat_data
@property
def chat_data_json(self):
def chat_data_json(self) -> str:
""":obj:`str`: The chat_data serialized as a JSON-string."""
if self._chat_data_json:
return self._chat_data_json
@ -147,12 +150,12 @@ class DictPersistence(BasePersistence):
return json.dumps(self.chat_data)
@property
def bot_data(self):
def bot_data(self) -> Optional[Dict]:
""":obj:`dict`: The bot_data as a dict."""
return self._bot_data
@property
def bot_data_json(self):
def bot_data_json(self) -> str:
""":obj:`str`: The bot_data serialized as a JSON-string."""
if self._bot_data_json:
return self._bot_data_json
@ -160,19 +163,19 @@ class DictPersistence(BasePersistence):
return json.dumps(self.bot_data)
@property
def conversations(self):
def conversations(self) -> Optional[Dict[str, Dict[Tuple, Any]]]:
""":obj:`dict`: The conversations as a dict."""
return self._conversations
@property
def conversations_json(self):
def conversations_json(self) -> str:
""":obj:`str`: The conversations serialized as a JSON-string."""
if self._conversations_json:
return self._conversations_json
else:
return encode_conversations_to_json(self.conversations)
return encode_conversations_to_json(self.conversations) # type: ignore[arg-type]
def get_user_data(self):
def get_user_data(self) -> DefaultDict[int, Dict[Any, Any]]:
"""Returns the user_data created from the ``user_data_json`` or an empty
:obj:`defaultdict`.
@ -183,9 +186,9 @@ class DictPersistence(BasePersistence):
pass
else:
self._user_data = defaultdict(dict)
return deepcopy(self.user_data)
return deepcopy(self.user_data) # type: ignore[arg-type]
def get_chat_data(self):
def get_chat_data(self) -> DefaultDict[int, Dict[Any, Any]]:
"""Returns the chat_data created from the ``chat_data_json`` or an empty
:obj:`defaultdict`.
@ -196,9 +199,9 @@ class DictPersistence(BasePersistence):
pass
else:
self._chat_data = defaultdict(dict)
return deepcopy(self.chat_data)
return deepcopy(self.chat_data) # type: ignore[arg-type]
def get_bot_data(self):
def get_bot_data(self) -> Dict[Any, Any]:
"""Returns the bot_data created from the ``bot_data_json`` or an empty :obj:`dict`.
Returns:
@ -208,9 +211,9 @@ class DictPersistence(BasePersistence):
pass
else:
self._bot_data = {}
return deepcopy(self.bot_data)
return deepcopy(self.bot_data) # type: ignore[arg-type]
def get_conversations(self, name):
def get_conversations(self, name: str) -> ConversationDict:
"""Returns the conversations created from the ``conversations_json`` or an empty
:obj:`dict`.
@ -221,9 +224,11 @@ class DictPersistence(BasePersistence):
pass
else:
self._conversations = {}
return self.conversations.get(name, {}).copy()
return self.conversations.get(name, {}).copy() # type: ignore[union-attr]
def update_conversation(self, name, key, new_state):
def update_conversation(self,
name: str, key: Tuple[int, ...],
new_state: Optional[object]) -> None:
"""Will update the conversations for the given handler.
Args:
@ -231,12 +236,14 @@ class DictPersistence(BasePersistence):
key (:obj:`tuple`): The key the state is changed for.
new_state (:obj:`tuple` | :obj:`any`): The new state for the given key.
"""
if not self._conversations:
self._conversations = {}
if self._conversations.setdefault(name, {}).get(key) == new_state:
return
self._conversations[name][key] = new_state
self._conversations_json = None
def update_user_data(self, user_id, data):
def update_user_data(self, user_id: int, data: Dict) -> None:
"""Will update the user_data (if changed).
Args:
@ -250,7 +257,7 @@ class DictPersistence(BasePersistence):
self._user_data[user_id] = data
self._user_data_json = None
def update_chat_data(self, chat_id, data):
def update_chat_data(self, chat_id: int, data: Dict) -> None:
"""Will update the chat_data (if changed).
Args:
@ -264,7 +271,7 @@ class DictPersistence(BasePersistence):
self._chat_data[chat_id] = data
self._chat_data_json = None
def update_bot_data(self, data):
def update_bot_data(self, data: Dict) -> None:
"""Will update the bot_data (if changed).
Args:

View file

@ -36,10 +36,19 @@ from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram.utils.promise import Promise
from telegram.ext import BasePersistence
from typing import Any, Callable, TYPE_CHECKING, Optional, Union, DefaultDict, Dict, List, Set
from telegram.utils.types import HandlerArg
if TYPE_CHECKING:
from telegram import Bot
from telegram.ext import JobQueue
DEFAULT_GROUP = 0
def run_async(func):
def run_async(func: Callable[[Update, CallbackContext],
Any]) -> Callable[[Update, CallbackContext], Any]:
"""
Function decorator that will run the function in a new thread.
@ -57,7 +66,7 @@ def run_async(func):
"""
@wraps(func)
def async_func(*args, **kwargs):
def async_func(*args: Any, **kwargs: Any) -> Any:
warnings.warn('The @run_async decorator is deprecated. Use the `run_async` parameter of'
'`Dispatcher.add_handler` or `Dispatcher.run_async` instead.',
TelegramDeprecationWarning,
@ -87,7 +96,7 @@ class DispatcherHandlerStop(Exception):
Args:
state (:obj:`object`, optional): The next state of the conversation.
"""
def __init__(self, state=None):
def __init__(self, state: object = None) -> None:
super().__init__()
self.state = state
@ -129,13 +138,13 @@ class Dispatcher:
logger = logging.getLogger(__name__)
def __init__(self,
bot,
update_queue,
workers=4,
exception_event=None,
job_queue=None,
persistence=None,
use_context=True):
bot: 'Bot',
update_queue: Queue,
workers: int = 4,
exception_event: Event = None,
job_queue: 'JobQueue' = None,
persistence: BasePersistence = None,
use_context: bool = True):
self.bot = bot
self.update_queue = update_queue
self.job_queue = job_queue
@ -146,9 +155,10 @@ class Dispatcher:
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.user_data: DefaultDict[int, Dict[Any, Any]] = defaultdict(dict)
self.chat_data: DefaultDict[int, Dict[Any, Any]] = defaultdict(dict)
self.bot_data = {}
self.persistence: Optional[BasePersistence] = None
self._update_persistence_lock = Lock()
if persistence:
if not isinstance(persistence, BasePersistence):
@ -170,11 +180,11 @@ class Dispatcher:
else:
self.persistence = None
self.handlers = {}
self.handlers: Dict[int, List[Handler]] = {}
"""Dict[:obj:`int`, List[:class:`telegram.ext.Handler`]]: Holds the handlers per group."""
self.groups = []
self.groups: List[int] = []
"""List[:obj:`int`]: A list with all groups."""
self.error_handlers = {}
self.error_handlers: Dict[Callable, bool] = {}
"""Dict[:obj:`callable`, :obj:`bool`]: A dict, where the keys are error handlers and the
values indicate whether they are to be run asynchronously."""
@ -182,22 +192,22 @@ class Dispatcher:
""":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()
self.__async_queue: Queue = Queue()
self.__async_threads: Set[Thread] = 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):
if self.__singleton_semaphore.acquire(blocking=False):
self._set_singleton(self)
else:
self._set_singleton(None)
@property
def exception_event(self):
def exception_event(self) -> Event:
return self.__exception_event
def _init_async_threads(self, base_name, workers):
def _init_async_threads(self, base_name: str, workers: int) -> None:
base_name = '{}_'.format(base_name) if base_name else ''
for i in range(workers):
@ -207,12 +217,12 @@ class Dispatcher:
thread.start()
@classmethod
def _set_singleton(cls, val):
def _set_singleton(cls, val: Optional['Dispatcher']) -> None:
cls.logger.debug('Setting singleton dispatcher as %s', val)
cls.__singleton = weakref.ref(val) if val else None
@classmethod
def get_instance(cls):
def get_instance(cls) -> 'Dispatcher':
"""Get the singleton instance of this class.
Returns:
@ -223,12 +233,12 @@ class Dispatcher:
"""
if cls.__singleton is not None:
return cls.__singleton() # pylint: disable=not-callable
return cls.__singleton() # type: ignore[return-value] # pylint: disable=not-callable
else:
raise RuntimeError('{} not initialized or multiple instances exist'.format(
cls.__name__))
def _pooled(self):
def _pooled(self) -> None:
thr_name = current_thread().getName()
while 1:
promise = self.__async_queue.get()
@ -270,7 +280,11 @@ class Dispatcher:
except Exception:
self.logger.exception('An uncaught error was raised while handling the error.')
def run_async(self, func, *args, update=None, **kwargs):
def run_async(self,
func: Callable[..., Any],
*args: Any,
update: HandlerArg = None,
**kwargs: Any) -> Promise:
"""
Queue a function (with given args/kwargs) to be run asynchronously. Exceptions raised
by the function will be handled by the error handlers registered with
@ -296,13 +310,18 @@ class Dispatcher:
"""
return self._run_async(func, *args, update=update, error_handling=True, **kwargs)
def _run_async(self, func, *args, update=None, error_handling=True, **kwargs):
def _run_async(self,
func: Callable[..., Any],
*args: Any,
update: HandlerArg = None,
error_handling: bool = True,
**kwargs: Any) -> Promise:
# TODO: Remove error_handling parameter once we drop the @run_async decorator
promise = Promise(func, args, kwargs, update=update, error_handling=error_handling)
self.__async_queue.put(promise)
return promise
def start(self, ready=None):
def start(self, ready: Event = None) -> None:
"""Thread target of thread 'dispatcher'.
Runs in background and processes the update queue.
@ -323,7 +342,7 @@ class Dispatcher:
self.logger.error(msg)
raise TelegramError(msg)
self._init_async_threads(uuid4(), self.workers)
self._init_async_threads(str(uuid4()), self.workers)
self.running = True
self.logger.debug('Dispatcher started')
@ -350,7 +369,7 @@ class Dispatcher:
self.running = False
self.logger.debug('Dispatcher thread stopped')
def stop(self):
def stop(self) -> None:
"""Stops the thread."""
if self.running:
self.__stop_event.set()
@ -374,10 +393,10 @@ class Dispatcher:
self.logger.debug('async thread {}/{} has ended'.format(i + 1, total))
@property
def has_running_threads(self):
def has_running_threads(self) -> bool:
return self.running or bool(self.__async_threads)
def process_update(self, update):
def process_update(self, update: Union[str, Update, TelegramError]) -> None:
"""Processes a single update.
Args:
@ -427,7 +446,7 @@ class Dispatcher:
except Exception:
self.logger.exception('An uncaught error was raised while handling the error.')
def add_handler(self, handler, group=DEFAULT_GROUP):
def add_handler(self, handler: Handler, group: int = DEFAULT_GROUP) -> None:
"""Register a handler.
TL;DR: Order and priority counts. 0 or 1 handlers per group will be used. End handling of
@ -459,7 +478,7 @@ class Dispatcher:
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 isinstance(handler, ConversationHandler) and handler.persistent and handler.name:
if not self.persistence:
raise ValueError(
"ConversationHandler {} can not be persistent if dispatcher has no "
@ -474,7 +493,7 @@ class Dispatcher:
self.handlers[group].append(handler)
def remove_handler(self, handler, group=DEFAULT_GROUP):
def remove_handler(self, handler: Handler, group: int = DEFAULT_GROUP) -> None:
"""Remove a handler from the specified group.
Args:
@ -488,7 +507,7 @@ class Dispatcher:
del self.handlers[group]
self.groups.remove(group)
def update_persistence(self, update=None):
def update_persistence(self, update: HandlerArg = None) -> None:
"""Update :attr:`user_data`, :attr:`chat_data` and :attr:`bot_data` in :attr:`persistence`.
Args:
@ -498,7 +517,7 @@ class Dispatcher:
with self._update_persistence_lock:
self.__update_persistence(update)
def __update_persistence(self, update):
def __update_persistence(self, update: HandlerArg = None) -> None:
if self.persistence:
# We use list() here in order to decouple chat_ids from self.chat_data, as dict view
# objects will change, when the dict does and we want to loop over chat_ids
@ -551,7 +570,9 @@ class Dispatcher:
'the error with an error_handler'
self.logger.exception(message)
def add_error_handler(self, callback, run_async=False):
def add_error_handler(self,
callback: Callable[[Any, CallbackContext], None],
run_async: bool = False) -> None:
"""Registers an error handler in the Dispatcher. This handler will receive every error
which happens in your bot.
@ -580,7 +601,7 @@ class Dispatcher:
return
self.error_handlers[callback] = run_async
def remove_error_handler(self, callback):
def remove_error_handler(self, callback: Callable[[Any, CallbackContext], None]) -> None:
"""Removes an error handler.
Args:
@ -589,7 +610,10 @@ class Dispatcher:
"""
self.error_handlers.pop(callback, None)
def dispatch_error(self, update, error, promise=None):
def dispatch_error(self,
update: Optional[HandlerArg],
error: Exception,
promise: Promise = None) -> None:
"""Dispatches an error.
Args:

File diff suppressed because it is too large Load diff

View file

@ -20,6 +20,15 @@
from abc import ABC, abstractmethod
from telegram.utils.promise import Promise
from telegram.utils.types import HandlerArg
from telegram import Update
from typing import Callable, TYPE_CHECKING, Any, Optional, Union, TypeVar, Dict
if TYPE_CHECKING:
from telegram.ext import CallbackContext, Dispatcher
RT = TypeVar('RT')
class Handler(ABC):
"""The base class for all update handlers. Create custom handlers by inheriting from it.
@ -78,15 +87,14 @@ class Handler(ABC):
Defaults to :obj:`False`.
"""
def __init__(self,
callback,
pass_update_queue=False,
pass_job_queue=False,
pass_user_data=False,
pass_chat_data=False,
run_async=False):
self.callback = callback
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
pass_update_queue: bool = False,
pass_job_queue: bool = False,
pass_user_data: bool = False,
pass_chat_data: bool = False,
run_async: bool = False):
self.callback: Callable[[HandlerArg, 'CallbackContext'], RT] = callback
self.pass_update_queue = pass_update_queue
self.pass_job_queue = pass_job_queue
self.pass_user_data = pass_user_data
@ -94,7 +102,7 @@ class Handler(ABC):
self.run_async = run_async
@abstractmethod
def check_update(self, update):
def check_update(self, update: HandlerArg) -> Optional[Union[bool, object]]:
"""
This method is called to determine if an update should be handled by
this handler instance. It should always be overridden.
@ -109,7 +117,11 @@ class Handler(ABC):
"""
def handle_update(self, update, dispatcher, check_result, context=None):
def handle_update(self,
update: HandlerArg,
dispatcher: 'Dispatcher',
check_result: object,
context: 'CallbackContext' = None) -> Union[RT, Promise]:
"""
This method is called if it was determined that an update should indeed
be handled by this instance. Calls :attr:`callback` along with its respectful
@ -120,7 +132,9 @@ class Handler(ABC):
Args:
update (:obj:`str` | :class:`telegram.Update`): The update to be handled.
dispatcher (:class:`telegram.ext.Dispatcher`): The calling dispatcher.
check_result: The result from :attr:`check_update`.
check_result (:obj:`obj`): The result from :attr:`check_update`.
context (:class:`telegram.ext.CallbackContext`, optional): The context as provided by
the dispatcher.
"""
if context:
@ -135,9 +149,13 @@ class Handler(ABC):
return dispatcher.run_async(self.callback, dispatcher.bot, update, update=update,
**optional_args)
else:
return self.callback(dispatcher.bot, update, **optional_args)
return self.callback(dispatcher.bot, update, **optional_args) # type: ignore
def collect_additional_context(self, context, update, dispatcher, check_result):
def collect_additional_context(self,
context: 'CallbackContext',
update: HandlerArg,
dispatcher: 'Dispatcher',
check_result: Any) -> None:
"""Prepares additional arguments for the context. Override if needed.
Args:
@ -149,7 +167,10 @@ class Handler(ABC):
"""
pass
def collect_optional_args(self, dispatcher, update=None, check_result=None):
def collect_optional_args(self,
dispatcher: 'Dispatcher',
update: HandlerArg = None,
check_result: Any = None) -> Dict[str, Any]:
"""
Prepares the optional arguments. If the handler has additional optional args,
it should subclass this method, but remember to call this super method.
@ -163,17 +184,19 @@ class Handler(ABC):
check_result: The result from check_update
"""
optional_args = dict()
optional_args: Dict[str, Any] = dict()
if self.pass_update_queue:
optional_args['update_queue'] = dispatcher.update_queue
if self.pass_job_queue:
optional_args['job_queue'] = dispatcher.job_queue
if self.pass_user_data:
if self.pass_user_data and isinstance(update, Update):
user = update.effective_user
optional_args['user_data'] = dispatcher.user_data[user.id if user else None]
if self.pass_chat_data:
optional_args['user_data'] = dispatcher.user_data[
user.id if user else None] # type: ignore[index]
if self.pass_chat_data and isinstance(update, Update):
chat = update.effective_chat
optional_args['chat_data'] = dispatcher.chat_data[chat.id if chat else None]
optional_args['chat_data'] = dispatcher.chat_data[
chat.id if chat else None] # type: ignore[index]
return optional_args

View file

@ -23,6 +23,15 @@ from telegram import Update
from .handler import Handler
from telegram.utils.types import HandlerArg
from typing import Callable, TYPE_CHECKING, Any, Optional, Union, TypeVar, Dict, Pattern, Match, \
cast
if TYPE_CHECKING:
from telegram.ext import CallbackContext, Dispatcher
RT = TypeVar('RT')
class InlineQueryHandler(Handler):
"""
@ -102,22 +111,22 @@ class InlineQueryHandler(Handler):
"""
def __init__(self,
callback,
pass_update_queue=False,
pass_job_queue=False,
pattern=None,
pass_groups=False,
pass_groupdict=False,
pass_user_data=False,
pass_chat_data=False,
run_async=False):
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
pass_update_queue: bool = False,
pass_job_queue: bool = False,
pattern: Union[str, Pattern] = None,
pass_groups: bool = False,
pass_groupdict: bool = False,
pass_user_data: bool = False,
pass_chat_data: bool = False,
run_async: bool = False):
super().__init__(
callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data,
run_async=False)
run_async=run_async)
if isinstance(pattern, str):
pattern = re.compile(pattern)
@ -126,7 +135,7 @@ class InlineQueryHandler(Handler):
self.pass_groups = pass_groups
self.pass_groupdict = pass_groupdict
def check_update(self, update):
def check_update(self, update: HandlerArg) -> Optional[Union[bool, Match]]:
"""
Determines whether an update should be passed to this handlers :attr:`callback`.
@ -146,16 +155,26 @@ class InlineQueryHandler(Handler):
return match
else:
return True
return None
def collect_optional_args(self, dispatcher, update=None, check_result=None):
def collect_optional_args(self,
dispatcher: 'Dispatcher',
update: HandlerArg = None,
check_result: Optional[Union[bool, Match]] = None) -> Dict[str, Any]:
optional_args = super().collect_optional_args(dispatcher, update, check_result)
if self.pattern:
check_result = cast(Match, check_result)
if self.pass_groups:
optional_args['groups'] = check_result.groups()
if self.pass_groupdict:
optional_args['groupdict'] = check_result.groupdict()
return optional_args
def collect_additional_context(self, context, update, dispatcher, check_result):
def collect_additional_context(self,
context: 'CallbackContext',
update: HandlerArg,
dispatcher: 'Dispatcher',
check_result: Optional[Union[bool, Match]]) -> None:
if self.pattern:
check_result = cast(Match, check_result)
context.matches = [check_result]

View file

@ -25,10 +25,16 @@ import pytz
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from apscheduler.triggers.combining import OrTrigger
from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR, JobEvent
from telegram.ext.callbackcontext import CallbackContext
from typing import TYPE_CHECKING, Union, Callable, Tuple, Optional, List, Any, cast, overload
from telegram.utils.types import JSONDict
if TYPE_CHECKING:
from telegram.ext import Dispatcher
from telegram import Bot
class Days:
MON, TUE, WED, THU, FRI, SAT, SUN = range(7)
@ -46,32 +52,32 @@ class JobQueue:
"""
def __init__(self):
self._dispatcher = None
def __init__(self) -> None:
self._dispatcher: 'Dispatcher' = None # type: ignore[assignment]
self.logger = logging.getLogger(self.__class__.__name__)
self.scheduler = BackgroundScheduler(timezone=pytz.utc)
self.scheduler.add_listener(self._update_persistence,
mask=EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
# Dispatch errors and don't log them in the APS logger
def aps_log_filter(record):
def aps_log_filter(record): # type: ignore
return 'raised an exception' not in record.msg
logging.getLogger('apscheduler.executors.default').addFilter(aps_log_filter)
self.scheduler.add_listener(self._dispatch_error, EVENT_JOB_ERROR)
def _build_args(self, job):
def _build_args(self, job: 'Job') -> List[Union[CallbackContext, 'Bot', 'Job']]:
if self._dispatcher.use_context:
return [CallbackContext.from_job(job, self._dispatcher)]
return [self._dispatcher.bot, job]
def _tz_now(self):
def _tz_now(self) -> datetime.datetime:
return datetime.datetime.now(self.scheduler.timezone)
def _update_persistence(self, event):
def _update_persistence(self, event: JobEvent) -> None:
self._dispatcher.update_persistence()
def _dispatch_error(self, event):
def _dispatch_error(self, event: JobEvent) -> None:
try:
self._dispatcher.dispatch_error(None, event.exception)
# Errors should not stop the thread.
@ -80,7 +86,21 @@ class JobQueue:
'uncaught error was raised while handling the error '
'with an error_handler.')
def _parse_time_input(self, time, shift_day=False):
@overload
def _parse_time_input(self, time: None, shift_day: bool = False) -> None:
...
@overload
def _parse_time_input(self,
time: Union[float, int, datetime.timedelta, datetime.datetime,
datetime.time],
shift_day: bool = False) -> datetime.datetime:
...
def _parse_time_input(self,
time: Union[float, int, datetime.timedelta, datetime.datetime,
datetime.time, None],
shift_day: bool = False) -> Optional[datetime.datetime]:
if time is None:
return None
if isinstance(time, (int, float)):
@ -98,7 +118,7 @@ class JobQueue:
# isinstance(time, datetime.datetime):
return time
def set_dispatcher(self, dispatcher):
def set_dispatcher(self, dispatcher: 'Dispatcher') -> None:
"""Set the dispatcher to be used by this JobQueue. Use this instead of passing a
:class:`telegram.Bot` to the JobQueue, which is deprecated.
@ -111,7 +131,12 @@ class JobQueue:
if dispatcher.bot.defaults:
self.scheduler.configure(timezone=dispatcher.bot.defaults.tzinfo or pytz.utc)
def run_once(self, callback, when, context=None, name=None, job_kwargs=None):
def run_once(self,
callback: Callable[['CallbackContext'], None],
when: Union[float, datetime.timedelta, datetime.datetime, datetime.time],
context: object = None,
name: str = None,
job_kwargs: JSONDict = None) -> 'Job':
"""Creates a new ``Job`` that runs once and adds it to the queue.
Args:
@ -169,8 +194,16 @@ class JobQueue:
job.job = j
return job
def run_repeating(self, callback, interval, first=None, last=None, context=None, name=None,
job_kwargs=None):
def run_repeating(self,
callback: Callable[['CallbackContext'], None],
interval: Union[float, datetime.timedelta],
first: Union[float, datetime.timedelta, datetime.datetime,
datetime.time] = None,
last: Union[float, datetime.timedelta, datetime.datetime,
datetime.time] = None,
context: object = None,
name: str = None,
job_kwargs: JSONDict = None) -> 'Job':
"""Creates a new ``Job`` that runs at specified intervals and adds it to the queue.
Args:
@ -256,8 +289,14 @@ class JobQueue:
job.job = j
return job
def run_monthly(self, callback, when, day, context=None, name=None, day_is_strict=True,
job_kwargs=None):
def run_monthly(self,
callback: Callable[['CallbackContext'], None],
when: datetime.time,
day: int,
context: object = None,
name: str = None,
day_is_strict: bool = True,
job_kwargs: JSONDict = None) -> 'Job':
"""Creates a new ``Job`` that runs on a monthly basis and adds it to the queue.
Args:
@ -325,8 +364,13 @@ class JobQueue:
job.job = j
return job
def run_daily(self, callback, time, days=Days.EVERY_DAY, context=None, name=None,
job_kwargs=None):
def run_daily(self,
callback: Callable[['CallbackContext'], None],
time: datetime.time,
days: Tuple[int, ...] = Days.EVERY_DAY,
context: object = None,
name: str = None,
job_kwargs: JSONDict = None) -> 'Job':
"""Creates a new ``Job`` that runs on a daily basis and adds it to the queue.
Args:
@ -379,7 +423,11 @@ class JobQueue:
job.job = j
return job
def run_custom(self, callback, job_kwargs, context=None, name=None):
def run_custom(self,
callback: Callable[['CallbackContext'], None],
job_kwargs: JSONDict,
context: object = None,
name: str = None) -> 'Job':
"""Creates a new customly defined ``Job``.
Args:
@ -393,7 +441,7 @@ class JobQueue:
job_kwargs (:obj:`dict`): Arbitrary keyword arguments. Used as arguments for
``scheduler.add_job``.
context (:obj:`object`, optional): Additional data needed for the callback function.
Can be accessed through ``job.context`` in the callback. Defaults to :obj:`None`.
Can be accessed through ``job.context`` in the callback. Defaults to ``None``.
name (:obj:`str`, optional): The name of the new job. Defaults to
``callback.__name__``.
@ -413,21 +461,21 @@ class JobQueue:
job.job = j
return job
def start(self):
def start(self) -> None:
"""Starts the job_queue thread."""
if not self.scheduler.running:
self.scheduler.start()
def stop(self):
def stop(self) -> None:
"""Stops the thread."""
if self.scheduler.running:
self.scheduler.shutdown()
def jobs(self):
def jobs(self) -> Tuple['Job', ...]:
"""Returns a tuple of all jobs that are currently in the ``JobQueue``."""
return tuple(Job.from_aps_job(job, self) for job in self.scheduler.get_jobs())
def get_jobs_by_name(self, name):
def get_jobs_by_name(self, name: str) -> Tuple['Job', ...]:
"""Returns a tuple of jobs with the given name that are currently in the ``JobQueue``"""
return tuple(job for job in self.jobs() if job.name == name)
@ -469,11 +517,11 @@ class Job:
"""
def __init__(self,
callback,
context=None,
name=None,
job_queue=None,
job=None):
callback: Callable[['CallbackContext'], None],
context: object = None,
name: str = None,
job_queue: JobQueue = None,
job: 'Job' = None):
self.callback = callback
self.context = context
@ -483,15 +531,15 @@ class Job:
self._removed = False
self._enabled = False
self.job = job
self.job = cast('Job', job)
def run(self, dispatcher):
def run(self, dispatcher: 'Dispatcher') -> None:
"""Executes the callback function independently of the jobs schedule."""
try:
if dispatcher.use_context:
self.callback(CallbackContext.from_job(self, dispatcher))
else:
self.callback(dispatcher.bot, self)
self.callback(dispatcher.bot, self) # type: ignore[arg-type,call-arg]
except Exception as e:
try:
dispatcher.dispatch_error(None, e)
@ -501,7 +549,7 @@ class Job:
'uncaught error was raised while handling the error '
'with an error_handler.')
def schedule_removal(self):
def schedule_removal(self) -> None:
"""
Schedules this job for removal from the ``JobQueue``. It will be removed without executing
its callback function again.
@ -510,17 +558,17 @@ class Job:
self._removed = True
@property
def removed(self):
def removed(self) -> bool:
""":obj:`bool`: Whether this job is due to be removed."""
return self._removed
@property
def enabled(self):
def enabled(self) -> bool:
""":obj:`bool`: Whether this job is enabled."""
return self._enabled
@enabled.setter
def enabled(self, status):
def enabled(self, status: bool) -> None:
if status:
self.job.resume()
else:
@ -528,7 +576,7 @@ class Job:
self._enabled = status
@property
def next_t(self):
def next_t(self) -> Optional[datetime.datetime]:
"""
:obj:`datetime.datetime`: Datetime for the next job execution.
Datetime is localized according to :attr:`tzinfo`.
@ -537,7 +585,7 @@ class Job:
return self.job.next_run_time
@classmethod
def from_aps_job(cls, job, job_queue):
def from_aps_job(cls, job: 'Job', job_queue: JobQueue) -> 'Job':
# context based callbacks
if len(job.args) == 1:
context = job.args[0].job.context
@ -545,13 +593,13 @@ class Job:
context = job.args[1].context
return cls(job.func, context=context, name=job.name, job_queue=job_queue, job=job)
def __getattr__(self, item):
def __getattr__(self, item: str) -> Any:
return getattr(self.job, item)
def __lt__(self, other):
def __lt__(self, other: object) -> bool:
return False
def __eq__(self, other):
def __eq__(self, other: object) -> bool:
if isinstance(other, self.__class__):
return self.id == other.id
return False

View file

@ -23,9 +23,16 @@ import warnings
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram import Update
from telegram.ext import Filters
from telegram.ext import Filters, BaseFilter
from .handler import Handler
from telegram.utils.types import HandlerArg
from typing import Callable, TYPE_CHECKING, Any, Optional, Union, TypeVar, Dict
if TYPE_CHECKING:
from telegram.ext import CallbackContext, Dispatcher
RT = TypeVar('RT')
class MessageHandler(Handler):
"""Handler class to handle telegram messages. They might contain text, media or status updates.
@ -114,16 +121,16 @@ class MessageHandler(Handler):
"""
def __init__(self,
filters,
callback,
pass_update_queue=False,
pass_job_queue=False,
pass_user_data=False,
pass_chat_data=False,
message_updates=None,
channel_post_updates=None,
edited_updates=None,
run_async=False):
filters: BaseFilter,
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
pass_update_queue: bool = False,
pass_job_queue: bool = False,
pass_user_data: bool = False,
pass_chat_data: bool = False,
message_updates: bool = None,
channel_post_updates: bool = None,
edited_updates: bool = None,
run_async: bool = False):
super().__init__(
callback,
@ -162,7 +169,7 @@ class MessageHandler(Handler):
self.filters &= ~(Filters.update.edited_message
| Filters.update.edited_channel_post)
def check_update(self, update):
def check_update(self, update: HandlerArg) -> Optional[Union[bool, Dict[str, Any]]]:
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:
@ -174,7 +181,12 @@ class MessageHandler(Handler):
"""
if isinstance(update, Update) and update.effective_message:
return self.filters(update)
return None
def collect_additional_context(self, context, update, dispatcher, check_result):
def collect_additional_context(self,
context: 'CallbackContext',
update: HandlerArg,
dispatcher: 'Dispatcher',
check_result: Optional[Union[bool, Dict[str, Any]]]) -> None:
if isinstance(check_result, dict):
context.update(check_result)

View file

@ -27,6 +27,14 @@ import time
import threading
import queue as q
from typing import Callable, Any, TYPE_CHECKING, List, NoReturn
if TYPE_CHECKING:
from telegram import Bot
# We need to count < 1s intervals, so the most accurate timer is needed
curtime = time.perf_counter
class DelayQueueError(RuntimeError):
"""Indicates processing errors."""
@ -68,12 +76,12 @@ class DelayQueue(threading.Thread):
_instcnt = 0 # instance counter
def __init__(self,
queue=None,
burst_limit=30,
time_limit_ms=1000,
exc_route=None,
autostart=True,
name=None):
queue: q.Queue = None,
burst_limit: int = 30,
time_limit_ms: int = 1000,
exc_route: Callable[[Exception], None] = None,
autostart: bool = True,
name: str = None):
self._queue = queue if queue is not None else q.Queue()
self.burst_limit = burst_limit
self.time_limit = time_limit_ms / 1000
@ -87,14 +95,14 @@ class DelayQueue(threading.Thread):
if autostart: # immediately start processing
super().start()
def run(self):
def run(self) -> None:
"""
Do not use the method except for unthreaded testing purposes, the method normally is
automatically called by autostart argument.
"""
times = [] # used to store each callable processing time
times: List[float] = [] # used to store each callable processing time
while True:
item = self._queue.get()
if self.__exit_req:
@ -119,7 +127,7 @@ class DelayQueue(threading.Thread):
except Exception as exc: # re-route any exceptions
self.exc_route(exc) # to prevent thread exit
def stop(self, timeout=None):
def stop(self, timeout: float = None) -> None:
"""Used to gently stop processor and shutdown its thread.
Args:
@ -136,7 +144,7 @@ class DelayQueue(threading.Thread):
super().join(timeout=timeout)
@staticmethod
def _default_exception_handler(exc):
def _default_exception_handler(exc: Exception) -> NoReturn:
"""
Dummy exception handler which re-raises exception in thread. Could be possibly overwritten
by subclasses.
@ -145,7 +153,7 @@ class DelayQueue(threading.Thread):
raise exc
def __call__(self, func, *args, **kwargs):
def __call__(self, func: Callable, *args: Any, **kwargs: Any) -> None:
"""Used to process callbacks in throughput-limiting thread through queue.
Args:
@ -194,12 +202,12 @@ class MessageQueue:
"""
def __init__(self,
all_burst_limit=30,
all_time_limit_ms=1000,
group_burst_limit=20,
group_time_limit_ms=60000,
exc_route=None,
autostart=True):
all_burst_limit: int = 30,
all_time_limit_ms: int = 1000,
group_burst_limit: int = 20,
group_time_limit_ms: int = 60000,
exc_route: Callable[[Exception], None] = None,
autostart: bool = True):
# create according delay queues, use composition
self._all_delayq = DelayQueue(
burst_limit=all_burst_limit,
@ -212,18 +220,18 @@ class MessageQueue:
exc_route=exc_route,
autostart=autostart)
def start(self):
def start(self) -> None:
"""Method is used to manually start the ``MessageQueue`` processing."""
self._all_delayq.start()
self._group_delayq.start()
def stop(self, timeout=None):
def stop(self, timeout: float = None) -> None:
self._group_delayq.stop(timeout=timeout)
self._all_delayq.stop(timeout=timeout)
stop.__doc__ = DelayQueue.stop.__doc__ or '' # reuse docstring if any
def __call__(self, promise, is_group_msg=False):
def __call__(self, promise: Callable, is_group_msg: bool = False) -> Callable:
"""
Processes callables in throughput-limiting queues to avoid hitting limits (specified with
:attr:`burst_limit` and :attr:`time_limit`.
@ -255,7 +263,7 @@ class MessageQueue:
return promise
def queuedmessage(method):
def queuedmessage(method: Callable) -> Callable:
"""A decorator to be used with :attr:`telegram.Bot` send* methods.
Note:
@ -288,12 +296,13 @@ def queuedmessage(method):
"""
@functools.wraps(method)
def wrapped(self, *args, **kwargs):
queued = kwargs.pop('queued', self._is_messages_queued_default)
def wrapped(self: 'Bot', *args: Any, **kwargs: Any) -> Any:
queued = kwargs.pop('queued',
self._is_messages_queued_default) # type: ignore[attr-defined]
isgroup = kwargs.pop('isgroup', False)
if queued:
prom = promise.Promise(method, (self, ) + args, kwargs)
return self._msg_queue(prom, isgroup)
return self._msg_queue(prom, isgroup) # type: ignore[attr-defined]
return method(self, *args, **kwargs)
return wrapped

View file

@ -23,6 +23,9 @@ from copy import deepcopy
from telegram.ext import BasePersistence
from typing import DefaultDict, Dict, Any, Tuple, Optional
from telegram.utils.types import ConversationDict
class PicklePersistence(BasePersistence):
"""Using python's builtin pickle for making you bot persistent.
@ -71,24 +74,25 @@ class PicklePersistence(BasePersistence):
Default is :obj:`False`.
"""
def __init__(self, filename,
store_user_data=True,
store_chat_data=True,
store_bot_data=True,
single_file=True,
on_flush=False):
def __init__(self,
filename: str,
store_user_data: bool = True,
store_chat_data: bool = True,
store_bot_data: bool = True,
single_file: bool = True,
on_flush: bool = False):
super().__init__(store_user_data=store_user_data,
store_chat_data=store_chat_data,
store_bot_data=store_bot_data)
self.filename = filename
self.single_file = single_file
self.on_flush = on_flush
self.user_data = None
self.chat_data = None
self.bot_data = None
self.conversations = None
self.user_data: Optional[DefaultDict[int, Dict]] = None
self.chat_data: Optional[DefaultDict[int, Dict]] = None
self.bot_data: Optional[Dict] = None
self.conversations: Optional[Dict[str, Dict[Tuple, Any]]] = None
def load_singlefile(self):
def load_singlefile(self) -> None:
try:
filename = self.filename
with open(self.filename, "rb") as f:
@ -99,7 +103,7 @@ class PicklePersistence(BasePersistence):
self.bot_data = data.get('bot_data', {})
self.conversations = data['conversations']
except IOError:
self.conversations = {}
self.conversations = dict()
self.user_data = defaultdict(dict)
self.chat_data = defaultdict(dict)
self.bot_data = {}
@ -108,7 +112,7 @@ class PicklePersistence(BasePersistence):
except Exception:
raise TypeError("Something went wrong unpickling {}".format(filename))
def load_file(self, filename):
def load_file(self, filename: str) -> Any:
try:
with open(filename, "rb") as f:
return pickle.load(f)
@ -119,17 +123,17 @@ class PicklePersistence(BasePersistence):
except Exception:
raise TypeError("Something went wrong unpickling {}".format(filename))
def dump_singlefile(self):
def dump_singlefile(self) -> None:
with open(self.filename, "wb") as f:
data = {'conversations': self.conversations, 'user_data': self.user_data,
'chat_data': self.chat_data, 'bot_data': self.bot_data}
pickle.dump(data, f)
def dump_file(self, filename, data):
def dump_file(self, filename: str, data: Any) -> None:
with open(filename, "wb") as f:
pickle.dump(data, f)
def get_user_data(self):
def get_user_data(self) -> DefaultDict[int, Dict[Any, Any]]:
"""Returns the user_data from the pickle file if it exists or an empty :obj:`defaultdict`.
Returns:
@ -147,9 +151,9 @@ class PicklePersistence(BasePersistence):
self.user_data = data
else:
self.load_singlefile()
return deepcopy(self.user_data)
return deepcopy(self.user_data) # type: ignore[arg-type]
def get_chat_data(self):
def get_chat_data(self) -> DefaultDict[int, Dict[Any, Any]]:
"""Returns the chat_data from the pickle file if it exists or an empty :obj:`defaultdict`.
Returns:
@ -167,9 +171,9 @@ class PicklePersistence(BasePersistence):
self.chat_data = data
else:
self.load_singlefile()
return deepcopy(self.chat_data)
return deepcopy(self.chat_data) # type: ignore[arg-type]
def get_bot_data(self):
def get_bot_data(self) -> Dict[Any, Any]:
"""Returns the bot_data from the pickle file if it exists or an empty :obj:`dict`.
Returns:
@ -185,10 +189,10 @@ class PicklePersistence(BasePersistence):
self.bot_data = data
else:
self.load_singlefile()
return deepcopy(self.bot_data)
return deepcopy(self.bot_data) # type: ignore[arg-type]
def get_conversations(self, name):
"""Returns the conversations from the pickle file if it exists or an empty :obj:`dict`.
def get_conversations(self, name: str) -> ConversationDict:
"""Returns the conversations from the pickle file if it exsists or an empty dict.
Args:
name (:obj:`str`): The handlers name.
@ -206,9 +210,11 @@ class PicklePersistence(BasePersistence):
self.conversations = data
else:
self.load_singlefile()
return self.conversations.get(name, {}).copy()
return self.conversations.get(name, {}).copy() # type: ignore[union-attr]
def update_conversation(self, name, key, new_state):
def update_conversation(self,
name: str, key: Tuple[int, ...],
new_state: Optional[object]) -> None:
"""Will update the conversations for the given handler and depending on :attr:`on_flush`
save the pickle file.
@ -217,6 +223,8 @@ class PicklePersistence(BasePersistence):
key (:obj:`tuple`): The key the state is changed for.
new_state (:obj:`tuple` | :obj:`any`): The new state for the given key.
"""
if not self.conversations:
self.conversations = dict()
if self.conversations.setdefault(name, {}).get(key) == new_state:
return
self.conversations[name][key] = new_state
@ -227,7 +235,7 @@ class PicklePersistence(BasePersistence):
else:
self.dump_singlefile()
def update_user_data(self, user_id, data):
def update_user_data(self, user_id: int, data: Dict) -> None:
"""Will update the user_data and depending on :attr:`on_flush` save the pickle file.
Args:
@ -246,7 +254,7 @@ class PicklePersistence(BasePersistence):
else:
self.dump_singlefile()
def update_chat_data(self, chat_id, data):
def update_chat_data(self, chat_id: int, data: Dict) -> None:
"""Will update the chat_data and depending on :attr:`on_flush` save the pickle file.
Args:
@ -265,7 +273,7 @@ class PicklePersistence(BasePersistence):
else:
self.dump_singlefile()
def update_bot_data(self, data):
def update_bot_data(self, data: Dict) -> None:
"""Will update the bot_data and depending on :attr:`on_flush` save the pickle file.
Args:
@ -281,7 +289,7 @@ class PicklePersistence(BasePersistence):
else:
self.dump_singlefile()
def flush(self):
def flush(self) -> None:
""" Will save all data in memory to pickle file(s).
"""
if self.single_file:

View file

@ -20,6 +20,8 @@
from telegram import Update
from .handler import Handler
from telegram.utils.types import HandlerArg
class PollAnswerHandler(Handler):
"""Handler class to handle Telegram updates that contain a poll answer.
@ -79,7 +81,7 @@ class PollAnswerHandler(Handler):
"""
def check_update(self, update):
def check_update(self, update: HandlerArg) -> bool:
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:
@ -89,4 +91,4 @@ class PollAnswerHandler(Handler):
:obj:`bool`
"""
return isinstance(update, Update) and update.poll_answer
return isinstance(update, Update) and bool(update.poll_answer)

View file

@ -20,6 +20,8 @@
from telegram import Update
from .handler import Handler
from telegram.utils.types import HandlerArg
class PollHandler(Handler):
"""Handler class to handle Telegram updates that contain a poll.
@ -79,7 +81,7 @@ class PollHandler(Handler):
"""
def check_update(self, update):
def check_update(self, update: HandlerArg) -> bool:
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:
@ -89,4 +91,4 @@ class PollHandler(Handler):
:obj:`bool`
"""
return isinstance(update, Update) and update.poll
return isinstance(update, Update) and bool(update.poll)

View file

@ -21,6 +21,8 @@
from telegram import Update
from .handler import Handler
from telegram.utils.types import HandlerArg
class PreCheckoutQueryHandler(Handler):
"""Handler class to handle Telegram PreCheckout callback queries.
@ -80,7 +82,7 @@ class PreCheckoutQueryHandler(Handler):
"""
def check_update(self, update):
def check_update(self, update: HandlerArg) -> bool:
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:
@ -90,4 +92,4 @@ class PreCheckoutQueryHandler(Handler):
:obj:`bool`
"""
return isinstance(update, Update) and update.pre_checkout_query
return isinstance(update, Update) and bool(update.pre_checkout_query)

View file

@ -25,6 +25,13 @@ from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram.ext import MessageHandler, Filters
from telegram.utils.types import HandlerArg
from typing import Callable, TYPE_CHECKING, Any, Optional, Union, TypeVar, Dict, Pattern
if TYPE_CHECKING:
from telegram.ext import CallbackContext, Dispatcher
RT = TypeVar('RT')
class RegexHandler(MessageHandler):
"""Handler class to handle Telegram updates based on a regex.
@ -102,19 +109,19 @@ class RegexHandler(MessageHandler):
"""
def __init__(self,
pattern,
callback,
pass_groups=False,
pass_groupdict=False,
pass_update_queue=False,
pass_job_queue=False,
pass_user_data=False,
pass_chat_data=False,
allow_edited=False,
message_updates=True,
channel_post_updates=False,
edited_updates=False,
run_async=False):
pattern: Union[str, Pattern],
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
pass_groups: bool = False,
pass_groupdict: bool = False,
pass_update_queue: bool = False,
pass_job_queue: bool = False,
pass_user_data: bool = False,
pass_chat_data: bool = False,
allow_edited: bool = False,
message_updates: bool = True,
channel_post_updates: bool = False,
edited_updates: bool = False,
run_async: bool = False):
warnings.warn('RegexHandler is deprecated. See https://git.io/fxJuV for more info',
TelegramDeprecationWarning,
stacklevel=2)
@ -131,10 +138,15 @@ class RegexHandler(MessageHandler):
self.pass_groups = pass_groups
self.pass_groupdict = pass_groupdict
def collect_optional_args(self, dispatcher, update=None, check_result=None):
def collect_optional_args(
self,
dispatcher: 'Dispatcher',
update: HandlerArg = None,
check_result: Optional[Union[bool, Dict[str, Any]]] = None) -> Dict[str, Any]:
optional_args = super().collect_optional_args(dispatcher, update, check_result)
if self.pass_groups:
optional_args['groups'] = check_result['matches'][0].groups()
if self.pass_groupdict:
optional_args['groupdict'] = check_result['matches'][0].groupdict()
if isinstance(check_result, dict):
if self.pass_groups:
optional_args['groups'] = check_result['matches'][0].groups()
if self.pass_groupdict:
optional_args['groupdict'] = check_result['matches'][0].groupdict()
return optional_args

View file

@ -21,6 +21,8 @@
from telegram import Update
from .handler import Handler
from telegram.utils.types import HandlerArg
class ShippingQueryHandler(Handler):
"""Handler class to handle Telegram shipping callback queries.
@ -80,7 +82,7 @@ class ShippingQueryHandler(Handler):
"""
def check_update(self, update):
def check_update(self, update: HandlerArg) -> bool:
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:
@ -90,4 +92,4 @@ class ShippingQueryHandler(Handler):
:obj:`bool`
"""
return isinstance(update, Update) and update.shipping_query
return isinstance(update, Update) and bool(update.shipping_query)

View file

@ -20,6 +20,13 @@
from .handler import Handler
from telegram.utils.types import HandlerArg
from typing import Callable, TYPE_CHECKING, Any, Optional, TypeVar, Dict, List
if TYPE_CHECKING:
from telegram.ext import CallbackContext, Dispatcher
RT = TypeVar('RT')
class StringCommandHandler(Handler):
"""Handler class to handle string commands. Commands are string updates that start with ``/``.
@ -44,6 +51,7 @@ class StringCommandHandler(Handler):
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
Args:
command (:obj:`str`): The command this handler should listen for.
callback (:obj:`callable`): The callback function for this handler. Will be called when
:attr:`check_update` has determined that an update should be processed by this handler.
Callback signature for context based API:
@ -73,12 +81,12 @@ class StringCommandHandler(Handler):
"""
def __init__(self,
command,
callback,
pass_args=False,
pass_update_queue=False,
pass_job_queue=False,
run_async=False):
command: str,
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
pass_args: bool = False,
pass_update_queue: bool = False,
pass_job_queue: bool = False,
run_async: bool = False):
super().__init__(
callback,
pass_update_queue=pass_update_queue,
@ -87,7 +95,7 @@ class StringCommandHandler(Handler):
self.command = command
self.pass_args = pass_args
def check_update(self, update):
def check_update(self, update: HandlerArg) -> Optional[List[str]]:
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:
@ -101,12 +109,20 @@ class StringCommandHandler(Handler):
args = update[1:].split(' ')
if args[0] == self.command:
return args[1:]
return None
def collect_optional_args(self, dispatcher, update=None, check_result=None):
def collect_optional_args(self,
dispatcher: 'Dispatcher',
update: HandlerArg = None,
check_result: Optional[List[str]] = None) -> Dict[str, Any]:
optional_args = super().collect_optional_args(dispatcher, update, check_result)
if self.pass_args:
optional_args['args'] = check_result
return optional_args
def collect_additional_context(self, context, update, dispatcher, check_result):
def collect_additional_context(self,
context: 'CallbackContext',
update: HandlerArg,
dispatcher: 'Dispatcher',
check_result: Optional[List[str]]) -> None:
context.args = check_result

View file

@ -22,6 +22,13 @@ import re
from .handler import Handler
from typing import Callable, TYPE_CHECKING, Optional, TypeVar, Match, Dict, Any, Union, Pattern
from telegram.utils.types import HandlerArg
if TYPE_CHECKING:
from telegram.ext import CallbackContext, Dispatcher
RT = TypeVar('RT')
class StringRegexHandler(Handler):
"""Handler class to handle string updates based on a regex which checks the update content.
@ -84,13 +91,13 @@ class StringRegexHandler(Handler):
"""
def __init__(self,
pattern,
callback,
pass_groups=False,
pass_groupdict=False,
pass_update_queue=False,
pass_job_queue=False,
run_async=False):
pattern: Union[str, Pattern],
callback: Callable[[HandlerArg, 'CallbackContext'], RT],
pass_groups: bool = False,
pass_groupdict: bool = False,
pass_update_queue: bool = False,
pass_job_queue: bool = False,
run_async: bool = False):
super().__init__(
callback,
pass_update_queue=pass_update_queue,
@ -104,7 +111,7 @@ class StringRegexHandler(Handler):
self.pass_groups = pass_groups
self.pass_groupdict = pass_groupdict
def check_update(self, update):
def check_update(self, update: HandlerArg) -> Optional[Match]:
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:
@ -118,16 +125,24 @@ class StringRegexHandler(Handler):
match = re.match(self.pattern, update)
if match:
return match
return None
def collect_optional_args(self, dispatcher, update=None, check_result=None):
def collect_optional_args(self,
dispatcher: 'Dispatcher',
update: HandlerArg = None,
check_result: Optional[Match] = None) -> Dict[str, Any]:
optional_args = super().collect_optional_args(dispatcher, update, check_result)
if self.pattern:
if self.pass_groups:
if self.pass_groups and check_result:
optional_args['groups'] = check_result.groups()
if self.pass_groupdict:
if self.pass_groupdict and check_result:
optional_args['groupdict'] = check_result.groupdict()
return optional_args
def collect_additional_context(self, context, update, dispatcher, check_result):
if self.pattern:
def collect_additional_context(self,
context: 'CallbackContext',
update: HandlerArg,
dispatcher: 'Dispatcher',
check_result: Optional[Match]) -> None:
if self.pattern and check_result:
context.matches = [check_result]

View file

@ -21,6 +21,14 @@
from .handler import Handler
from typing import Callable, TYPE_CHECKING, TypeVar, Type, Any
if TYPE_CHECKING:
from telegram.ext import CallbackContext
RT = TypeVar('RT')
class TypeHandler(Handler):
"""Handler class to handle updates of custom types.
@ -67,12 +75,12 @@ class TypeHandler(Handler):
"""
def __init__(self,
type,
callback,
strict=False,
pass_update_queue=False,
pass_job_queue=False,
run_async=False):
type: Type,
callback: Callable[[Any, 'CallbackContext'], RT],
strict: bool = False,
pass_update_queue: bool = False,
pass_job_queue: bool = False,
run_async: bool = False):
super().__init__(
callback,
pass_update_queue=pass_update_queue,
@ -81,7 +89,7 @@ class TypeHandler(Handler):
self.type = type
self.strict = strict
def check_update(self, update):
def check_update(self, update: Any) -> bool:
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:

View file

@ -34,6 +34,11 @@ from telegram.utils.helpers import get_signal_name
from telegram.utils.request import Request
from telegram.utils.webhookhandler import (WebhookServer, WebhookAppClass)
from typing import Callable, Dict, TYPE_CHECKING, Any, List, Union, Tuple, no_type_check, Optional
if TYPE_CHECKING:
from telegram.ext import BasePersistence, Defaults
class Updater:
"""
@ -104,19 +109,19 @@ class Updater:
_request = None
def __init__(self,
token=None,
base_url=None,
workers=4,
bot=None,
private_key=None,
private_key_password=None,
user_sig_handler=None,
request_kwargs=None,
persistence=None,
defaults=None,
use_context=True,
dispatcher=None,
base_file_url=None):
token: str = None,
base_url: str = None,
workers: int = 4,
bot: Bot = None,
private_key: bytes = None,
private_key_password: bytes = None,
user_sig_handler: Callable = None,
request_kwargs: Dict[str, Any] = None,
persistence: 'BasePersistence' = None,
defaults: 'Defaults' = None,
use_context: bool = True,
dispatcher: Dispatcher = None,
base_file_url: str = None):
if defaults and bot:
warnings.warn('Passing defaults to an Updater has no effect when a Bot is passed '
@ -164,14 +169,14 @@ class Updater:
if 'con_pool_size' not in request_kwargs:
request_kwargs['con_pool_size'] = con_pool_size
self._request = Request(**request_kwargs)
self.bot = Bot(token,
self.bot = Bot(token, # type: ignore[arg-type]
base_url,
base_file_url=base_file_url,
request=self._request,
private_key=private_key,
private_key_password=private_key_password,
defaults=defaults)
self.update_queue = Queue()
self.update_queue: Queue = Queue()
self.job_queue = JobQueue()
self.__exception_event = Event()
self.persistence = persistence
@ -203,9 +208,9 @@ class Updater:
self.is_idle = False
self.httpd = None
self.__lock = Lock()
self.__threads = []
self.__threads: List[Thread] = []
def _init_thread(self, target, name, *args, **kwargs):
def _init_thread(self, target: Callable, name: str, *args: Any, **kwargs: Any) -> None:
thr = Thread(target=self._thread_wrapper,
name="Bot:{}:{}".format(self.bot.id, name),
args=(target,) + args,
@ -213,7 +218,7 @@ class Updater:
thr.start()
self.__threads.append(thr)
def _thread_wrapper(self, target, *args, **kwargs):
def _thread_wrapper(self, target: Callable, *args: Any, **kwargs: Any) -> None:
thr_name = current_thread().name
self.logger.debug('{} - started'.format(thr_name))
try:
@ -225,12 +230,12 @@ class Updater:
self.logger.debug('{} - ended'.format(thr_name))
def start_polling(self,
poll_interval=0.0,
timeout=10,
clean=False,
bootstrap_retries=-1,
read_latency=2.,
allowed_updates=None):
poll_interval: float = 0.0,
timeout: float = 10,
clean: bool = False,
bootstrap_retries: int = -1,
read_latency: float = 2.,
allowed_updates: List[str] = None) -> Optional[Queue]:
"""Starts polling updates from Telegram.
Args:
@ -275,18 +280,19 @@ class Updater:
# Return the update queue so the main thread can insert updates
return self.update_queue
return None
def start_webhook(self,
listen='127.0.0.1',
port=80,
url_path='',
cert=None,
key=None,
clean=False,
bootstrap_retries=0,
webhook_url=None,
allowed_updates=None,
force_event_loop=False):
listen: str = '127.0.0.1',
port: int = 80,
url_path: str = '',
cert: str = None,
key: str = None,
clean: bool = False,
bootstrap_retries: int = 0,
webhook_url: str = None,
allowed_updates: List[str] = None,
force_event_loop: bool = False) -> Optional[Queue]:
"""
Starts a small http server to listen for updates via webhook. If cert
and key are not provided, the webhook will be started directly on
@ -348,7 +354,9 @@ class Updater:
# Return the update queue so the main thread can insert updates
return self.update_queue
return None
@no_type_check
def _start_polling(self, poll_interval, timeout, read_latency, bootstrap_retries, clean,
allowed_updates, ready=None): # pragma: no cover
# Thread target of thread 'updater'. Runs in background, pulls
@ -388,6 +396,7 @@ class Updater:
self._network_loop_retry(polling_action_cb, polling_onerr_cb, 'getting Updates',
poll_interval)
@no_type_check
def _network_loop_retry(self, action_cb, onerr_cb, description, interval):
"""Perform a loop calling `action_cb`, retrying after network errors.
@ -430,7 +439,7 @@ class Updater:
sleep(cur_interval)
@staticmethod
def _increase_poll_interval(current_interval):
def _increase_poll_interval(current_interval: float) -> float:
# increase waiting times on subsequent errors up to 30secs
if current_interval == 0:
current_interval = 1
@ -440,6 +449,7 @@ class Updater:
current_interval = 30
return current_interval
@no_type_check
def _start_webhook(self, listen, port, url_path, cert, key, bootstrap_retries, clean,
webhook_url, allowed_updates, ready=None, force_event_loop=False):
self.logger.debug('Updater thread started (webhook)')
@ -481,9 +491,10 @@ class Updater:
self.httpd.serve_forever(force_event_loop=force_event_loop, ready=ready)
@staticmethod
def _gen_webhook_url(listen, port, url_path):
def _gen_webhook_url(listen: str, port: int, url_path: str) -> str:
return 'https://{listen}:{port}{path}'.format(listen=listen, port=port, path=url_path)
@no_type_check
def _bootstrap(self,
max_retries,
clean,
@ -541,7 +552,7 @@ class Updater:
self._network_loop_retry(bootstrap_set_webhook, bootstrap_onerr_cb,
'bootstrap set webhook', bootstrap_interval)
def stop(self):
def stop(self) -> None:
"""Stops the polling/webhook thread, the dispatcher and the job queue."""
self.job_queue.stop()
@ -559,7 +570,8 @@ class Updater:
if self._request:
self._request.stop()
def _stop_httpd(self):
@no_type_check
def _stop_httpd(self) -> None:
if self.httpd:
self.logger.debug('Waiting for current webhook connection to be '
'closed... Send a Telegram message to the bot to exit '
@ -567,18 +579,21 @@ class Updater:
self.httpd.shutdown()
self.httpd = None
def _stop_dispatcher(self):
@no_type_check
def _stop_dispatcher(self) -> None:
self.logger.debug('Requesting Dispatcher to stop...')
self.dispatcher.stop()
def _join_threads(self):
@no_type_check
def _join_threads(self) -> None:
for thr in self.__threads:
self.logger.debug('Waiting for {} thread to end'.format(thr.name))
thr.join()
self.logger.debug('{} thread has ended'.format(thr.name))
self.__threads = []
def signal_handler(self, signum, frame):
@no_type_check
def signal_handler(self, signum, frame) -> None:
self.is_idle = False
if self.running:
self.logger.info('Received signal {} ({}), stopping...'.format(
@ -595,7 +610,7 @@ class Updater:
import os
os._exit(1)
def idle(self, stop_signals=(SIGINT, SIGTERM, SIGABRT)):
def idle(self, stop_signals: Union[List, Tuple] = (SIGINT, SIGTERM, SIGABRT)) -> None:
"""Blocks until one of the signals are received and stops the updater.
Args:

View file

@ -20,6 +20,11 @@
from telegram import PhotoSize
from telegram import TelegramObject
from telegram.utils.types import JSONDict
from typing import Any, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot, File
class Animation(TelegramObject):
"""This object represents an animation file (GIF or H.264/MPEG-4 AVC video without sound).
@ -60,17 +65,17 @@ class Animation(TelegramObject):
"""
def __init__(self,
file_id,
file_unique_id,
width,
height,
duration,
thumb=None,
file_name=None,
mime_type=None,
file_size=None,
bot=None,
**kwargs):
file_id: str,
file_unique_id: str,
width: int,
height: int,
duration: int,
thumb: PhotoSize = None,
file_name: str = None,
mime_type: str = None,
file_size: int = None,
bot: 'Bot' = None,
**kwargs: Any):
# Required
self.file_id = str(file_id)
self.file_unique_id = str(file_unique_id)
@ -87,17 +92,17 @@ class Animation(TelegramObject):
self._id_attrs = (self.file_unique_id,)
@classmethod
def de_json(cls, data, bot):
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Animation']:
data = cls.parse_data(data)
if not data:
return None
data = super().de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
return cls(bot=bot, **data)
def get_file(self, timeout=None, api_kwargs=None):
def get_file(self, timeout: int = None, api_kwargs: JSONDict = None) -> 'File':
"""Convenience wrapper over :attr:`telegram.Bot.get_file`
Args:

View file

@ -20,6 +20,11 @@
from telegram import TelegramObject, PhotoSize
from telegram.utils.types import JSONDict
from typing import Any, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot, File
class Audio(TelegramObject):
"""This object represents an audio file to be treated as music by the Telegram clients.
@ -61,16 +66,16 @@ class Audio(TelegramObject):
"""
def __init__(self,
file_id,
file_unique_id,
duration,
performer=None,
title=None,
mime_type=None,
file_size=None,
thumb=None,
bot=None,
**kwargs):
file_id: str,
file_unique_id: str,
duration: int,
performer: str = None,
title: str = None,
mime_type: str = None,
file_size: int = None,
thumb: PhotoSize = None,
bot: 'Bot' = None,
**kwargs: Any):
# Required
self.file_id = str(file_id)
self.file_unique_id = str(file_unique_id)
@ -86,7 +91,9 @@ class Audio(TelegramObject):
self._id_attrs = (self.file_unique_id,)
@classmethod
def de_json(cls, data, bot):
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Audio']:
data = cls.parse_data(data)
if not data:
return None
@ -94,7 +101,7 @@ class Audio(TelegramObject):
return cls(bot=bot, **data)
def get_file(self, timeout=None, api_kwargs=None):
def get_file(self, timeout: int = None, api_kwargs: JSONDict = None) -> 'File':
"""Convenience wrapper over :attr:`telegram.Bot.get_file`
Args:

View file

@ -19,6 +19,10 @@
"""This module contains an object that represents a Telegram ChatPhoto."""
from telegram import TelegramObject
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot, File
class ChatPhoto(TelegramObject):
"""This object represents a chat photo.
@ -58,11 +62,12 @@ class ChatPhoto(TelegramObject):
"""
def __init__(self,
small_file_id,
small_file_unique_id,
big_file_id,
big_file_unique_id,
bot=None, **kwargs):
small_file_id: str,
small_file_unique_id: str,
big_file_id: str,
big_file_unique_id: str,
bot: 'Bot' = None,
**kwargs: Any):
self.small_file_id = small_file_id
self.small_file_unique_id = small_file_unique_id
self.big_file_id = big_file_id
@ -72,14 +77,7 @@ class ChatPhoto(TelegramObject):
self._id_attrs = (self.small_file_unique_id, self.big_file_unique_id,)
@classmethod
def de_json(cls, data, bot):
if not data:
return None
return cls(bot=bot, **data)
def get_small_file(self, timeout=None, **kwargs):
def get_small_file(self, timeout: int = None, **kwargs: Any) -> 'File':
"""Convenience wrapper over :attr:`telegram.Bot.get_file` for getting the
small (160x160) chat photo
@ -99,7 +97,7 @@ class ChatPhoto(TelegramObject):
"""
return self.bot.get_file(self.small_file_id, timeout=timeout, **kwargs)
def get_big_file(self, timeout=None, **kwargs):
def get_big_file(self, timeout: int = None, **kwargs: Any) -> 'File':
"""Convenience wrapper over :attr:`telegram.Bot.get_file` for getting the
big (640x640) chat photo

View file

@ -19,6 +19,7 @@
"""This module contains an object that represents a Telegram Contact."""
from telegram import TelegramObject
from typing import Any
class Contact(TelegramObject):
@ -44,8 +45,13 @@ class Contact(TelegramObject):
"""
def __init__(self, phone_number, first_name, last_name=None, user_id=None, vcard=None,
**kwargs):
def __init__(self,
phone_number: str,
first_name: str,
last_name: str = None,
user_id: int = None,
vcard: str = None,
**kwargs: Any):
# Required
self.phone_number = str(phone_number)
self.first_name = first_name
@ -55,10 +61,3 @@ class Contact(TelegramObject):
self.vcard = vcard
self._id_attrs = (self.phone_number,)
@classmethod
def de_json(cls, data, bot):
if not data:
return None
return cls(**data)

View file

@ -20,6 +20,11 @@
from telegram import PhotoSize, TelegramObject
from telegram.utils.types import JSONDict
from typing import Any, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot, File
class Document(TelegramObject):
"""This object represents a general file
@ -55,14 +60,14 @@ class Document(TelegramObject):
_id_keys = ('file_id',)
def __init__(self,
file_id,
file_unique_id,
thumb=None,
file_name=None,
mime_type=None,
file_size=None,
bot=None,
**kwargs):
file_id: str,
file_unique_id: str,
thumb: PhotoSize = None,
file_name: str = None,
mime_type: str = None,
file_size: int = None,
bot: 'Bot' = None,
**kwargs: Any):
# Required
self.file_id = str(file_id)
self.file_unique_id = str(file_unique_id)
@ -76,17 +81,17 @@ class Document(TelegramObject):
self._id_attrs = (self.file_unique_id,)
@classmethod
def de_json(cls, data, bot):
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Document']:
data = cls.parse_data(data)
if not data:
return None
data = super().de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
return cls(bot=bot, **data)
def get_file(self, timeout=None, api_kwargs=None):
def get_file(self, timeout: int = None, api_kwargs: JSONDict = None) -> 'File':
"""Convenience wrapper over :attr:`telegram.Bot.get_file`
Args:

View file

@ -26,6 +26,10 @@ import urllib.parse as urllib_parse
from telegram import TelegramObject
from telegram.passport.credentials import decrypt
from typing import Any, Optional, IO, Union, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot, FileCredentials
class File(TelegramObject):
"""
@ -65,12 +69,12 @@ class File(TelegramObject):
"""
def __init__(self,
file_id,
file_unique_id,
bot=None,
file_size=None,
file_path=None,
**kwargs):
file_id: str,
file_unique_id: str,
bot: 'Bot' = None,
file_size: int = None,
file_path: str = None,
**kwargs: Any):
# Required
self.file_id = str(file_id)
self.file_unique_id = str(file_unique_id)
@ -78,18 +82,14 @@ class File(TelegramObject):
self.file_size = file_size
self.file_path = file_path
self.bot = bot
self._credentials = None
self._credentials: Optional['FileCredentials'] = None
self._id_attrs = (self.file_unique_id,)
@classmethod
def de_json(cls, data, bot):
if not data:
return None
return cls(bot=bot, **data)
def download(self, custom_path=None, out=None, timeout=None):
def download(self,
custom_path: str = None,
out: IO = None,
timeout: int = None) -> Union[str, IO]:
"""
Download this file. By default, the file is saved in the current working directory with its
original filename as reported by Telegram. If the file has no filename, it the file ID will
@ -147,13 +147,13 @@ class File(TelegramObject):
fobj.write(buf)
return filename
def _get_encoded_url(self):
def _get_encoded_url(self) -> str:
"""Convert any UTF-8 char in :obj:`File.file_path` into a url encoded ASCII string."""
sres = urllib_parse.urlsplit(self.file_path)
return urllib_parse.urlunsplit(urllib_parse.SplitResult(
sres.scheme, sres.netloc, urllib_parse.quote(sres.path), sres.query, sres.fragment))
def download_as_bytearray(self, buf=None):
def download_as_bytearray(self, buf: bytearray = None) -> bytes:
"""Download this file and return it as a bytearray.
Args:
@ -170,5 +170,5 @@ class File(TelegramObject):
buf.extend(self.bot.request.retrieve(self._get_encoded_url()))
return buf
def set_credentials(self, credentials):
def set_credentials(self, credentials: 'FileCredentials') -> None:
self._credentials = credentials

View file

@ -26,6 +26,8 @@ from uuid import uuid4
from telegram import TelegramError
from typing import IO, Tuple, Optional
DEFAULT_MIME_TYPE = 'application/octet-stream'
@ -48,7 +50,7 @@ class InputFile:
"""
def __init__(self, obj, filename=None, attach=None):
def __init__(self, obj: IO, filename: str = None, attach: bool = None):
self.filename = None
self.input_file_content = obj.read()
self.attach = 'attached' + uuid4().hex if attach else None
@ -70,15 +72,15 @@ class InputFile:
self.filename = self.mimetype.replace('/', '.')
@property
def field_tuple(self):
def field_tuple(self) -> Tuple[str, bytes, str]:
return self.filename, self.input_file_content, self.mimetype
@staticmethod
def is_image(stream):
def is_image(stream: bytes) -> str:
"""Check if the content file is an image by analyzing its headers.
Args:
stream (:obj:`str`): A str representing the content of a file.
stream (:obj:`bytes`): A byte stream representing the content of a file.
Returns:
:obj:`str`: The str mime-type of an image.
@ -91,9 +93,10 @@ class InputFile:
raise TelegramError('Could not parse file content')
@staticmethod
def is_file(obj):
def is_file(obj: object) -> bool:
return hasattr(obj, 'read')
def to_dict(self):
def to_dict(self) -> Optional[str]:
if self.attach:
return 'attach://' + self.attach
return None

View file

@ -19,7 +19,11 @@
"""Base class for Telegram InputMedia Objects."""
from telegram import TelegramObject, InputFile, PhotoSize, Animation, Video, Audio, Document
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
from typing import Union, IO, cast
from telegram.utils.types import FileLike
class InputMedia(TelegramObject):
@ -73,29 +77,32 @@ class InputMediaAnimation(InputMedia):
"""
def __init__(self,
media,
thumb=None,
caption=None,
parse_mode=DEFAULT_NONE,
width=None,
height=None,
duration=None):
media: Union[str, FileLike, Animation],
thumb: FileLike = None,
caption: str = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
width: int = None,
height: int = None,
duration: int = None):
self.type = 'animation'
if isinstance(media, Animation):
self.media = media.file_id
self.media: Union[str, InputFile] = media.file_id
self.width = media.width
self.height = media.height
self.duration = media.duration
elif InputFile.is_file(media):
media = cast(IO, media)
self.media = InputFile(media, attach=True)
else:
self.media = media
self.media = media # type: ignore[assignment]
if thumb:
self.thumb = thumb
if InputFile.is_file(self.thumb):
self.thumb = InputFile(self.thumb, attach=True)
if InputFile.is_file(thumb):
thumb = cast(IO, thumb)
self.thumb = InputFile(thumb, attach=True)
else:
self.thumb = thumb # type: ignore[assignment]
if caption:
self.caption = caption
@ -129,15 +136,19 @@ class InputMediaPhoto(InputMedia):
in :class:`telegram.ParseMode` for the available modes.
"""
def __init__(self, media, caption=None, parse_mode=DEFAULT_NONE):
def __init__(self,
media: Union[str, FileLike, PhotoSize],
caption: str = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE):
self.type = 'photo'
if isinstance(media, PhotoSize):
self.media = media.file_id
self.media: Union[str, InputFile] = media.file_id
elif InputFile.is_file(media):
media = cast(IO, media)
self.media = InputFile(media, attach=True)
else:
self.media = media
self.media = media # type: ignore[assignment]
if caption:
self.caption = caption
@ -189,24 +200,34 @@ class InputMediaVideo(InputMedia):
by Telegram.
"""
def __init__(self, media, caption=None, width=None, height=None, duration=None,
supports_streaming=None, parse_mode=DEFAULT_NONE, thumb=None):
def __init__(self,
media: Union[str, FileLike, Video],
caption: str = None,
width: int = None,
height: int = None,
duration: int = None,
supports_streaming: bool = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
thumb: FileLike = None):
self.type = 'video'
if isinstance(media, Video):
self.media = media.file_id
self.media: Union[str, InputFile] = media.file_id
self.width = media.width
self.height = media.height
self.duration = media.duration
elif InputFile.is_file(media):
media = cast(IO, media)
self.media = InputFile(media, attach=True)
else:
self.media = media
self.media = media # type: ignore[assignment]
if thumb:
self.thumb = thumb
if InputFile.is_file(self.thumb):
self.thumb = InputFile(self.thumb, attach=True)
if InputFile.is_file(thumb):
thumb = cast(IO, thumb)
self.thumb = InputFile(thumb, attach=True)
else:
self.thumb = thumb # type: ignore[assignment]
if caption:
self.caption = caption
@ -261,24 +282,33 @@ class InputMediaAudio(InputMedia):
optional arguments.
"""
def __init__(self, media, thumb=None, caption=None, parse_mode=DEFAULT_NONE,
duration=None, performer=None, title=None):
def __init__(self,
media: Union[str, FileLike, Audio],
thumb: FileLike = None,
caption: str = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
duration: int = None,
performer: str = None,
title: str = None):
self.type = 'audio'
if isinstance(media, Audio):
self.media = media.file_id
self.media: Union[str, InputFile] = media.file_id
self.duration = media.duration
self.performer = media.performer
self.title = media.title
elif InputFile.is_file(media):
media = cast(IO, media)
self.media = InputFile(media, attach=True)
else:
self.media = media
self.media = media # type: ignore[assignment]
if thumb:
self.thumb = thumb
if InputFile.is_file(self.thumb):
self.thumb = InputFile(self.thumb, attach=True)
if InputFile.is_file(thumb):
thumb = cast(IO, thumb)
self.thumb = InputFile(thumb, attach=True)
else:
self.thumb = thumb # type: ignore[assignment]
if caption:
self.caption = caption
@ -318,20 +348,27 @@ class InputMediaDocument(InputMedia):
Thumbnails can't be reused and can be only uploaded as a new file.
"""
def __init__(self, media, thumb=None, caption=None, parse_mode=DEFAULT_NONE):
def __init__(self,
media: Union[str, FileLike, Document],
thumb: FileLike = None,
caption: str = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE):
self.type = 'document'
if isinstance(media, Document):
self.media = media.file_id
self.media: Union[str, InputFile] = media.file_id
elif InputFile.is_file(media):
media = cast(IO, media)
self.media = InputFile(media, attach=True)
else:
self.media = media
self.media = media # type: ignore[assignment]
if thumb:
self.thumb = thumb
if InputFile.is_file(self.thumb):
self.thumb = InputFile(self.thumb, attach=True)
if InputFile.is_file(thumb):
thumb = cast(IO, thumb)
self.thumb = InputFile(thumb, attach=True)
else:
self.thumb = thumb # type: ignore[assignment]
if caption:
self.caption = caption

View file

@ -19,6 +19,7 @@
"""This module contains an object that represents a Telegram Location."""
from telegram import TelegramObject
from typing import Any
class Location(TelegramObject):
@ -38,16 +39,9 @@ class Location(TelegramObject):
"""
def __init__(self, longitude, latitude, **kwargs):
def __init__(self, longitude: float, latitude: float, **kwargs: Any):
# Required
self.longitude = float(longitude)
self.latitude = float(latitude)
self._id_attrs = (self.longitude, self.latitude)
@classmethod
def de_json(cls, data, bot):
if not data:
return None
return cls(**data)

View file

@ -19,6 +19,10 @@
"""This module contains an object that represents a Telegram PhotoSize."""
from telegram import TelegramObject
from telegram.utils.types import JSONDict
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot, File
class PhotoSize(TelegramObject):
@ -52,13 +56,13 @@ class PhotoSize(TelegramObject):
"""
def __init__(self,
file_id,
file_unique_id,
width,
height,
file_size=None,
bot=None,
**kwargs):
file_id: str,
file_unique_id: str,
width: int,
height: int,
file_size: int = None,
bot: 'Bot' = None,
**kwargs: Any):
# Required
self.file_id = str(file_id)
self.file_unique_id = str(file_unique_id)
@ -70,25 +74,7 @@ class PhotoSize(TelegramObject):
self._id_attrs = (self.file_unique_id,)
@classmethod
def de_json(cls, data, bot):
if not data:
return None
return cls(bot=bot, **data)
@classmethod
def de_list(cls, data, bot):
if not data:
return []
photos = list()
for photo in data:
photos.append(cls.de_json(photo, bot))
return photos
def get_file(self, timeout=None, api_kwargs=None):
def get_file(self, timeout: int = None, api_kwargs: JSONDict = None) -> 'File':
"""Convenience wrapper over :attr:`telegram.Bot.get_file`
Args:

View file

@ -19,6 +19,10 @@
"""This module contains objects that represents stickers."""
from telegram import PhotoSize, TelegramObject
from telegram.utils.types import JSONDict
from typing import Any, Optional, List, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot, File
class Sticker(TelegramObject):
@ -68,18 +72,18 @@ class Sticker(TelegramObject):
"""
def __init__(self,
file_id,
file_unique_id,
width,
height,
is_animated,
thumb=None,
emoji=None,
file_size=None,
set_name=None,
mask_position=None,
bot=None,
**kwargs):
file_id: str,
file_unique_id: str,
width: int,
height: int,
is_animated: bool,
thumb: PhotoSize = None,
emoji: str = None,
file_size: int = None,
set_name: str = None,
mask_position: 'MaskPosition' = None,
bot: 'Bot' = None,
**kwargs: Any):
# Required
self.file_id = str(file_id)
self.file_unique_id = str(file_unique_id)
@ -97,25 +101,18 @@ class Sticker(TelegramObject):
self._id_attrs = (self.file_unique_id,)
@classmethod
def de_json(cls, data, bot):
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Sticker']:
data = cls.parse_data(data)
if not data:
return None
data = super().de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
data['mask_position'] = MaskPosition.de_json(data.get('mask_position'), bot)
return cls(bot=bot, **data)
@classmethod
def de_list(cls, data, bot):
if not data:
return list()
return [cls.de_json(d, bot) for d in data]
def get_file(self, timeout=None, api_kwargs=None):
def get_file(self, timeout: str = None, api_kwargs: JSONDict = None) -> 'File':
"""Convenience wrapper over :attr:`telegram.Bot.get_file`
Args:
@ -161,8 +158,15 @@ class StickerSet(TelegramObject):
"""
def __init__(self, name, title, is_animated, contains_masks, stickers, bot=None, thumb=None,
**kwargs):
def __init__(self,
name: str,
title: str,
is_animated: bool,
contains_masks: bool,
stickers: List[Sticker],
bot: 'Bot' = None,
thumb: PhotoSize = None,
**kwargs: Any):
self.name = name
self.title = title
self.is_animated = is_animated
@ -174,18 +178,16 @@ class StickerSet(TelegramObject):
self._id_attrs = (self.name,)
@classmethod
def de_json(cls, data, bot):
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['StickerSet']:
if not data:
return None
data = super().de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
data['stickers'] = Sticker.de_list(data.get('stickers'), bot)
return cls(bot=bot, **data)
def to_dict(self):
def to_dict(self) -> JSONDict:
data = super().to_dict()
data['stickers'] = [s.to_dict() for s in data.get('stickers')]
@ -225,16 +227,16 @@ class MaskPosition(TelegramObject):
scale (:obj:`float`): Mask scaling coefficient. For example, 2.0 means double size.
"""
FOREHEAD = 'forehead'
FOREHEAD: str = 'forehead'
""":obj:`str`: 'forehead'"""
EYES = 'eyes'
EYES: str = 'eyes'
""":obj:`str`: 'eyes'"""
MOUTH = 'mouth'
MOUTH: str = 'mouth'
""":obj:`str`: 'mouth'"""
CHIN = 'chin'
CHIN: str = 'chin'
""":obj:`str`: 'chin'"""
def __init__(self, point, x_shift, y_shift, scale, **kwargs):
def __init__(self, point: str, x_shift: float, y_shift: float, scale: float, **kwargs: Any):
self.point = point
self.x_shift = x_shift
self.y_shift = y_shift
@ -243,7 +245,9 @@ class MaskPosition(TelegramObject):
self._id_attrs = (self.point, self.x_shift, self.y_shift, self.scale)
@classmethod
def de_json(cls, data, bot):
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['MaskPosition']:
data = cls.parse_data(data)
if data is None:
return None

View file

@ -19,6 +19,10 @@
"""This module contains an object that represents a Telegram Venue."""
from telegram import TelegramObject, Location
from telegram.utils.types import JSONDict
from typing import Any, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot
class Venue(TelegramObject):
@ -46,8 +50,13 @@ class Venue(TelegramObject):
"""
def __init__(self, location, title, address, foursquare_id=None, foursquare_type=None,
**kwargs):
def __init__(self,
location: Location,
title: str,
address: str,
foursquare_id: str = None,
foursquare_type: str = None,
**kwargs: Any):
# Required
self.location = location
self.title = title
@ -59,8 +68,8 @@ class Venue(TelegramObject):
self._id_attrs = (self.location, self.title)
@classmethod
def de_json(cls, data, bot):
data = super().de_json(data, bot)
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Venue']:
data = cls.parse_data(data)
if not data:
return None

View file

@ -19,6 +19,10 @@
"""This module contains an object that represents a Telegram Video."""
from telegram import PhotoSize, TelegramObject
from telegram.utils.types import JSONDict
from typing import Any, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot, File
class Video(TelegramObject):
@ -58,16 +62,16 @@ class Video(TelegramObject):
"""
def __init__(self,
file_id,
file_unique_id,
width,
height,
duration,
thumb=None,
mime_type=None,
file_size=None,
bot=None,
**kwargs):
file_id: str,
file_unique_id: str,
width: int,
height: int,
duration: int,
thumb: PhotoSize = None,
mime_type: str = None,
file_size: int = None,
bot: 'Bot' = None,
**kwargs: Any):
# Required
self.file_id = str(file_id)
self.file_unique_id = str(file_unique_id)
@ -83,17 +87,17 @@ class Video(TelegramObject):
self._id_attrs = (self.file_unique_id,)
@classmethod
def de_json(cls, data, bot):
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Video']:
data = cls.parse_data(data)
if not data:
return None
data = super().de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
return cls(bot=bot, **data)
def get_file(self, timeout=None, api_kwargs=None):
def get_file(self, timeout: int = None, api_kwargs: JSONDict = None) -> 'File':
"""Convenience wrapper over :attr:`telegram.Bot.get_file`
Args:

View file

@ -19,6 +19,10 @@
"""This module contains an object that represents a Telegram VideoNote."""
from telegram import PhotoSize, TelegramObject
from telegram.utils.types import JSONDict
from typing import Any, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot, File
class VideoNote(TelegramObject):
@ -55,14 +59,14 @@ class VideoNote(TelegramObject):
"""
def __init__(self,
file_id,
file_unique_id,
length,
duration,
thumb=None,
file_size=None,
bot=None,
**kwargs):
file_id: str,
file_unique_id: str,
length: int,
duration: int,
thumb: PhotoSize = None,
file_size: int = None,
bot: 'Bot' = None,
**kwargs: Any):
# Required
self.file_id = str(file_id)
self.file_unique_id = str(file_unique_id)
@ -76,17 +80,17 @@ class VideoNote(TelegramObject):
self._id_attrs = (self.file_unique_id,)
@classmethod
def de_json(cls, data, bot):
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['VideoNote']:
data = cls.parse_data(data)
if not data:
return None
data = super().de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
return cls(bot=bot, **data)
def get_file(self, timeout=None, api_kwargs=None):
def get_file(self, timeout: int = None, api_kwargs: JSONDict = None) -> 'File':
"""Convenience wrapper over :attr:`telegram.Bot.get_file`
Args:

View file

@ -19,6 +19,10 @@
"""This module contains an object that represents a Telegram Voice."""
from telegram import TelegramObject
from telegram.utils.types import JSONDict
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot, File
class Voice(TelegramObject):
@ -52,13 +56,13 @@ class Voice(TelegramObject):
"""
def __init__(self,
file_id,
file_unique_id,
duration,
mime_type=None,
file_size=None,
bot=None,
**kwargs):
file_id: str,
file_unique_id: str,
duration: int,
mime_type: str = None,
file_size: int = None,
bot: 'Bot' = None,
**kwargs: Any):
# Required
self.file_id = str(file_id)
self.file_unique_id = str(file_unique_id)
@ -70,16 +74,7 @@ class Voice(TelegramObject):
self._id_attrs = (self.file_unique_id,)
@classmethod
def de_json(cls, data, bot):
if not data:
return None
data = super().de_json(data, bot)
return cls(bot=bot, **data)
def get_file(self, timeout=None, api_kwargs=None):
def get_file(self, timeout: int = None, api_kwargs: JSONDict = None) -> 'File':
"""Convenience wrapper over :attr:`telegram.Bot.get_file`
Args:

View file

@ -19,6 +19,7 @@
"""This module contains an object that represents a Telegram ForceReply."""
from telegram import ReplyMarkup
from typing import Any
class ForceReply(ReplyMarkup):
@ -48,7 +49,7 @@ class ForceReply(ReplyMarkup):
"""
def __init__(self, force_reply=True, selective=False, **kwargs):
def __init__(self, force_reply: bool = True, selective: bool = False, **kwargs: Any):
# Required
self.force_reply = bool(force_reply)
# Optionals

View file

@ -21,6 +21,10 @@
import sys
from telegram import MessageEntity, TelegramObject, Animation, PhotoSize
from telegram.utils.types import JSONDict
from typing import List, Any, Dict, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot
class Game(TelegramObject):
@ -63,13 +67,13 @@ class Game(TelegramObject):
"""
def __init__(self,
title,
description,
photo,
text=None,
text_entities=None,
animation=None,
**kwargs):
title: str,
description: str,
photo: List[PhotoSize],
text: str = None,
text_entities: List[MessageEntity] = None,
animation: Animation = None,
**kwargs: Any):
# Required
self.title = title
self.description = description
@ -82,19 +86,19 @@ class Game(TelegramObject):
self._id_attrs = (self.title, self.description, self.photo)
@classmethod
def de_json(cls, data, bot):
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Game']:
data = cls.parse_data(data)
if not data:
return None
data = super().de_json(data, bot)
data['photo'] = PhotoSize.de_list(data.get('photo'), bot)
data['text_entities'] = MessageEntity.de_list(data.get('text_entities'), bot)
data['animation'] = Animation.de_json(data.get('animation'), bot)
return cls(**data)
def to_dict(self):
def to_dict(self) -> JSONDict:
data = super().to_dict()
data['photo'] = [p.to_dict() for p in self.photo]
@ -103,7 +107,7 @@ class Game(TelegramObject):
return data
def parse_text_entity(self, entity):
def parse_text_entity(self, entity: MessageEntity) -> str:
"""Returns the text from a given :class:`telegram.MessageEntity`.
Note:
@ -118,7 +122,13 @@ class Game(TelegramObject):
Returns:
:obj:`str`: The text of the given entity.
Raises:
RuntimeError: If this game has no text.
"""
if not self.text:
raise RuntimeError("This Game has no 'text'.")
# Is it a narrow build, if so we don't need to convert
if sys.maxunicode == 0xffff:
return self.text[entity.offset:entity.offset + entity.length]
@ -128,7 +138,7 @@ class Game(TelegramObject):
return entity_text.decode('utf-16-le')
def parse_text_entities(self, types=None):
def parse_text_entities(self, types: List[str] = None) -> Dict[MessageEntity, str]:
"""
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
It contains entities from this message filtered by their ``type`` attribute as the key, and
@ -154,8 +164,8 @@ class Game(TelegramObject):
return {
entity: self.parse_text_entity(entity)
for entity in self.text_entities if entity.type in types
for entity in (self.text_entities or []) if entity.type in types
}
def __hash__(self):
def __hash__(self) -> int:
return hash((self.title, self.description, tuple(p for p in self.photo)))

View file

@ -19,6 +19,10 @@
"""This module contains an object that represents a Telegram GameHighScore."""
from telegram import TelegramObject, User
from telegram.utils.types import JSONDict
from typing import Optional, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot
class GameHighScore(TelegramObject):
@ -39,7 +43,7 @@ class GameHighScore(TelegramObject):
"""
def __init__(self, position, user, score):
def __init__(self, position: int, user: User, score: int):
self.position = position
self.user = user
self.score = score
@ -47,12 +51,12 @@ class GameHighScore(TelegramObject):
self._id_attrs = (self.position, self.user, self.score)
@classmethod
def de_json(cls, data, bot):
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['GameHighScore']:
data = cls.parse_data(data)
if not data:
return None
data = super().de_json(data, bot)
data['user'] = User.de_json(data.get('user'), bot)
return cls(**data)

View file

@ -19,6 +19,9 @@
"""This module contains an object that represents a Telegram InlineKeyboardButton."""
from telegram import TelegramObject
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import CallbackGame, LoginUrl
class InlineKeyboardButton(TelegramObject):
@ -79,15 +82,15 @@ class InlineKeyboardButton(TelegramObject):
"""
def __init__(self,
text,
url=None,
callback_data=None,
switch_inline_query=None,
switch_inline_query_current_chat=None,
callback_game=None,
pay=None,
login_url=None,
**kwargs):
text: str,
url: str = None,
callback_data: str = None,
switch_inline_query: str = None,
switch_inline_query_current_chat: str = None,
callback_game: 'CallbackGame' = None,
pay: bool = None,
login_url: 'LoginUrl' = None,
**kwargs: Any):
# Required
self.text = text
@ -110,10 +113,3 @@ class InlineKeyboardButton(TelegramObject):
self.callback_game,
self.pay,
)
@classmethod
def de_json(cls, data, bot):
if not data:
return None
return cls(**data)

View file

@ -19,6 +19,10 @@
"""This module contains an object that represents a Telegram InlineKeyboardMarkup."""
from telegram import ReplyMarkup, InlineKeyboardButton
from telegram.utils.types import JSONDict
from typing import Any, List, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot
class InlineKeyboardMarkup(ReplyMarkup):
@ -39,11 +43,11 @@ class InlineKeyboardMarkup(ReplyMarkup):
"""
def __init__(self, inline_keyboard, **kwargs):
def __init__(self, inline_keyboard: List[List[InlineKeyboardButton]], **kwargs: Any):
# Required
self.inline_keyboard = inline_keyboard
def to_dict(self):
def to_dict(self) -> JSONDict:
data = super().to_dict()
data['inline_keyboard'] = []
@ -53,20 +57,26 @@ class InlineKeyboardMarkup(ReplyMarkup):
return data
@classmethod
def de_json(cls, data, bot):
def de_json(cls, data: Optional[JSONDict],
bot: 'Bot') -> Optional['InlineKeyboardMarkup']:
data = cls.parse_data(data)
if not data:
return None
keyboard = []
for row in data['inline_keyboard']:
tmp = []
for col in row:
tmp.append(InlineKeyboardButton.de_json(col, bot))
btn = InlineKeyboardButton.de_json(col, bot)
if btn:
tmp.append(btn)
keyboard.append(tmp)
return cls(keyboard)
@classmethod
def from_button(cls, button, **kwargs):
def from_button(cls, button: InlineKeyboardButton, **kwargs: Any) -> 'InlineKeyboardMarkup':
"""Shortcut for::
InlineKeyboardMarkup([[button]], **kwargs)
@ -81,7 +91,8 @@ class InlineKeyboardMarkup(ReplyMarkup):
return cls([[button]], **kwargs)
@classmethod
def from_row(cls, button_row, **kwargs):
def from_row(cls, button_row: List[InlineKeyboardButton],
**kwargs: Any) -> 'InlineKeyboardMarkup':
"""Shortcut for::
InlineKeyboardMarkup([button_row], **kwargs)
@ -97,7 +108,8 @@ class InlineKeyboardMarkup(ReplyMarkup):
return cls([button_row], **kwargs)
@classmethod
def from_column(cls, button_column, **kwargs):
def from_column(cls, button_column: List[InlineKeyboardButton],
**kwargs: Any) -> 'InlineKeyboardMarkup':
"""Shortcut for::
InlineKeyboardMarkup([[button] for button in button_column], **kwargs)
@ -113,7 +125,7 @@ class InlineKeyboardMarkup(ReplyMarkup):
button_grid = [[button] for button in button_column]
return cls(button_grid, **kwargs)
def __eq__(self, other):
def __eq__(self, other: object) -> bool:
if isinstance(other, self.__class__):
if len(self.inline_keyboard) != len(other.inline_keyboard):
return False
@ -126,5 +138,5 @@ class InlineKeyboardMarkup(ReplyMarkup):
return True
return super(InlineKeyboardMarkup, self).__eq__(other) # pylint: disable=no-member
def __hash__(self):
def __hash__(self) -> int:
return hash(tuple(tuple(button for button in row) for row in self.inline_keyboard))

View file

@ -20,6 +20,10 @@
"""This module contains an object that represents a Telegram InlineQuery."""
from telegram import TelegramObject, User, Location
from telegram.utils.types import JSONDict
from typing import Any, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot
class InlineQuery(TelegramObject):
@ -53,7 +57,14 @@ class InlineQuery(TelegramObject):
"""
def __init__(self, id, from_user, query, offset, location=None, bot=None, **kwargs):
def __init__(self,
id: str,
from_user: User,
query: str,
offset: str,
location: Location = None,
bot: 'Bot' = None,
**kwargs: Any):
# Required
self.id = id
self.from_user = from_user
@ -67,8 +78,8 @@ class InlineQuery(TelegramObject):
self._id_attrs = (self.id,)
@classmethod
def de_json(cls, data, bot):
data = super().de_json(data, bot)
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['InlineQuery']:
data = cls.parse_data(data)
if not data:
return None
@ -78,7 +89,7 @@ class InlineQuery(TelegramObject):
return cls(bot=bot, **data)
def answer(self, *args, auto_pagination=False, **kwargs):
def answer(self, *args: Any, auto_pagination: bool = False, **kwargs: Any) -> bool:
"""Shortcut for::
bot.answer_inline_query(update.inline_query.id,

View file

@ -19,6 +19,7 @@
"""This module contains the classes that represent Telegram InlineQueryResult."""
from telegram import TelegramObject
from typing import Any
class InlineQueryResult(TelegramObject):
@ -38,7 +39,7 @@ class InlineQueryResult(TelegramObject):
"""
def __init__(self, type, id, **kwargs):
def __init__(self, type: str, id: str, **kwargs: Any):
# Required
self.type = str(type)
self.id = str(id)
@ -46,9 +47,9 @@ class InlineQueryResult(TelegramObject):
self._id_attrs = (self.id,)
@property
def _has_parse_mode(self):
def _has_parse_mode(self) -> bool:
return hasattr(self, 'parse_mode')
@property
def _has_input_message_content(self):
def _has_input_message_content(self) -> bool:
return hasattr(self, 'input_message_content')

View file

@ -19,6 +19,9 @@
"""This module contains the classes that represent Telegram InlineQueryResultArticle."""
from telegram import InlineQueryResult
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import InputMessageContent, ReplyMarkup
class InlineQueryResultArticle(InlineQueryResult):
@ -59,17 +62,17 @@ class InlineQueryResultArticle(InlineQueryResult):
"""
def __init__(self,
id,
title,
input_message_content,
reply_markup=None,
url=None,
hide_url=None,
description=None,
thumb_url=None,
thumb_width=None,
thumb_height=None,
**kwargs):
id: str,
title: str,
input_message_content: 'InputMessageContent',
reply_markup: 'ReplyMarkup' = None,
url: str = None,
hide_url: bool = None,
description: str = None,
thumb_url: str = None,
thumb_width: int = None,
thumb_height: int = None,
**kwargs: Any):
# Required
super().__init__('article', id)

View file

@ -19,7 +19,10 @@
"""This module contains the classes that represent Telegram InlineQueryResultAudio."""
from telegram import InlineQueryResult
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
from typing import Any, Union, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import InputMessageContent, ReplyMarkup
class InlineQueryResultAudio(InlineQueryResult):
@ -63,16 +66,16 @@ class InlineQueryResultAudio(InlineQueryResult):
"""
def __init__(self,
id,
audio_url,
title,
performer=None,
audio_duration=None,
caption=None,
reply_markup=None,
input_message_content=None,
parse_mode=DEFAULT_NONE,
**kwargs):
id: str,
audio_url: str,
title: str,
performer: str = None,
audio_duration: int = None,
caption: str = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
**kwargs: Any):
# Required
super().__init__('audio', id)

View file

@ -19,7 +19,10 @@
"""This module contains the classes that represent Telegram InlineQueryResultCachedAudio."""
from telegram import InlineQueryResult
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
from typing import Any, Union, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import InputMessageContent, ReplyMarkup
class InlineQueryResultCachedAudio(InlineQueryResult):
@ -57,13 +60,13 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
"""
def __init__(self,
id,
audio_file_id,
caption=None,
reply_markup=None,
input_message_content=None,
parse_mode=DEFAULT_NONE,
**kwargs):
id: str,
audio_file_id: str,
caption: str = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
**kwargs: Any):
# Required
super().__init__('audio', id)
self.audio_file_id = audio_file_id

View file

@ -19,7 +19,10 @@
"""This module contains the classes that represent Telegram InlineQueryResultCachedDocument."""
from telegram import InlineQueryResult
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
from typing import Any, Union, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import InputMessageContent, ReplyMarkup
class InlineQueryResultCachedDocument(InlineQueryResult):
@ -63,15 +66,15 @@ class InlineQueryResultCachedDocument(InlineQueryResult):
"""
def __init__(self,
id,
title,
document_file_id,
description=None,
caption=None,
reply_markup=None,
input_message_content=None,
parse_mode=DEFAULT_NONE,
**kwargs):
id: str,
title: str,
document_file_id: str,
description: str = None,
caption: str = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
**kwargs: Any):
# Required
super().__init__('document', id)
self.title = title

View file

@ -19,7 +19,10 @@
"""This module contains the classes that represent Telegram InlineQueryResultCachedGif."""
from telegram import InlineQueryResult
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
from typing import Any, Union, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import InputMessageContent, ReplyMarkup
class InlineQueryResultCachedGif(InlineQueryResult):
@ -62,14 +65,14 @@ class InlineQueryResultCachedGif(InlineQueryResult):
"""
def __init__(self,
id,
gif_file_id,
title=None,
caption=None,
reply_markup=None,
input_message_content=None,
parse_mode=DEFAULT_NONE,
**kwargs):
id: str,
gif_file_id: str,
title: str = None,
caption: str = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
**kwargs: Any):
# Required
super().__init__('gif', id)
self.gif_file_id = gif_file_id

View file

@ -19,7 +19,10 @@
"""This module contains the classes that represent Telegram InlineQueryResultMpeg4Gif."""
from telegram import InlineQueryResult
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
from typing import Any, Union, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import InputMessageContent, ReplyMarkup
class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
@ -62,14 +65,14 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
"""
def __init__(self,
id,
mpeg4_file_id,
title=None,
caption=None,
reply_markup=None,
input_message_content=None,
parse_mode=DEFAULT_NONE,
**kwargs):
id: str,
mpeg4_file_id: str,
title: str = None,
caption: str = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
**kwargs: Any):
# Required
super().__init__('mpeg4_gif', id)
self.mpeg4_file_id = mpeg4_file_id

View file

@ -19,7 +19,10 @@
"""This module contains the classes that represent Telegram InlineQueryResultPhoto"""
from telegram import InlineQueryResult
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
from typing import Any, Union, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import InputMessageContent, ReplyMarkup
class InlineQueryResultCachedPhoto(InlineQueryResult):
@ -64,15 +67,15 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
"""
def __init__(self,
id,
photo_file_id,
title=None,
description=None,
caption=None,
reply_markup=None,
input_message_content=None,
parse_mode=DEFAULT_NONE,
**kwargs):
id: str,
photo_file_id: str,
title: str = None,
description: str = None,
caption: str = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
**kwargs: Any):
# Required
super().__init__('photo', id)
self.photo_file_id = photo_file_id

View file

@ -19,6 +19,9 @@
"""This module contains the classes that represent Telegram InlineQueryResultCachedSticker."""
from telegram import InlineQueryResult
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import ReplyMarkup, InputMessageContent
class InlineQueryResultCachedSticker(InlineQueryResult):
@ -48,11 +51,11 @@ class InlineQueryResultCachedSticker(InlineQueryResult):
"""
def __init__(self,
id,
sticker_file_id,
reply_markup=None,
input_message_content=None,
**kwargs):
id: str,
sticker_file_id: str,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
**kwargs: Any):
# Required
super().__init__('sticker', id)
self.sticker_file_id = sticker_file_id

View file

@ -19,7 +19,10 @@
"""This module contains the classes that represent Telegram InlineQueryResultCachedVideo."""
from telegram import InlineQueryResult
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
from typing import Any, Union, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import InputMessageContent, ReplyMarkup
class InlineQueryResultCachedVideo(InlineQueryResult):
@ -64,15 +67,15 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
"""
def __init__(self,
id,
video_file_id,
title,
description=None,
caption=None,
reply_markup=None,
input_message_content=None,
parse_mode=DEFAULT_NONE,
**kwargs):
id: str,
video_file_id: str,
title: str,
description: str = None,
caption: str = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
**kwargs: Any):
# Required
super().__init__('video', id)
self.video_file_id = video_file_id

View file

@ -19,7 +19,10 @@
"""This module contains the classes that represent Telegram InlineQueryResultCachedVoice."""
from telegram import InlineQueryResult
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
from typing import Any, Union, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import InputMessageContent, ReplyMarkup
class InlineQueryResultCachedVoice(InlineQueryResult):
@ -59,14 +62,14 @@ class InlineQueryResultCachedVoice(InlineQueryResult):
"""
def __init__(self,
id,
voice_file_id,
title,
caption=None,
reply_markup=None,
input_message_content=None,
parse_mode=DEFAULT_NONE,
**kwargs):
id: str,
voice_file_id: str,
title: str,
caption: str = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
**kwargs: Any):
# Required
super().__init__('voice', id)
self.voice_file_id = voice_file_id

View file

@ -19,6 +19,9 @@
"""This module contains the classes that represent Telegram InlineQueryResultContact."""
from telegram import InlineQueryResult
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import ReplyMarkup, InputMessageContent
class InlineQueryResultContact(InlineQueryResult):
@ -62,17 +65,17 @@ class InlineQueryResultContact(InlineQueryResult):
"""
def __init__(self,
id,
phone_number,
first_name,
last_name=None,
reply_markup=None,
input_message_content=None,
thumb_url=None,
thumb_width=None,
thumb_height=None,
vcard=None,
**kwargs):
id: str,
phone_number: str,
first_name: str,
last_name: str = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
thumb_url: str = None,
thumb_width: int = None,
thumb_height: int = None,
vcard: str = None,
**kwargs: Any):
# Required
super().__init__('contact', id)
self.phone_number = phone_number

View file

@ -19,7 +19,10 @@
"""This module contains the classes that represent Telegram InlineQueryResultDocument"""
from telegram import InlineQueryResult
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
from typing import Any, Union, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import InputMessageContent, ReplyMarkup
class InlineQueryResultDocument(InlineQueryResult):
@ -74,19 +77,19 @@ class InlineQueryResultDocument(InlineQueryResult):
"""
def __init__(self,
id,
document_url,
title,
mime_type,
caption=None,
description=None,
reply_markup=None,
input_message_content=None,
thumb_url=None,
thumb_width=None,
thumb_height=None,
parse_mode=DEFAULT_NONE,
**kwargs):
id: str,
document_url: str,
title: str,
mime_type: str,
caption: str = None,
description: str = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
thumb_url: str = None,
thumb_width: int = None,
thumb_height: int = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
**kwargs: Any):
# Required
super().__init__('document', id)
self.document_url = document_url

View file

@ -19,6 +19,9 @@
"""This module contains the classes that represent Telegram InlineQueryResultGame."""
from telegram import InlineQueryResult
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import ReplyMarkup
class InlineQueryResultGame(InlineQueryResult):
@ -40,7 +43,11 @@ class InlineQueryResultGame(InlineQueryResult):
"""
def __init__(self, id, game_short_name, reply_markup=None, **kwargs):
def __init__(self,
id: str,
game_short_name: str,
reply_markup: 'ReplyMarkup' = None,
**kwargs: Any):
# Required
super().__init__('game', id)
self.id = id

View file

@ -19,7 +19,10 @@
"""This module contains the classes that represent Telegram InlineQueryResultGif."""
from telegram import InlineQueryResult
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
from typing import Any, Union, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import InputMessageContent, ReplyMarkup
class InlineQueryResultGif(InlineQueryResult):
@ -74,19 +77,19 @@ class InlineQueryResultGif(InlineQueryResult):
"""
def __init__(self,
id,
gif_url,
thumb_url,
gif_width=None,
gif_height=None,
title=None,
caption=None,
reply_markup=None,
input_message_content=None,
gif_duration=None,
parse_mode=DEFAULT_NONE,
thumb_mime_type=None,
**kwargs):
id: str,
gif_url: str,
thumb_url: str,
gif_width: int = None,
gif_height: int = None,
title: str = None,
caption: str = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
gif_duration: int = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
thumb_mime_type: str = None,
**kwargs: Any):
# Required
super().__init__('gif', id)

View file

@ -19,6 +19,9 @@
"""This module contains the classes that represent Telegram InlineQueryResultLocation."""
from telegram import InlineQueryResult
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import ReplyMarkup, InputMessageContent
class InlineQueryResultLocation(InlineQueryResult):
@ -62,17 +65,17 @@ class InlineQueryResultLocation(InlineQueryResult):
"""
def __init__(self,
id,
latitude,
longitude,
title,
live_period=None,
reply_markup=None,
input_message_content=None,
thumb_url=None,
thumb_width=None,
thumb_height=None,
**kwargs):
id: str,
latitude: float,
longitude: float,
title: str,
live_period: int = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
thumb_url: str = None,
thumb_width: int = None,
thumb_height: int = None,
**kwargs: Any):
# Required
super().__init__('location', id)
self.latitude = latitude

View file

@ -19,7 +19,10 @@
"""This module contains the classes that represent Telegram InlineQueryResultMpeg4Gif."""
from telegram import InlineQueryResult
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
from typing import Any, Union, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import InputMessageContent, ReplyMarkup
class InlineQueryResultMpeg4Gif(InlineQueryResult):
@ -74,19 +77,19 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
"""
def __init__(self,
id,
mpeg4_url,
thumb_url,
mpeg4_width=None,
mpeg4_height=None,
title=None,
caption=None,
reply_markup=None,
input_message_content=None,
mpeg4_duration=None,
parse_mode=DEFAULT_NONE,
thumb_mime_type=None,
**kwargs):
id: str,
mpeg4_url: str,
thumb_url: str,
mpeg4_width: int = None,
mpeg4_height: int = None,
title: str = None,
caption: str = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
mpeg4_duration: int = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
thumb_mime_type: str = None,
**kwargs: Any):
# Required
super().__init__('mpeg4_gif', id)

View file

@ -19,7 +19,10 @@
"""This module contains the classes that represent Telegram InlineQueryResultPhoto."""
from telegram import InlineQueryResult
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
from typing import Any, Union, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import InputMessageContent, ReplyMarkup
class InlineQueryResultPhoto(InlineQueryResult):
@ -71,18 +74,18 @@ class InlineQueryResultPhoto(InlineQueryResult):
"""
def __init__(self,
id,
photo_url,
thumb_url,
photo_width=None,
photo_height=None,
title=None,
description=None,
caption=None,
reply_markup=None,
input_message_content=None,
parse_mode=DEFAULT_NONE,
**kwargs):
id: str,
photo_url: str,
thumb_url: str,
photo_width: int = None,
photo_height: int = None,
title: str = None,
description: str = None,
caption: str = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
**kwargs: Any):
# Required
super().__init__('photo', id)
self.photo_url = photo_url

View file

@ -19,6 +19,9 @@
"""This module contains the classes that represent Telegram InlineQueryResultVenue."""
from telegram import InlineQueryResult
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import ReplyMarkup, InputMessageContent
class InlineQueryResultVenue(InlineQueryResult):
@ -68,19 +71,19 @@ class InlineQueryResultVenue(InlineQueryResult):
"""
def __init__(self,
id,
latitude,
longitude,
title,
address,
foursquare_id=None,
foursquare_type=None,
reply_markup=None,
input_message_content=None,
thumb_url=None,
thumb_width=None,
thumb_height=None,
**kwargs):
id: str,
latitude: float,
longitude: float,
title: str,
address: str,
foursquare_id: str = None,
foursquare_type: str = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
thumb_url: str = None,
thumb_width: int = None,
thumb_height: int = None,
**kwargs: Any):
# Required
super().__init__('venue', id)

View file

@ -19,7 +19,10 @@
"""This module contains the classes that represent Telegram InlineQueryResultVideo."""
from telegram import InlineQueryResult
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
from typing import Any, Union, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import InputMessageContent, ReplyMarkup
class InlineQueryResultVideo(InlineQueryResult):
@ -81,20 +84,20 @@ class InlineQueryResultVideo(InlineQueryResult):
"""
def __init__(self,
id,
video_url,
mime_type,
thumb_url,
title,
caption=None,
video_width=None,
video_height=None,
video_duration=None,
description=None,
reply_markup=None,
input_message_content=None,
parse_mode=DEFAULT_NONE,
**kwargs):
id: str,
video_url: str,
mime_type: str,
thumb_url: str,
title: str,
caption: str = None,
video_width: int = None,
video_height: int = None,
video_duration: int = None,
description: str = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
**kwargs: Any):
# Required
super().__init__('video', id)

View file

@ -19,7 +19,10 @@
"""This module contains the classes that represent Telegram InlineQueryResultVoice."""
from telegram import InlineQueryResult
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
from typing import Any, Union, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import InputMessageContent, ReplyMarkup
class InlineQueryResultVoice(InlineQueryResult):
@ -62,15 +65,15 @@ class InlineQueryResultVoice(InlineQueryResult):
"""
def __init__(self,
id,
voice_url,
title,
voice_duration=None,
caption=None,
reply_markup=None,
input_message_content=None,
parse_mode=DEFAULT_NONE,
**kwargs):
id: str,
voice_url: str,
title: str,
voice_duration: int = None,
caption: str = None,
reply_markup: 'ReplyMarkup' = None,
input_message_content: 'InputMessageContent' = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
**kwargs: Any):
# Required
super().__init__('voice', id)

View file

@ -19,6 +19,7 @@
"""This module contains the classes that represent Telegram InputContactMessageContent."""
from telegram import InputMessageContent
from typing import Any
class InputContactMessageContent(InputMessageContent):
@ -44,7 +45,12 @@ class InputContactMessageContent(InputMessageContent):
"""
def __init__(self, phone_number, first_name, last_name=None, vcard=None, **kwargs):
def __init__(self,
phone_number: str,
first_name: str,
last_name: str = None,
vcard: str = None,
**kwargs: Any):
# Required
self.phone_number = phone_number
self.first_name = first_name

View file

@ -19,6 +19,7 @@
"""This module contains the classes that represent Telegram InputLocationMessageContent."""
from telegram import InputMessageContent
from typing import Any
class InputLocationMessageContent(InputMessageContent):
@ -43,7 +44,7 @@ class InputLocationMessageContent(InputMessageContent):
"""
def __init__(self, latitude, longitude, live_period=None, **kwargs):
def __init__(self, latitude: float, longitude: float, live_period: int = None, **kwargs: Any):
# Required
self.latitude = latitude
self.longitude = longitude

View file

@ -30,9 +30,9 @@ class InputMessageContent(TelegramObject):
"""
@property
def _has_parse_mode(self):
def _has_parse_mode(self) -> bool:
return hasattr(self, 'parse_mode')
@property
def _has_disable_web_page_preview(self):
def _has_disable_web_page_preview(self) -> bool:
return hasattr(self, 'disable_web_page_preview')

View file

@ -19,7 +19,8 @@
"""This module contains the classes that represent Telegram InputTextMessageContent."""
from telegram import InputMessageContent
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.helpers import DEFAULT_NONE, DefaultValue
from typing import Any, Union
class InputTextMessageContent(InputMessageContent):
@ -51,10 +52,10 @@ class InputTextMessageContent(InputMessageContent):
"""
def __init__(self,
message_text,
parse_mode=DEFAULT_NONE,
disable_web_page_preview=DEFAULT_NONE,
**kwargs):
message_text: str,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
disable_web_page_preview: Union[bool, DefaultValue] = DEFAULT_NONE,
**kwargs: Any):
# Required
self.message_text = message_text
# Optionals

View file

@ -19,6 +19,7 @@
"""This module contains the classes that represent Telegram InputVenueMessageContent."""
from telegram import InputMessageContent
from typing import Any
class InputVenueMessageContent(InputMessageContent):
@ -51,8 +52,14 @@ class InputVenueMessageContent(InputMessageContent):
"""
def __init__(self, latitude, longitude, title, address, foursquare_id=None,
foursquare_type=None, **kwargs):
def __init__(self,
latitude: float,
longitude: float,
title: str,
address: str,
foursquare_id: str = None,
foursquare_type: str = None,
**kwargs: Any):
# Required
self.latitude = latitude
self.longitude = longitude

View file

@ -19,6 +19,7 @@
"""This module contains an object that represents a Telegram KeyboardButton."""
from telegram import TelegramObject
from typing import Any
class KeyboardButton(TelegramObject):
@ -59,8 +60,12 @@ class KeyboardButton(TelegramObject):
"""
def __init__(self, text, request_contact=None, request_location=None, request_poll=None,
**kwargs):
def __init__(self,
text: str,
request_contact: bool = None,
request_location: bool = None,
request_poll: bool = None,
**kwargs: Any):
# Required
self.text = text
# Optionals

View file

@ -19,6 +19,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a type of a Telegram Poll."""
from telegram import TelegramObject
from typing import Any
class KeyboardButtonPollType(TelegramObject):
@ -34,7 +35,7 @@ class KeyboardButtonPollType(TelegramObject):
passed, only regular polls will be allowed. Otherwise, the user will be allowed to
create a poll of any type.
"""
def __init__(self, type=None):
def __init__(self, type: str = None, **kwargs: Any):
self.type = type
self._id_attrs = (self.type,)

View file

@ -19,6 +19,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram LoginUrl."""
from telegram import TelegramObject
from typing import Any
class LoginUrl(TelegramObject):
@ -66,7 +67,12 @@ class LoginUrl(TelegramObject):
`Checking authorization <https://core.telegram.org/widgets/login#checking-authorization>`_
"""
def __init__(self, url, forward_text=None, bot_username=None, request_write_access=None):
def __init__(self,
url: str,
forward_text: bool = None,
bot_username: str = None,
request_write_access: bool = None,
**kwargs: Any):
# Required
self.url = url
# Optional

View file

@ -19,6 +19,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Message."""
import sys
import datetime
from html import escape
from telegram import (Animation, Audio, Contact, Document, Chat, Location, PhotoSize, Sticker,
@ -27,6 +28,11 @@ from telegram import (Animation, Audio, Contact, Document, Chat, Location, Photo
from telegram import ParseMode
from telegram.utils.helpers import escape_markdown, to_timestamp, from_timestamp
from telegram.utils.types import JSONDict
from typing import Any, List, Dict, Optional, Union, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot, InputMedia, GameHighScore
_UNDEFINED = object()
@ -203,7 +209,7 @@ class Message(TelegramObject):
programming languages may have difficulty/silent defects in interpreting it. But it is
smaller than 52 bits, so a signed 64 bit integer or double-precision float type are
safe for storing this identifier.
pinned_message (:class:`telegram.message`, optional): Specified message was pinned. Note
pinned_message (:class:`telegram.Message`, optional): Specified message was pinned. Note
that the Message object in this field will not contain further :attr:`reply_to_message`
fields even if it is itself a reply.
invoice (:class:`telegram.Invoice`, optional): Message is an invoice for a payment,
@ -239,57 +245,57 @@ class Message(TelegramObject):
'passport_data'] + ATTACHMENT_TYPES
def __init__(self,
message_id,
from_user,
date,
chat,
forward_from=None,
forward_from_chat=None,
forward_from_message_id=None,
forward_date=None,
reply_to_message=None,
edit_date=None,
text=None,
entities=None,
caption_entities=None,
audio=None,
document=None,
game=None,
photo=None,
sticker=None,
video=None,
voice=None,
video_note=None,
new_chat_members=None,
caption=None,
contact=None,
location=None,
venue=None,
left_chat_member=None,
new_chat_title=None,
new_chat_photo=None,
delete_chat_photo=False,
group_chat_created=False,
supergroup_chat_created=False,
channel_chat_created=False,
migrate_to_chat_id=None,
migrate_from_chat_id=None,
pinned_message=None,
invoice=None,
successful_payment=None,
forward_signature=None,
author_signature=None,
media_group_id=None,
connected_website=None,
animation=None,
passport_data=None,
poll=None,
forward_sender_name=None,
reply_markup=None,
bot=None,
dice=None,
via_bot=None,
**kwargs):
message_id: int,
date: datetime.datetime,
chat: Chat,
from_user: User = None,
forward_from: User = None,
forward_from_chat: Chat = None,
forward_from_message_id: int = None,
forward_date: datetime.datetime = None,
reply_to_message: 'Message' = None,
edit_date: datetime.datetime = None,
text: str = None,
entities: List[MessageEntity] = None,
caption_entities: List[MessageEntity] = None,
audio: Audio = None,
document: Document = None,
game: Game = None,
photo: List[PhotoSize] = None,
sticker: Sticker = None,
video: Video = None,
voice: Voice = None,
video_note: VideoNote = None,
new_chat_members: List[User] = None,
caption: str = None,
contact: Contact = None,
location: Location = None,
venue: Venue = None,
left_chat_member: User = None,
new_chat_title: str = None,
new_chat_photo: List[PhotoSize] = None,
delete_chat_photo: bool = False,
group_chat_created: bool = False,
supergroup_chat_created: bool = False,
channel_chat_created: bool = False,
migrate_to_chat_id: int = None,
migrate_from_chat_id: int = None,
pinned_message: 'Message' = None,
invoice: Invoice = None,
successful_payment: SuccessfulPayment = None,
forward_signature: str = None,
author_signature: str = None,
media_group_id: str = None,
connected_website: str = None,
animation: Animation = None,
passport_data: PassportData = None,
poll: Poll = None,
forward_sender_name: str = None,
reply_markup: InlineKeyboardMarkup = None,
bot: 'Bot' = None,
dice: Dice = None,
via_bot: User = None,
**kwargs: Any):
# Required
self.message_id = int(message_id)
self.from_user = from_user
@ -346,12 +352,12 @@ class Message(TelegramObject):
self._id_attrs = (self.message_id, self.chat)
@property
def chat_id(self):
def chat_id(self) -> int:
""":obj:`int`: Shortcut for :attr:`telegram.Chat.id` for :attr:`chat`."""
return self.chat.id
@property
def link(self):
def link(self) -> Optional[str]:
""":obj:`str`: Convenience property. If the chat of the message is not
a private chat or normal group, returns a t.me link of the message."""
if self.chat.type not in [Chat.PRIVATE, Chat.GROUP]:
@ -364,12 +370,12 @@ class Message(TelegramObject):
return None
@classmethod
def de_json(cls, data, bot):
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> 'Message':
data = cls.parse_data(data)
if not data:
return None
data = super().de_json(data, bot)
data['from_user'] = User.de_json(data.get('from'), bot)
data['date'] = from_timestamp(data['date'])
data['chat'] = Chat.de_json(data.get('chat'), bot)
@ -407,7 +413,9 @@ class Message(TelegramObject):
return cls(bot=bot, **data)
@property
def effective_attachment(self):
def effective_attachment(self) -> Union[Contact, Document, Animation, Game, Invoice, Location,
List[PhotoSize], Sticker, SuccessfulPayment, Venue,
Video, VideoNote, Voice, None]:
"""
:class:`telegram.Audio`
or :class:`telegram.Contact`
@ -427,7 +435,7 @@ class Message(TelegramObject):
"""
if self._effective_attachment is not _UNDEFINED:
return self._effective_attachment
return self._effective_attachment # type: ignore
for i in Message.ATTACHMENT_TYPES:
if getattr(self, i, None):
@ -436,15 +444,15 @@ class Message(TelegramObject):
else:
self._effective_attachment = None
return self._effective_attachment
return self._effective_attachment # type: ignore
def __getitem__(self, item):
def __getitem__(self, item: str) -> Any:
if item in self.__dict__.keys():
return self.__dict__[item]
elif item == 'chat_id':
return self.chat.id
def to_dict(self):
def to_dict(self) -> JSONDict:
data = super().to_dict()
# Required
@ -467,7 +475,7 @@ class Message(TelegramObject):
return data
def _quote(self, kwargs):
def _quote(self, kwargs: JSONDict) -> None:
"""Modify kwargs for replying with or without quoting."""
if 'reply_to_message_id' in kwargs:
if 'quote' in kwargs:
@ -487,7 +495,7 @@ class Message(TelegramObject):
if (default_quote is None and self.chat.type != Chat.PRIVATE) or default_quote:
kwargs['reply_to_message_id'] = self.message_id
def reply_text(self, *args, **kwargs):
def reply_text(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_message(update.message.chat_id, *args, **kwargs)
@ -505,7 +513,7 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_message(self.chat_id, *args, **kwargs)
def reply_markdown(self, *args, **kwargs):
def reply_markdown(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_message(update.message.chat_id, parse_mode=ParseMode.MARKDOWN, *args,
@ -533,7 +541,7 @@ class Message(TelegramObject):
return self.bot.send_message(self.chat_id, *args, **kwargs)
def reply_markdown_v2(self, *args, **kwargs):
def reply_markdown_v2(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_message(update.message.chat_id, parse_mode=ParseMode.MARKDOWN_V2, *args,
@ -557,7 +565,7 @@ class Message(TelegramObject):
return self.bot.send_message(self.chat_id, *args, **kwargs)
def reply_html(self, *args, **kwargs):
def reply_html(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_message(update.message.chat_id, parse_mode=ParseMode.HTML, *args, **kwargs)
@ -580,7 +588,7 @@ class Message(TelegramObject):
return self.bot.send_message(self.chat_id, *args, **kwargs)
def reply_media_group(self, *args, **kwargs):
def reply_media_group(self, *args: Any, **kwargs: Any) -> List[Optional['Message']]:
"""Shortcut for::
bot.send_media_group(update.message.chat_id, *args, **kwargs)
@ -600,7 +608,7 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_media_group(self.chat_id, *args, **kwargs)
def reply_photo(self, *args, **kwargs):
def reply_photo(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_photo(update.message.chat_id, *args, **kwargs)
@ -618,7 +626,7 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_photo(self.chat_id, *args, **kwargs)
def reply_audio(self, *args, **kwargs):
def reply_audio(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_audio(update.message.chat_id, *args, **kwargs)
@ -636,7 +644,7 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_audio(self.chat_id, *args, **kwargs)
def reply_document(self, *args, **kwargs):
def reply_document(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_document(update.message.chat_id, *args, **kwargs)
@ -654,7 +662,7 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_document(self.chat_id, *args, **kwargs)
def reply_animation(self, *args, **kwargs):
def reply_animation(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_animation(update.message.chat_id, *args, **kwargs)
@ -672,7 +680,7 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_animation(self.chat_id, *args, **kwargs)
def reply_sticker(self, *args, **kwargs):
def reply_sticker(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_sticker(update.message.chat_id, *args, **kwargs)
@ -690,7 +698,7 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_sticker(self.chat_id, *args, **kwargs)
def reply_video(self, *args, **kwargs):
def reply_video(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_video(update.message.chat_id, *args, **kwargs)
@ -708,7 +716,7 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_video(self.chat_id, *args, **kwargs)
def reply_video_note(self, *args, **kwargs):
def reply_video_note(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_video_note(update.message.chat_id, *args, **kwargs)
@ -726,7 +734,7 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_video_note(self.chat_id, *args, **kwargs)
def reply_voice(self, *args, **kwargs):
def reply_voice(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_voice(update.message.chat_id, *args, **kwargs)
@ -744,7 +752,7 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_voice(self.chat_id, *args, **kwargs)
def reply_location(self, *args, **kwargs):
def reply_location(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_location(update.message.chat_id, *args, **kwargs)
@ -762,7 +770,7 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_location(self.chat_id, *args, **kwargs)
def reply_venue(self, *args, **kwargs):
def reply_venue(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_venue(update.message.chat_id, *args, **kwargs)
@ -780,7 +788,7 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_venue(self.chat_id, *args, **kwargs)
def reply_contact(self, *args, **kwargs):
def reply_contact(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_contact(update.message.chat_id, *args, **kwargs)
@ -798,7 +806,7 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_contact(self.chat_id, *args, **kwargs)
def reply_poll(self, *args, **kwargs):
def reply_poll(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_poll(update.message.chat_id, *args, **kwargs)
@ -816,7 +824,7 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_poll(self.chat_id, *args, **kwargs)
def reply_dice(self, *args, **kwargs):
def reply_dice(self, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.send_dice(update.message.chat_id, *args, **kwargs)
@ -834,7 +842,7 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_dice(self.chat_id, *args, **kwargs)
def forward(self, chat_id, *args, **kwargs):
def forward(self, chat_id: int, *args: Any, **kwargs: Any) -> 'Message':
"""Shortcut for::
bot.forward_message(chat_id=chat_id,
@ -854,7 +862,7 @@ class Message(TelegramObject):
*args,
**kwargs)
def edit_text(self, *args, **kwargs):
def edit_text(self, *args: Any, **kwargs: Any) -> Union['Message', bool]:
"""Shortcut for::
bot.edit_message_text(chat_id=message.chat_id,
@ -868,13 +876,14 @@ class Message(TelegramObject):
behaviour is undocumented and might be changed by Telegram.
Returns:
:class:`telegram.Message`: On success, instance representing the edited message.
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
edited Message is returned, otherwise ``True`` is returned.
"""
return self.bot.edit_message_text(
chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs)
def edit_caption(self, *args, **kwargs):
def edit_caption(self, *args: Any, **kwargs: Any) -> Union['Message', bool]:
"""Shortcut for::
bot.edit_message_caption(chat_id=message.chat_id,
@ -888,34 +897,35 @@ class Message(TelegramObject):
behaviour is undocumented and might be changed by Telegram.
Returns:
:class:`telegram.Message`: On success, instance representing the edited message.
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
edited Message is returned, otherwise ``True`` is returned.
"""
return self.bot.edit_message_caption(
chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs)
def edit_media(self, media, *args, **kwargs):
def edit_media(self, media: 'InputMedia', *args: Any, **kwargs: Any) -> Union['Message', bool]:
"""Shortcut for::
bot.edit_message_media(chat_id=message.chat_id,
message_id=message.message_id,
*args,
**kwargs)
message_id=message.message_id,
*args,
**kwargs)
Note:
You can only edit messages that the bot sent itself (i.e. of the ``bot.send_*`` family
You can only edit messages that the bot sent itself(i.e. of the ``bot.send_*`` family
of methods) or channel posts, if the bot is an admin in that channel. However, this
behaviour is undocumented and might be changed by Telegram.
Returns:
:class:`telegram.Message`: On success, instance representing the edited
message.
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
edited Message is returned, otherwise ``True`` is returned.
"""
return self.bot.edit_message_media(
chat_id=self.chat_id, message_id=self.message_id, media=media, *args, **kwargs)
def edit_reply_markup(self, *args, **kwargs):
def edit_reply_markup(self, *args: Any, **kwargs: Any) -> Union['Message', bool]:
"""Shortcut for::
bot.edit_message_reply_markup(chat_id=message.chat_id,
@ -929,12 +939,13 @@ class Message(TelegramObject):
behaviour is undocumented and might be changed by Telegram.
Returns:
:class:`telegram.Message`: On success, instance representing the edited message.
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
edited Message is returned, otherwise ``True`` is returned.
"""
return self.bot.edit_message_reply_markup(
chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs)
def edit_live_location(self, *args, **kwargs):
def edit_live_location(self, *args: Any, **kwargs: Any) -> Union['Message', bool]:
"""Shortcut for::
bot.edit_message_live_location(chat_id=message.chat_id,
@ -954,7 +965,7 @@ class Message(TelegramObject):
return self.bot.edit_message_live_location(
chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs)
def stop_live_location(self, *args, **kwargs):
def stop_live_location(self, *args: Any, **kwargs: Any) -> Union['Message', bool]:
"""Shortcut for::
bot.stop_message_live_location(chat_id=message.chat_id,
@ -974,7 +985,7 @@ class Message(TelegramObject):
return self.bot.stop_message_live_location(
chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs)
def set_game_score(self, *args, **kwargs):
def set_game_score(self, *args: Any, **kwargs: Any) -> Union['Message', bool]:
"""Shortcut for::
bot.set_game_score(chat_id=message.chat_id,
@ -994,7 +1005,7 @@ class Message(TelegramObject):
return self.bot.set_game_score(
chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs)
def get_game_high_scores(self, *args, **kwargs):
def get_game_high_scores(self, *args: Any, **kwargs: Any) -> List['GameHighScore']:
"""Shortcut for::
bot.get_game_high_scores(chat_id=message.chat_id,
@ -1008,13 +1019,12 @@ class Message(TelegramObject):
behaviour is undocumented and might be changed by Telegram.
Returns:
:class:`telegram.Message`: On success, if edited message is sent by the bot, the
edited Message is returned, otherwise :obj:`True` is returned.
List[:class:`telegram.GameHighScore`]
"""
return self.bot.get_game_high_scores(
chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs)
def delete(self, *args, **kwargs):
def delete(self, *args: Any, **kwargs: Any) -> bool:
"""Shortcut for::
bot.delete_message(chat_id=message.chat_id,
@ -1029,7 +1039,7 @@ class Message(TelegramObject):
return self.bot.delete_message(
chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs)
def stop_poll(self, *args, **kwargs):
def stop_poll(self, *args: Any, **kwargs: Any) -> Poll:
"""Shortcut for::
bot.stop_poll(chat_id=message.chat_id,
@ -1045,7 +1055,7 @@ class Message(TelegramObject):
return self.bot.stop_poll(
chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs)
def pin(self, *args, **kwargs):
def pin(self, *args: Any, **kwargs: Any) -> bool:
"""Shortcut for::
bot.pin_chat_message(chat_id=message.chat_id,
@ -1060,7 +1070,7 @@ class Message(TelegramObject):
return self.bot.pin_chat_message(
chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs)
def parse_entity(self, entity):
def parse_entity(self, entity: MessageEntity) -> str:
"""Returns the text from a given :class:`telegram.MessageEntity`.
Note:
@ -1075,7 +1085,13 @@ class Message(TelegramObject):
Returns:
:obj:`str`: The text of the given entity.
Raises:
RuntimeError: If the message has no text.
"""
if not self.text:
raise RuntimeError("This Message has no 'text'.")
# Is it a narrow build, if so we don't need to convert
if sys.maxunicode == 0xffff:
return self.text[entity.offset:entity.offset + entity.length]
@ -1085,7 +1101,7 @@ class Message(TelegramObject):
return entity_text.decode('utf-16-le')
def parse_caption_entity(self, entity):
def parse_caption_entity(self, entity: MessageEntity) -> str:
"""Returns the text from a given :class:`telegram.MessageEntity`.
Note:
@ -1100,7 +1116,13 @@ class Message(TelegramObject):
Returns:
:obj:`str`: The text of the given entity.
Raises:
RuntimeError: If the message has no caption.
"""
if not self.caption:
raise RuntimeError("This Message has no 'caption'.")
# Is it a narrow build, if so we don't need to convert
if sys.maxunicode == 0xffff:
return self.caption[entity.offset:entity.offset + entity.length]
@ -1110,7 +1132,7 @@ class Message(TelegramObject):
return entity_text.decode('utf-16-le')
def parse_entities(self, types=None):
def parse_entities(self, types: List[str] = None) -> Dict[MessageEntity, str]:
"""
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
It contains entities from this message filtered by their
@ -1138,10 +1160,10 @@ class Message(TelegramObject):
return {
entity: self.parse_entity(entity)
for entity in self.entities if entity.type in types
for entity in (self.entities or []) if entity.type in types
}
def parse_caption_entities(self, types=None):
def parse_caption_entities(self, types: List[str] = None) -> Dict[MessageEntity, str]:
"""
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
It contains entities from this message's caption filtered by their
@ -1169,16 +1191,19 @@ class Message(TelegramObject):
return {
entity: self.parse_caption_entity(entity)
for entity in self.caption_entities if entity.type in types
for entity in (self.caption_entities or []) if entity.type in types
}
@staticmethod
def _parse_html(message_text, entities, urled=False, offset=0):
def _parse_html(message_text: Optional[str],
entities: Dict[MessageEntity, str],
urled: bool = False,
offset: int = 0) -> Optional[str]:
if message_text is None:
return None
if not sys.maxunicode == 0xffff:
message_text = message_text.encode('utf-16-le')
message_text = message_text.encode('utf-16-le') # type: ignore
html_text = ''
last_offset = 0
@ -1232,15 +1257,16 @@ class Message(TelegramObject):
html_text += escape(message_text[last_offset:entity.offset
- offset]) + insert
else:
html_text += escape(message_text[last_offset * 2:(entity.offset
- offset) * 2]
.decode('utf-16-le')) + insert
html_text += escape(message_text[ # type: ignore
last_offset * 2:(entity.offset - offset) * 2].decode('utf-16-le')
) + insert
else:
if sys.maxunicode == 0xffff:
html_text += message_text[last_offset:entity.offset - offset] + insert
else:
html_text += message_text[last_offset * 2:(entity.offset
- offset) * 2].decode('utf-16-le') + insert
html_text += message_text[ # type: ignore
last_offset * 2:(entity.offset - offset) * 2
].decode('utf-16-le') + insert
last_offset = entity.offset - offset + entity.length
@ -1248,17 +1274,18 @@ class Message(TelegramObject):
if sys.maxunicode == 0xffff:
html_text += escape(message_text[last_offset:])
else:
html_text += escape(message_text[last_offset * 2:].decode('utf-16-le'))
html_text += escape(
message_text[last_offset * 2:].decode('utf-16-le')) # type: ignore
else:
if sys.maxunicode == 0xffff:
html_text += message_text[last_offset:]
else:
html_text += message_text[last_offset * 2:].decode('utf-16-le')
html_text += message_text[last_offset * 2:].decode('utf-16-le') # type: ignore
return html_text
@property
def text_html(self):
def text_html(self) -> str:
"""Creates an HTML-formatted string from the markup entities found in the message.
Use this if you want to retrieve the message text with the entities formatted as HTML in
@ -1271,7 +1298,7 @@ class Message(TelegramObject):
return self._parse_html(self.text, self.parse_entities(), urled=False)
@property
def text_html_urled(self):
def text_html_urled(self) -> str:
"""Creates an HTML-formatted string from the markup entities found in the message.
Use this if you want to retrieve the message text with the entities formatted as HTML.
@ -1284,7 +1311,7 @@ class Message(TelegramObject):
return self._parse_html(self.text, self.parse_entities(), urled=True)
@property
def caption_html(self):
def caption_html(self) -> str:
"""Creates an HTML-formatted string from the markup entities found in the message's
caption.
@ -1298,7 +1325,7 @@ class Message(TelegramObject):
return self._parse_html(self.caption, self.parse_caption_entities(), urled=False)
@property
def caption_html_urled(self):
def caption_html_urled(self) -> str:
"""Creates an HTML-formatted string from the markup entities found in the message's
caption.
@ -1312,14 +1339,18 @@ class Message(TelegramObject):
return self._parse_html(self.caption, self.parse_caption_entities(), urled=True)
@staticmethod
def _parse_markdown(message_text, entities, urled=False, version=1, offset=0):
def _parse_markdown(message_text: Optional[str],
entities: Dict[MessageEntity, str],
urled: bool = False,
version: int = 1,
offset: int = 0) -> Optional[str]:
version = int(version)
if message_text is None:
return None
if not sys.maxunicode == 0xffff:
message_text = message_text.encode('utf-16-le')
message_text = message_text.encode('utf-16-le') # type: ignore
markdown_text = ''
last_offset = 0
@ -1404,16 +1435,18 @@ class Message(TelegramObject):
- offset],
version=version) + insert
else:
markdown_text += escape_markdown(message_text[last_offset * 2:
(entity.offset - offset) * 2]
.decode('utf-16-le'),
version=version) + insert
markdown_text += escape_markdown(
message_text[ # type: ignore
last_offset * 2: (entity.offset - offset) * 2
].decode('utf-16-le'),
version=version) + insert
else:
if sys.maxunicode == 0xffff:
markdown_text += message_text[last_offset:entity.offset - offset] + insert
else:
markdown_text += message_text[last_offset * 2:(entity.offset
- offset) * 2].decode('utf-16-le') + insert
markdown_text += message_text[ # type: ignore
last_offset * 2:(entity.offset - offset) * 2
].decode('utf-16-le') + insert
last_offset = entity.offset - offset + entity.length
@ -1421,18 +1454,19 @@ class Message(TelegramObject):
if sys.maxunicode == 0xffff:
markdown_text += escape_markdown(message_text[last_offset:], version=version)
else:
markdown_text += escape_markdown(message_text[last_offset * 2:]
.decode('utf-16-le'), version=version)
markdown_text += escape_markdown(
message_text[last_offset * 2:] .decode('utf-16-le'), # type: ignore
version=version)
else:
if sys.maxunicode == 0xffff:
markdown_text += message_text[last_offset:]
else:
markdown_text += message_text[last_offset * 2:].decode('utf-16-le')
markdown_text += message_text[last_offset * 2:].decode('utf-16-le') # type: ignore
return markdown_text
@property
def text_markdown(self):
def text_markdown(self) -> str:
"""Creates an Markdown-formatted string from the markup entities found in the message
using :class:`telegram.ParseMode.MARKDOWN`.
@ -1450,7 +1484,7 @@ class Message(TelegramObject):
return self._parse_markdown(self.text, self.parse_entities(), urled=False)
@property
def text_markdown_v2(self):
def text_markdown_v2(self) -> str:
"""Creates an Markdown-formatted string from the markup entities found in the message
using :class:`telegram.ParseMode.MARKDOWN_V2`.
@ -1464,7 +1498,7 @@ class Message(TelegramObject):
return self._parse_markdown(self.text, self.parse_entities(), urled=False, version=2)
@property
def text_markdown_urled(self):
def text_markdown_urled(self) -> str:
"""Creates an Markdown-formatted string from the markup entities found in the message
using :class:`telegram.ParseMode.MARKDOWN`.
@ -1482,7 +1516,7 @@ class Message(TelegramObject):
return self._parse_markdown(self.text, self.parse_entities(), urled=True)
@property
def text_markdown_v2_urled(self):
def text_markdown_v2_urled(self) -> str:
"""Creates an Markdown-formatted string from the markup entities found in the message
using :class:`telegram.ParseMode.MARKDOWN_V2`.
@ -1496,7 +1530,7 @@ class Message(TelegramObject):
return self._parse_markdown(self.text, self.parse_entities(), urled=True, version=2)
@property
def caption_markdown(self):
def caption_markdown(self) -> str:
"""Creates an Markdown-formatted string from the markup entities found in the message's
caption using :class:`telegram.ParseMode.MARKDOWN`.
@ -1514,7 +1548,7 @@ class Message(TelegramObject):
return self._parse_markdown(self.caption, self.parse_caption_entities(), urled=False)
@property
def caption_markdown_v2(self):
def caption_markdown_v2(self) -> str:
"""Creates an Markdown-formatted string from the markup entities found in the message's
caption using :class:`telegram.ParseMode.MARKDOWN_V2`.
@ -1529,7 +1563,7 @@ class Message(TelegramObject):
urled=False, version=2)
@property
def caption_markdown_urled(self):
def caption_markdown_urled(self) -> str:
"""Creates an Markdown-formatted string from the markup entities found in the message's
caption using :class:`telegram.ParseMode.MARKDOWN`.
@ -1547,7 +1581,7 @@ class Message(TelegramObject):
return self._parse_markdown(self.caption, self.parse_caption_entities(), urled=True)
@property
def caption_markdown_v2_urled(self):
def caption_markdown_v2_urled(self) -> str:
"""Creates an Markdown-formatted string from the markup entities found in the message's
caption using :class:`telegram.ParseMode.MARKDOWN_V2`.

View file

@ -19,6 +19,11 @@
"""This module contains an object that represents a Telegram MessageEntity."""
from telegram import User, TelegramObject
from telegram.utils.types import JSONDict
from typing import Any, Optional, List, TYPE_CHECKING
if TYPE_CHECKING:
from telegram import Bot
class MessageEntity(TelegramObject):
@ -53,7 +58,14 @@ class MessageEntity(TelegramObject):
"""
def __init__(self, type, offset, length, url=None, user=None, language=None, **kwargs):
def __init__(self,
type: str,
offset: int,
length: int,
url: str = None,
user: User = None,
language: str = None,
**kwargs: Any):
# Required
self.type = type
self.offset = offset
@ -66,8 +78,8 @@ class MessageEntity(TelegramObject):
self._id_attrs = (self.type, self.offset, self.length)
@classmethod
def de_json(cls, data, bot):
data = super().de_json(data, bot)
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['MessageEntity']:
data = cls.parse_data(data)
if not data:
return None
@ -76,48 +88,37 @@ class MessageEntity(TelegramObject):
return cls(**data)
@classmethod
def de_list(cls, data, bot):
if not data:
return list()
entities = list()
for entity in data:
entities.append(cls.de_json(entity, bot))
return entities
MENTION = 'mention'
MENTION: str = 'mention'
""":obj:`str`: 'mention'"""
HASHTAG = 'hashtag'
HASHTAG: str = 'hashtag'
""":obj:`str`: 'hashtag'"""
CASHTAG = 'cashtag'
CASHTAG: str = 'cashtag'
""":obj:`str`: 'cashtag'"""
PHONE_NUMBER = 'phone_number'
PHONE_NUMBER: str = 'phone_number'
""":obj:`str`: 'phone_number'"""
BOT_COMMAND = 'bot_command'
BOT_COMMAND: str = 'bot_command'
""":obj:`str`: 'bot_command'"""
URL = 'url'
URL: str = 'url'
""":obj:`str`: 'url'"""
EMAIL = 'email'
EMAIL: str = 'email'
""":obj:`str`: 'email'"""
BOLD = 'bold'
BOLD: str = 'bold'
""":obj:`str`: 'bold'"""
ITALIC = 'italic'
ITALIC: str = 'italic'
""":obj:`str`: 'italic'"""
CODE = 'code'
CODE: str = 'code'
""":obj:`str`: 'code'"""
PRE = 'pre'
PRE: str = 'pre'
""":obj:`str`: 'pre'"""
TEXT_LINK = 'text_link'
TEXT_LINK: str = 'text_link'
""":obj:`str`: 'text_link'"""
TEXT_MENTION = 'text_mention'
TEXT_MENTION: str = 'text_mention'
""":obj:`str`: 'text_mention'"""
UNDERLINE = 'underline'
UNDERLINE: str = 'underline'
""":obj:`str`: 'underline'"""
STRIKETHROUGH = 'strikethrough'
STRIKETHROUGH: str = 'strikethrough'
""":obj:`str`: 'strikethrough'"""
ALL_TYPES = [
ALL_TYPES: List[str] = [
MENTION, HASHTAG, CASHTAG, PHONE_NUMBER, BOT_COMMAND, URL,
EMAIL, BOLD, ITALIC, CODE, PRE, TEXT_LINK, TEXT_MENTION, UNDERLINE, STRIKETHROUGH
]

View file

@ -23,14 +23,14 @@
class ParseMode:
"""This object represents a Telegram Message Parse Modes."""
MARKDOWN = 'Markdown'
MARKDOWN: str = 'Markdown'
""":obj:`str`: 'Markdown'
Note:
:attr:`MARKDOWN` is a legacy mode, retained by Telegram for backward compatibility.
You should use :attr:`MARKDOWN_V2` instead.
"""
MARKDOWN_V2 = 'MarkdownV2'
MARKDOWN_V2: str = 'MarkdownV2'
""":obj:`str`: 'MarkdownV2'"""
HTML = 'HTML'
HTML: str = 'HTML'
""":obj:`str`: 'HTML'"""

Some files were not shown because too many files have changed in this diff Show more