diff --git a/.github/workflows/type_completeness.yml b/.github/workflows/type_completeness.yml new file mode 100644 index 000000000..be5f92523 --- /dev/null +++ b/.github/workflows/type_completeness.yml @@ -0,0 +1,61 @@ +name: Check Type Completeness +on: + pull_request: + branches: + - master + push: + branches: + - master + +jobs: + test-type-completeness: + name: test-type-completeness + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: git fetch --depth=1 # https://github.com/actions/checkout/issues/329#issuecomment-674881489 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: 3.9 + cache: 'pip' + cache-dependency-path: '**/requirements*.txt' + - name: Install Pyright + run: | + python -W ignore -m pip install pyright~=1.1.291 + - name: Get Base Completeness + run: | + git checkout ${{ github.base_ref }} + pip install . -U + pyright --verifytypes telegram --ignoreexternal --outputjson > base.json || true + - name: Get PR Completeness + run: | + git checkout ${{ github.head_ref }} + pip install . -U + pyright --verifytypes telegram --ignoreexternal --outputjson > pr.json || true + - name: Compare Completeness + uses: jannekem/run-python-script-action@v1 + with: + script: | + import json + import os + + base = float( + json.load(open("base.json", "rb"))["typeCompleteness"]["completenessScore"] + ) + pr = float( + json.load(open("pr.json", "rb"))["typeCompleteness"]["completenessScore"] + ) + if pr < (base - 0.1): + text = f"This PR decreases type completeness by {round(base - pr, 3)} ❌" + set_summary(text) + error(text) + exit(1) + elif pr > (base + 0.1): + text = f"This PR increases type completeness by {round(pr - base, 3)} ✨" + set_summary(text) + print(text) + else: + text = f"This PR does not change type completeness by more than 0.1 ✅" + set_summary(text) + print(text) diff --git a/docs/source/conf.py b/docs/source/conf.py index 82150ea94..995900bb8 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -565,6 +565,11 @@ def autodoc_process_bases(app, name, obj, option, bases: list): # let's use a string representation of the object base = str(base) + # Special case for abstract context managers which are wrongly resoled for some reason + if base.startswith("typing.AbstractAsyncContextManager"): + bases[idx] = ":class:`contextlib.AbstractAsyncContextManager`" + continue + # Special case because base classes are in std lib: if "StringEnum" in base == "": bases[idx] = ":class:`enum.Enum`" diff --git a/docs/source/telegram.ext.filters.rst b/docs/source/telegram.ext.filters.rst index d332480cd..d32a0015d 100644 --- a/docs/source/telegram.ext.filters.rst +++ b/docs/source/telegram.ext.filters.rst @@ -5,7 +5,7 @@ telegram.ext.filters Module The classes in `filters.py` are sorted alphabetically such that :bysource: still is readable .. automodule:: telegram.ext.filters - :inherited-members: + :inherited-members: BaseFilter, MessageFilter, UpdateFilter :members: :show-inheritance: - :member-order: bysource + :member-order: bysource \ No newline at end of file diff --git a/telegram/__init__.py b/telegram/__init__.py index 38c3aecf4..ea4c7e929 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -343,7 +343,7 @@ from ._writeaccessallowed import WriteAccessAllowed #: :obj:`str`: The version of the `python-telegram-bot` library as string. #: To get detailed information about the version number, please use :data:`__version_info__` #: instead. -__version__ = _version.__version__ +__version__: str = _version.__version__ #: :class:`typing.NamedTuple`: A tuple containing the five components of the version number: #: `major`, `minor`, `micro`, `releaselevel`, and `serial`. #: All values except `releaselevel` are integers. @@ -352,13 +352,13 @@ __version__ = _version.__version__ #: ``__version_info__.major`` and so on. #: #: .. versionadded:: 20.0 -__version_info__ = _version.__version_info__ +__version_info__: _version.Version = _version.__version_info__ #: :obj:`str`: Shortcut for :const:`telegram.constants.BOT_API_VERSION`. #: #: .. versionchanged:: 20.0 #: This constant was previously named ``bot_api_version``. -__bot_api_version__ = _version.__bot_api_version__ +__bot_api_version__: str = _version.__bot_api_version__ #: :class:`typing.NamedTuple`: Shortcut for :const:`telegram.constants.BOT_API_VERSION_INFO`. #: #: .. versionadded:: 20.0 -__bot_api_version_info__ = _version.__bot_api_version_info__ +__bot_api_version_info__: constants._BotAPIVersion = _version.__bot_api_version_info__ diff --git a/telegram/__main__.py b/telegram/__main__.py index e0bf54284..bff34c90b 100644 --- a/telegram/__main__.py +++ b/telegram/__main__.py @@ -36,6 +36,7 @@ def _git_revision() -> Optional[str]: def print_ver_info() -> None: # skipcq: PY-D0003 + """Prints version information for python-telegram-bot, the Bot API and Python.""" git_revision = _git_revision() print(f"python-telegram-bot {telegram_ver}" + (f" ({git_revision})" if git_revision else "")) print(f"Bot API {BOT_API_VERSION}") @@ -44,6 +45,7 @@ def print_ver_info() -> None: # skipcq: PY-D0003 def main() -> None: # skipcq: PY-D0003 + """Prints version information for python-telegram-bot, the Bot API and Python.""" print_ver_info() diff --git a/telegram/_bot.py b/telegram/_bot.py index cc61b39d1..9400dd175 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -23,12 +23,12 @@ import copy import functools import logging import pickle -from contextlib import AbstractAsyncContextManager from datetime import datetime from types import TracebackType from typing import ( TYPE_CHECKING, Any, + AsyncContextManager, Callable, Dict, List, @@ -114,7 +114,7 @@ if TYPE_CHECKING: BT = TypeVar("BT", bound="Bot") -class Bot(TelegramObject, AbstractAsyncContextManager): +class Bot(TelegramObject, AsyncContextManager["Bot"]): """This object represents a Telegram Bot. Instances of this class can be used as asyncio context managers, where @@ -229,13 +229,13 @@ class Bot(TelegramObject, AbstractAsyncContextManager): super().__init__(api_kwargs=None) if not token: raise InvalidToken("You must pass the token you received from https://t.me/Botfather!") - self._token = token + self._token: str = token - self._base_url = base_url + self._token - self._base_file_url = base_file_url + self._token - self._local_mode = local_mode + self._base_url: str = base_url + self._token + self._base_file_url: str = base_file_url + self._token + self._local_mode: bool = local_mode self._bot_user: Optional[User] = None - self._private_key = None + self._private_key: Optional[bytes] = None self._logger = logging.getLogger(__name__) self._initialized = False @@ -312,7 +312,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager): """ raise pickle.PicklingError("Bot objects cannot be pickled!") - def __deepcopy__(self, memodict: dict) -> NoReturn: + def __deepcopy__(self, memodict: Dict[int, object]) -> NoReturn: """Customizes how :func:`copy.deepcopy` processes objects of this type. Bots can not be deepcopied and this method will always raise an exception. diff --git a/telegram/_botcommand.py b/telegram/_botcommand.py index a94f0d1a2..941c19ca8 100644 --- a/telegram/_botcommand.py +++ b/telegram/_botcommand.py @@ -54,8 +54,8 @@ class BotCommand(TelegramObject): def __init__(self, command: str, description: str, *, api_kwargs: JSONDict = None): super().__init__(api_kwargs=api_kwargs) - self.command = command - self.description = description + self.command: str = command + self.description: str = description self._id_attrs = (self.command, self.description) diff --git a/telegram/_botcommandscope.py b/telegram/_botcommandscope.py index ff4f64b0d..7bd0db4b3 100644 --- a/telegram/_botcommandscope.py +++ b/telegram/_botcommandscope.py @@ -77,7 +77,7 @@ class BotCommandScope(TelegramObject): def __init__(self, type: str, *, api_kwargs: JSONDict = None): super().__init__(api_kwargs=api_kwargs) - self.type = type + self.type: str = type self._id_attrs = (self.type,) self._freeze() @@ -200,7 +200,7 @@ class BotCommandScopeChat(BotCommandScope): def __init__(self, chat_id: Union[str, int], *, api_kwargs: JSONDict = None): super().__init__(type=BotCommandScope.CHAT, api_kwargs=api_kwargs) with self._unfrozen(): - self.chat_id = ( + self.chat_id: Union[str, int] = ( chat_id if isinstance(chat_id, str) and chat_id.startswith("@") else int(chat_id) ) self._id_attrs = (self.type, self.chat_id) @@ -227,7 +227,7 @@ class BotCommandScopeChatAdministrators(BotCommandScope): def __init__(self, chat_id: Union[str, int], *, api_kwargs: JSONDict = None): super().__init__(type=BotCommandScope.CHAT_ADMINISTRATORS, api_kwargs=api_kwargs) with self._unfrozen(): - self.chat_id = ( + self.chat_id: Union[str, int] = ( chat_id if isinstance(chat_id, str) and chat_id.startswith("@") else int(chat_id) ) self._id_attrs = (self.type, self.chat_id) @@ -257,8 +257,8 @@ class BotCommandScopeChatMember(BotCommandScope): def __init__(self, chat_id: Union[str, int], user_id: int, *, api_kwargs: JSONDict = None): super().__init__(type=BotCommandScope.CHAT_MEMBER, api_kwargs=api_kwargs) with self._unfrozen(): - self.chat_id = ( + self.chat_id: Union[str, int] = ( chat_id if isinstance(chat_id, str) and chat_id.startswith("@") else int(chat_id) ) - self.user_id = user_id + self.user_id: int = user_id self._id_attrs = (self.type, self.chat_id, self.user_id) diff --git a/telegram/_callbackquery.py b/telegram/_callbackquery.py index fa784df3c..e640743a8 100644 --- a/telegram/_callbackquery.py +++ b/telegram/_callbackquery.py @@ -127,14 +127,14 @@ class CallbackQuery(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.id = id # pylint: disable=invalid-name - self.from_user = from_user - self.chat_instance = chat_instance + self.id: str = id # pylint: disable=invalid-name + self.from_user: User = from_user + self.chat_instance: str = chat_instance # Optionals - self.message = message - self.data = data - self.inline_message_id = inline_message_id - self.game_short_name = game_short_name + self.message: Optional[Message] = message + self.data: Optional[str] = data + self.inline_message_id: Optional[str] = inline_message_id + self.game_short_name: Optional[str] = game_short_name self._id_attrs = (self.id,) diff --git a/telegram/_chat.py b/telegram/_chat.py index f562e9ffb..d517037bd 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -352,37 +352,39 @@ class Chat(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.id = id # pylint: disable=invalid-name - self.type = enum.get_member(constants.ChatType, type, type) + self.id: int = id # pylint: disable=invalid-name + self.type: str = enum.get_member(constants.ChatType, type, type) # Optionals - self.title = title - self.username = username - self.first_name = first_name - self.last_name = last_name - self.photo = photo - self.bio = bio - self.has_private_forwards = has_private_forwards - self.description = description - self.invite_link = invite_link - self.pinned_message = pinned_message - self.permissions = permissions - self.slow_mode_delay = slow_mode_delay - self.message_auto_delete_time = ( + self.title: Optional[str] = title + self.username: Optional[str] = username + self.first_name: Optional[str] = first_name + self.last_name: Optional[str] = last_name + self.photo: Optional[ChatPhoto] = photo + self.bio: Optional[str] = bio + self.has_private_forwards: Optional[bool] = has_private_forwards + self.description: Optional[str] = description + self.invite_link: Optional[str] = invite_link + self.pinned_message: Optional[Message] = pinned_message + self.permissions: Optional[ChatPermissions] = permissions + self.slow_mode_delay: Optional[int] = slow_mode_delay + self.message_auto_delete_time: Optional[int] = ( int(message_auto_delete_time) if message_auto_delete_time is not None else None ) - self.has_protected_content = has_protected_content - self.sticker_set_name = sticker_set_name - self.can_set_sticker_set = can_set_sticker_set - self.linked_chat_id = linked_chat_id - self.location = location - self.join_to_send_messages = join_to_send_messages - self.join_by_request = join_by_request - self.has_restricted_voice_and_video_messages = has_restricted_voice_and_video_messages - self.is_forum = is_forum - self.active_usernames = parse_sequence_arg(active_usernames) - self.emoji_status_custom_emoji_id = emoji_status_custom_emoji_id - self.has_aggressive_anti_spam_enabled = has_aggressive_anti_spam_enabled - self.has_hidden_members = has_hidden_members + self.has_protected_content: Optional[bool] = has_protected_content + self.sticker_set_name: Optional[str] = sticker_set_name + self.can_set_sticker_set: Optional[bool] = can_set_sticker_set + self.linked_chat_id: Optional[int] = linked_chat_id + self.location: Optional[ChatLocation] = location + self.join_to_send_messages: Optional[bool] = join_to_send_messages + self.join_by_request: Optional[bool] = join_by_request + self.has_restricted_voice_and_video_messages: Optional[ + bool + ] = has_restricted_voice_and_video_messages + self.is_forum: Optional[bool] = is_forum + self.active_usernames: Tuple[str, ...] = parse_sequence_arg(active_usernames) + self.emoji_status_custom_emoji_id: Optional[str] = emoji_status_custom_emoji_id + self.has_aggressive_anti_spam_enabled: Optional[bool] = has_aggressive_anti_spam_enabled + self.has_hidden_members: Optional[bool] = has_hidden_members self._id_attrs = (self.id,) diff --git a/telegram/_chatadministratorrights.py b/telegram/_chatadministratorrights.py index fe68c5280..c551a6d0b 100644 --- a/telegram/_chatadministratorrights.py +++ b/telegram/_chatadministratorrights.py @@ -17,6 +17,7 @@ # 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 class which represents a Telegram ChatAdministratorRights.""" +from typing import Optional from telegram._telegramobject import TelegramObject from telegram._utils.types import JSONDict @@ -138,19 +139,19 @@ class ChatAdministratorRights(TelegramObject): ) -> None: super().__init__(api_kwargs=api_kwargs) # Required - self.is_anonymous = is_anonymous - self.can_manage_chat = can_manage_chat - self.can_delete_messages = can_delete_messages - self.can_manage_video_chats = can_manage_video_chats - self.can_restrict_members = can_restrict_members - self.can_promote_members = can_promote_members - self.can_change_info = can_change_info - self.can_invite_users = can_invite_users + self.is_anonymous: bool = is_anonymous + self.can_manage_chat: bool = can_manage_chat + self.can_delete_messages: bool = can_delete_messages + self.can_manage_video_chats: bool = can_manage_video_chats + self.can_restrict_members: bool = can_restrict_members + self.can_promote_members: bool = can_promote_members + self.can_change_info: bool = can_change_info + self.can_invite_users: bool = can_invite_users # Optionals - self.can_post_messages = can_post_messages - self.can_edit_messages = can_edit_messages - self.can_pin_messages = can_pin_messages - self.can_manage_topics = can_manage_topics + self.can_post_messages: Optional[bool] = can_post_messages + self.can_edit_messages: Optional[bool] = can_edit_messages + self.can_pin_messages: Optional[bool] = can_pin_messages + self.can_manage_topics: Optional[bool] = can_manage_topics self._id_attrs = ( self.is_anonymous, diff --git a/telegram/_chatinvitelink.py b/telegram/_chatinvitelink.py index 94aff6c43..a4611a2c3 100644 --- a/telegram/_chatinvitelink.py +++ b/telegram/_chatinvitelink.py @@ -121,17 +121,17 @@ class ChatInviteLink(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.invite_link = invite_link - self.creator = creator - self.creates_join_request = creates_join_request - self.is_primary = is_primary - self.is_revoked = is_revoked + self.invite_link: str = invite_link + self.creator: User = creator + self.creates_join_request: bool = creates_join_request + self.is_primary: bool = is_primary + self.is_revoked: bool = is_revoked # Optionals - self.expire_date = expire_date - self.member_limit = member_limit - self.name = name - self.pending_join_request_count = ( + self.expire_date: Optional[datetime.datetime] = expire_date + self.member_limit: Optional[int] = member_limit + self.name: Optional[str] = name + self.pending_join_request_count: Optional[int] = ( int(pending_join_request_count) if pending_join_request_count is not None else None ) self._id_attrs = ( diff --git a/telegram/_chatjoinrequest.py b/telegram/_chatjoinrequest.py index 75f9f094e..3f4016542 100644 --- a/telegram/_chatjoinrequest.py +++ b/telegram/_chatjoinrequest.py @@ -78,13 +78,13 @@ class ChatJoinRequest(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.chat = chat - self.from_user = from_user - self.date = date + self.chat: Chat = chat + self.from_user: User = from_user + self.date: datetime.datetime = date # Optionals - self.bio = bio - self.invite_link = invite_link + self.bio: Optional[str] = bio + self.invite_link: Optional[ChatInviteLink] = invite_link self._id_attrs = (self.chat, self.from_user, self.date) diff --git a/telegram/_chatlocation.py b/telegram/_chatlocation.py index e319a859b..7bc878766 100644 --- a/telegram/_chatlocation.py +++ b/telegram/_chatlocation.py @@ -60,8 +60,8 @@ class ChatLocation(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.location = location - self.address = address + self.location: Location = location + self.address: str = address self._id_attrs = (self.location,) diff --git a/telegram/_chatmember.py b/telegram/_chatmember.py index a5f9dcf0c..d9c7d1124 100644 --- a/telegram/_chatmember.py +++ b/telegram/_chatmember.py @@ -96,8 +96,8 @@ class ChatMember(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required by all subclasses - self.user = user - self.status = status + self.user: User = user + self.status: str = status self._id_attrs = (self.user, self.status) @@ -165,8 +165,8 @@ class ChatMemberOwner(ChatMember): ): super().__init__(status=ChatMember.OWNER, user=user, api_kwargs=api_kwargs) with self._unfrozen(): - self.is_anonymous = is_anonymous - self.custom_title = custom_title + self.is_anonymous: bool = is_anonymous + self.custom_title: Optional[str] = custom_title class ChatMemberAdministrator(ChatMember): @@ -302,20 +302,20 @@ class ChatMemberAdministrator(ChatMember): ): super().__init__(status=ChatMember.ADMINISTRATOR, user=user, api_kwargs=api_kwargs) with self._unfrozen(): - self.can_be_edited = can_be_edited - self.is_anonymous = is_anonymous - self.can_manage_chat = can_manage_chat - self.can_delete_messages = can_delete_messages - self.can_manage_video_chats = can_manage_video_chats - self.can_restrict_members = can_restrict_members - self.can_promote_members = can_promote_members - self.can_change_info = can_change_info - self.can_invite_users = can_invite_users - self.can_post_messages = can_post_messages - self.can_edit_messages = can_edit_messages - self.can_pin_messages = can_pin_messages - self.can_manage_topics = can_manage_topics - self.custom_title = custom_title + self.can_be_edited: bool = can_be_edited + self.is_anonymous: bool = is_anonymous + self.can_manage_chat: bool = can_manage_chat + self.can_delete_messages: bool = can_delete_messages + self.can_manage_video_chats: bool = can_manage_video_chats + self.can_restrict_members: bool = can_restrict_members + self.can_promote_members: bool = can_promote_members + self.can_change_info: bool = can_change_info + self.can_invite_users: bool = can_invite_users + self.can_post_messages: Optional[bool] = can_post_messages + self.can_edit_messages: Optional[bool] = can_edit_messages + self.can_pin_messages: Optional[bool] = can_pin_messages + self.can_manage_topics: Optional[bool] = can_manage_topics + self.custom_title: Optional[str] = custom_title class ChatMemberMember(ChatMember): @@ -448,17 +448,17 @@ class ChatMemberRestricted(ChatMember): ): super().__init__(status=ChatMember.RESTRICTED, user=user, api_kwargs=api_kwargs) with self._unfrozen(): - self.is_member = is_member - self.can_change_info = can_change_info - self.can_invite_users = can_invite_users - self.can_pin_messages = can_pin_messages - self.can_send_messages = can_send_messages - self.can_send_media_messages = can_send_media_messages - self.can_send_polls = can_send_polls - self.can_send_other_messages = can_send_other_messages - self.can_add_web_page_previews = can_add_web_page_previews - self.can_manage_topics = can_manage_topics - self.until_date = until_date + self.is_member: bool = is_member + self.can_change_info: bool = can_change_info + self.can_invite_users: bool = can_invite_users + self.can_pin_messages: bool = can_pin_messages + self.can_send_messages: bool = can_send_messages + self.can_send_media_messages: bool = can_send_media_messages + self.can_send_polls: bool = can_send_polls + self.can_send_other_messages: bool = can_send_other_messages + self.can_add_web_page_previews: bool = can_add_web_page_previews + self.can_manage_topics: bool = can_manage_topics + self.until_date: datetime.datetime = until_date class ChatMemberLeft(ChatMember): @@ -521,4 +521,4 @@ class ChatMemberBanned(ChatMember): ): super().__init__(status=ChatMember.BANNED, user=user, api_kwargs=api_kwargs) with self._unfrozen(): - self.until_date = until_date + self.until_date: datetime.datetime = until_date diff --git a/telegram/_chatmemberupdated.py b/telegram/_chatmemberupdated.py index fc6f80e26..659a09f42 100644 --- a/telegram/_chatmemberupdated.py +++ b/telegram/_chatmemberupdated.py @@ -91,14 +91,14 @@ class ChatMemberUpdated(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.chat = chat - self.from_user = from_user - self.date = date - self.old_chat_member = old_chat_member - self.new_chat_member = new_chat_member + self.chat: Chat = chat + self.from_user: User = from_user + self.date: datetime.datetime = date + self.old_chat_member: ChatMember = old_chat_member + self.new_chat_member: ChatMember = new_chat_member # Optionals - self.invite_link = invite_link + self.invite_link: Optional[ChatInviteLink] = invite_link self._id_attrs = ( self.chat, diff --git a/telegram/_chatpermissions.py b/telegram/_chatpermissions.py index 8f9b7d366..9cdd71b29 100644 --- a/telegram/_chatpermissions.py +++ b/telegram/_chatpermissions.py @@ -17,6 +17,7 @@ # 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 ChatPermission.""" +from typing import Optional from telegram._telegramobject import TelegramObject from telegram._utils.types import JSONDict @@ -120,15 +121,15 @@ class ChatPermissions(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.can_send_messages = can_send_messages - self.can_send_media_messages = can_send_media_messages - self.can_send_polls = can_send_polls - self.can_send_other_messages = can_send_other_messages - self.can_add_web_page_previews = can_add_web_page_previews - self.can_change_info = can_change_info - self.can_invite_users = can_invite_users - self.can_pin_messages = can_pin_messages - self.can_manage_topics = can_manage_topics + self.can_send_messages: Optional[bool] = can_send_messages + self.can_send_media_messages: Optional[bool] = can_send_media_messages + self.can_send_polls: Optional[bool] = can_send_polls + self.can_send_other_messages: Optional[bool] = can_send_other_messages + self.can_add_web_page_previews: Optional[bool] = can_add_web_page_previews + self.can_change_info: Optional[bool] = can_change_info + self.can_invite_users: Optional[bool] = can_invite_users + self.can_pin_messages: Optional[bool] = can_pin_messages + self.can_manage_topics: Optional[bool] = can_manage_topics self._id_attrs = ( self.can_send_messages, diff --git a/telegram/_choseninlineresult.py b/telegram/_choseninlineresult.py index 89959d4db..c9c9b01e8 100644 --- a/telegram/_choseninlineresult.py +++ b/telegram/_choseninlineresult.py @@ -80,12 +80,12 @@ class ChosenInlineResult(TelegramObject): super().__init__(api_kwargs=api_kwargs) # Required - self.result_id = result_id - self.from_user = from_user - self.query = query + self.result_id: str = result_id + self.from_user: User = from_user + self.query: str = query # Optionals - self.location = location - self.inline_message_id = inline_message_id + self.location: Optional[Location] = location + self.inline_message_id: Optional[str] = inline_message_id self._id_attrs = (self.result_id,) diff --git a/telegram/_dice.py b/telegram/_dice.py index e463bc984..48964527a 100644 --- a/telegram/_dice.py +++ b/telegram/_dice.py @@ -91,8 +91,8 @@ class Dice(TelegramObject): def __init__(self, value: int, emoji: str, *, api_kwargs: JSONDict = None): super().__init__(api_kwargs=api_kwargs) - self.value = value - self.emoji = emoji + self.value: int = value + self.emoji: str = emoji self._id_attrs = (self.value, self.emoji) diff --git a/telegram/_files/_basemedium.py b/telegram/_files/_basemedium.py index 3e8de7792..403c4842f 100644 --- a/telegram/_files/_basemedium.py +++ b/telegram/_files/_basemedium.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. """Common base class for media objects""" -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from telegram._telegramobject import TelegramObject from telegram._utils.defaultvalue import DEFAULT_NONE @@ -64,9 +64,9 @@ class _BaseMedium(TelegramObject): # Required self.file_id: str = str(file_id) - self.file_unique_id = str(file_unique_id) + self.file_unique_id: str = str(file_unique_id) # Optionals - self.file_size = file_size + self.file_size: Optional[int] = file_size self._id_attrs = (self.file_unique_id,) diff --git a/telegram/_files/_basethumbedmedium.py b/telegram/_files/_basethumbedmedium.py index fd776c32e..2fd0a5ad1 100644 --- a/telegram/_files/_basethumbedmedium.py +++ b/telegram/_files/_basethumbedmedium.py @@ -74,7 +74,7 @@ class _BaseThumbedMedium(_BaseMedium): file_size=file_size, api_kwargs=api_kwargs, ) - self.thumb = thumb + self.thumb: Optional[PhotoSize] = thumb @classmethod def de_json( diff --git a/telegram/_files/animation.py b/telegram/_files/animation.py index 6d8043b50..fc96bbbe4 100644 --- a/telegram/_files/animation.py +++ b/telegram/_files/animation.py @@ -17,6 +17,7 @@ # 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 Animation.""" +from typing import Optional from telegram._files._basethumbedmedium import _BaseThumbedMedium from telegram._files.photosize import PhotoSize @@ -85,9 +86,9 @@ class Animation(_BaseThumbedMedium): ) with self._unfrozen(): # Required - self.width = width - self.height = height - self.duration = duration + self.width: int = width + self.height: int = height + self.duration: int = duration # Optional - self.mime_type = mime_type - self.file_name = file_name + self.mime_type: Optional[str] = mime_type + self.file_name: Optional[str] = file_name diff --git a/telegram/_files/audio.py b/telegram/_files/audio.py index 8ac573a81..e5357d90a 100644 --- a/telegram/_files/audio.py +++ b/telegram/_files/audio.py @@ -17,6 +17,7 @@ # 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 Audio.""" +from typing import Optional from telegram._files._basethumbedmedium import _BaseThumbedMedium from telegram._files.photosize import PhotoSize @@ -88,9 +89,9 @@ class Audio(_BaseThumbedMedium): ) with self._unfrozen(): # Required - self.duration = duration + self.duration: int = duration # Optional - self.performer = performer - self.title = title - self.mime_type = mime_type - self.file_name = file_name + self.performer: Optional[str] = performer + self.title: Optional[str] = title + self.mime_type: Optional[str] = mime_type + self.file_name: Optional[str] = file_name diff --git a/telegram/_files/chatphoto.py b/telegram/_files/chatphoto.py index 7179508f1..5ab606380 100644 --- a/telegram/_files/chatphoto.py +++ b/telegram/_files/chatphoto.py @@ -90,10 +90,10 @@ class ChatPhoto(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.small_file_id = small_file_id - self.small_file_unique_id = small_file_unique_id - self.big_file_id = big_file_id - self.big_file_unique_id = big_file_unique_id + self.small_file_id: str = small_file_id + self.small_file_unique_id: str = small_file_unique_id + self.big_file_id: str = big_file_id + self.big_file_unique_id: str = big_file_unique_id self._id_attrs = ( self.small_file_unique_id, diff --git a/telegram/_files/contact.py b/telegram/_files/contact.py index f8fc6587d..aff65deb5 100644 --- a/telegram/_files/contact.py +++ b/telegram/_files/contact.py @@ -17,6 +17,7 @@ # 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 Contact.""" +from typing import Optional from telegram._telegramobject import TelegramObject from telegram._utils.types import JSONDict @@ -58,12 +59,12 @@ class Contact(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.phone_number = str(phone_number) - self.first_name = first_name + self.phone_number: str = str(phone_number) + self.first_name: str = first_name # Optionals - self.last_name = last_name - self.user_id = user_id - self.vcard = vcard + self.last_name: Optional[str] = last_name + self.user_id: Optional[int] = user_id + self.vcard: Optional[str] = vcard self._id_attrs = (self.phone_number,) diff --git a/telegram/_files/document.py b/telegram/_files/document.py index 42301ef75..f13c57dc7 100644 --- a/telegram/_files/document.py +++ b/telegram/_files/document.py @@ -17,6 +17,7 @@ # 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 Document.""" +from typing import Optional from telegram._files._basethumbedmedium import _BaseThumbedMedium from telegram._files.photosize import PhotoSize @@ -74,5 +75,5 @@ class Document(_BaseThumbedMedium): ) with self._unfrozen(): # Optional - self.mime_type = mime_type - self.file_name = file_name + self.mime_type: Optional[str] = mime_type + self.file_name: Optional[str] = file_name diff --git a/telegram/_files/file.py b/telegram/_files/file.py index c9412808a..23145b8af 100644 --- a/telegram/_files/file.py +++ b/telegram/_files/file.py @@ -93,11 +93,11 @@ class File(TelegramObject): super().__init__(api_kwargs=api_kwargs) # Required - self.file_id = str(file_id) - self.file_unique_id = str(file_unique_id) + self.file_id: str = str(file_id) + self.file_unique_id: str = str(file_unique_id) # Optionals - self.file_size = file_size - self.file_path = file_path + self.file_size: Optional[int] = file_size + self.file_path: Optional[str] = file_path self._credentials: Optional["FileCredentials"] = None diff --git a/telegram/_files/inputfile.py b/telegram/_files/inputfile.py index 1fe82990a..3a0df5546 100644 --- a/telegram/_files/inputfile.py +++ b/telegram/_files/inputfile.py @@ -71,7 +71,7 @@ class InputFile: self, obj: Union[IO[bytes], bytes, str], filename: str = None, attach: bool = False ): if isinstance(obj, bytes): - self.input_file_content = obj + self.input_file_content: bytes = obj elif isinstance(obj, str): self.input_file_content = obj.encode("utf-8") else: @@ -81,11 +81,13 @@ class InputFile: self.attach_name: Optional[str] = "attached" + uuid4().hex if attach else None if filename: - self.mimetype = mimetypes.guess_type(filename, strict=False)[0] or _DEFAULT_MIME_TYPE + self.mimetype: str = ( + mimetypes.guess_type(filename, strict=False)[0] or _DEFAULT_MIME_TYPE + ) else: self.mimetype = _DEFAULT_MIME_TYPE - self.filename = filename or self.mimetype.replace("/", ".") + self.filename: str = filename or self.mimetype.replace("/", ".") @property def field_tuple(self) -> FieldTuple: diff --git a/telegram/_files/inputmedia.py b/telegram/_files/inputmedia.py index f08f38e64..3c6946158 100644 --- a/telegram/_files/inputmedia.py +++ b/telegram/_files/inputmedia.py @@ -17,7 +17,7 @@ # 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 InputMedia Objects.""" -from typing import Optional, Sequence, Union +from typing import Optional, Sequence, Tuple, Union from telegram._files.animation import Animation from telegram._files.audio import Audio @@ -94,11 +94,11 @@ class InputMedia(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.type = media_type - self.media = media - self.caption = caption - self.caption_entities = parse_sequence_arg(caption_entities) - self.parse_mode = parse_mode + self.type: str = media_type + self.media: Union[str, InputFile, Animation, Audio, Document, PhotoSize, Video] = media + self.caption: Optional[str] = caption + self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) + self.parse_mode: ODVInput[str] = parse_mode self._freeze() @@ -214,11 +214,11 @@ class InputMediaAnimation(InputMedia): api_kwargs=api_kwargs, ) with self._unfrozen(): - self.thumb = self._parse_thumb_input(thumb) - self.width = width - self.height = height - self.duration = duration - self.has_spoiler = has_spoiler + self.thumb: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumb) + self.width: Optional[int] = width + self.height: Optional[int] = height + self.duration: Optional[int] = duration + self.has_spoiler: Optional[bool] = has_spoiler class InputMediaPhoto(InputMedia): @@ -296,7 +296,7 @@ class InputMediaPhoto(InputMedia): ) with self._unfrozen(): - self.has_spoiler = has_spoiler + self.has_spoiler: Optional[bool] = has_spoiler class InputMediaVideo(InputMedia): @@ -411,12 +411,12 @@ class InputMediaVideo(InputMedia): api_kwargs=api_kwargs, ) with self._unfrozen(): - self.width = width - self.height = height - self.duration = duration - self.thumb = self._parse_thumb_input(thumb) - self.supports_streaming = supports_streaming - self.has_spoiler = has_spoiler + self.width: Optional[int] = width + self.height: Optional[int] = height + self.duration: Optional[int] = duration + self.thumb: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumb) + self.supports_streaming: Optional[bool] = supports_streaming + self.has_spoiler: Optional[bool] = has_spoiler class InputMediaAudio(InputMedia): @@ -516,10 +516,10 @@ class InputMediaAudio(InputMedia): api_kwargs=api_kwargs, ) with self._unfrozen(): - self.thumb = self._parse_thumb_input(thumb) - self.duration = duration - self.title = title - self.performer = performer + self.thumb: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumb) + self.duration: Optional[int] = duration + self.title: Optional[str] = title + self.performer: Optional[str] = performer class InputMediaDocument(InputMedia): @@ -603,5 +603,5 @@ class InputMediaDocument(InputMedia): api_kwargs=api_kwargs, ) with self._unfrozen(): - self.thumb = self._parse_thumb_input(thumb) - self.disable_content_type_detection = disable_content_type_detection + self.thumb: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumb) + self.disable_content_type_detection: Optional[bool] = disable_content_type_detection diff --git a/telegram/_files/location.py b/telegram/_files/location.py index 919fc4a6c..e818f1bdb 100644 --- a/telegram/_files/location.py +++ b/telegram/_files/location.py @@ -18,7 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram Location.""" -from typing import ClassVar +from typing import ClassVar, Optional from telegram import constants from telegram._telegramobject import TelegramObject @@ -81,14 +81,14 @@ class Location(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.longitude = longitude - self.latitude = latitude + self.longitude: float = longitude + self.latitude: float = latitude # Optionals - self.horizontal_accuracy = horizontal_accuracy - self.live_period = live_period - self.heading = heading - self.proximity_alert_radius = ( + self.horizontal_accuracy: Optional[float] = horizontal_accuracy + self.live_period: Optional[int] = live_period + self.heading: Optional[int] = heading + self.proximity_alert_radius: Optional[int] = ( int(proximity_alert_radius) if proximity_alert_radius else None ) diff --git a/telegram/_files/photosize.py b/telegram/_files/photosize.py index 87de4c1a2..c31897c02 100644 --- a/telegram/_files/photosize.py +++ b/telegram/_files/photosize.py @@ -71,5 +71,5 @@ class PhotoSize(_BaseMedium): ) with self._unfrozen(): # Required - self.width = width - self.height = height + self.width: int = width + self.height: int = height diff --git a/telegram/_files/sticker.py b/telegram/_files/sticker.py index 3fdeeeb95..95061d123 100644 --- a/telegram/_files/sticker.py +++ b/telegram/_files/sticker.py @@ -17,7 +17,7 @@ # 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 objects that represent stickers.""" -from typing import TYPE_CHECKING, ClassVar, Optional, Sequence +from typing import TYPE_CHECKING, ClassVar, Optional, Sequence, Tuple from telegram import constants from telegram._files._basethumbedmedium import _BaseThumbedMedium @@ -152,17 +152,17 @@ class Sticker(_BaseThumbedMedium): ) with self._unfrozen(): # Required - self.width = width - self.height = height - self.is_animated = is_animated - self.is_video = is_video - self.type = type + self.width: int = width + self.height: int = height + self.is_animated: bool = is_animated + self.is_video: bool = is_video + self.type: str = type # Optional - self.emoji = emoji - self.set_name = set_name - self.mask_position = mask_position - self.premium_animation = premium_animation - self.custom_emoji_id = custom_emoji_id + self.emoji: Optional[str] = emoji + self.set_name: Optional[str] = set_name + self.mask_position: Optional[MaskPosition] = mask_position + self.premium_animation: Optional[File] = premium_animation + self.custom_emoji_id: Optional[str] = custom_emoji_id REGULAR: ClassVar[str] = constants.StickerType.REGULAR """:const:`telegram.constants.StickerType.REGULAR`""" @@ -265,14 +265,14 @@ class StickerSet(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.name = name - self.title = title - self.is_animated = is_animated - self.is_video = is_video - self.stickers = parse_sequence_arg(stickers) - self.sticker_type = sticker_type + self.name: str = name + self.title: str = title + self.is_animated: bool = is_animated + self.is_video: bool = is_video + self.stickers: Tuple[Sticker, ...] = parse_sequence_arg(stickers) + self.sticker_type: str = sticker_type # Optional - self.thumb = thumb + self.thumb: Optional[PhotoSize] = thumb self._id_attrs = (self.name,) @@ -348,10 +348,10 @@ class MaskPosition(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.point = point - self.x_shift = x_shift - self.y_shift = y_shift - self.scale = scale + self.point: str = point + self.x_shift: float = x_shift + self.y_shift: float = y_shift + self.scale: float = scale self._id_attrs = (self.point, self.x_shift, self.y_shift, self.scale) diff --git a/telegram/_files/venue.py b/telegram/_files/venue.py index ef49a815b..5811d7a73 100644 --- a/telegram/_files/venue.py +++ b/telegram/_files/venue.py @@ -89,14 +89,14 @@ class Venue(TelegramObject): super().__init__(api_kwargs=api_kwargs) # Required - self.location = location - self.title = title - self.address = address + self.location: Location = location + self.title: str = title + self.address: str = address # Optionals - self.foursquare_id = foursquare_id - self.foursquare_type = foursquare_type - self.google_place_id = google_place_id - self.google_place_type = google_place_type + self.foursquare_id: Optional[str] = foursquare_id + self.foursquare_type: Optional[str] = foursquare_type + self.google_place_id: Optional[str] = google_place_id + self.google_place_type: Optional[str] = google_place_type self._id_attrs = (self.location, self.title) diff --git a/telegram/_files/video.py b/telegram/_files/video.py index b22743d96..a62d680f0 100644 --- a/telegram/_files/video.py +++ b/telegram/_files/video.py @@ -17,6 +17,7 @@ # 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 Video.""" +from typing import Optional from telegram._files._basethumbedmedium import _BaseThumbedMedium from telegram._files.photosize import PhotoSize @@ -84,9 +85,9 @@ class Video(_BaseThumbedMedium): ) with self._unfrozen(): # Required - self.width = width - self.height = height - self.duration = duration + self.width: int = width + self.height: int = height + self.duration: int = duration # Optional - self.mime_type = mime_type - self.file_name = file_name + self.mime_type: Optional[str] = mime_type + self.file_name: Optional[str] = file_name diff --git a/telegram/_files/videonote.py b/telegram/_files/videonote.py index 664614ed6..d4b181e25 100644 --- a/telegram/_files/videonote.py +++ b/telegram/_files/videonote.py @@ -77,5 +77,5 @@ class VideoNote(_BaseThumbedMedium): ) with self._unfrozen(): # Required - self.length = length - self.duration = duration + self.length: int = length + self.duration: int = duration diff --git a/telegram/_files/voice.py b/telegram/_files/voice.py index cace16790..2f557d93e 100644 --- a/telegram/_files/voice.py +++ b/telegram/_files/voice.py @@ -17,6 +17,7 @@ # 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 Voice.""" +from typing import Optional from telegram._files._basemedium import _BaseMedium from telegram._utils.types import JSONDict @@ -70,6 +71,6 @@ class Voice(_BaseMedium): ) with self._unfrozen(): # Required - self.duration = duration + self.duration: int = duration # Optional - self.mime_type = mime_type + self.mime_type: Optional[str] = mime_type diff --git a/telegram/_forcereply.py b/telegram/_forcereply.py index ad29f23c9..32acd38e2 100644 --- a/telegram/_forcereply.py +++ b/telegram/_forcereply.py @@ -18,7 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram ForceReply.""" -from typing import ClassVar +from typing import ClassVar, Optional from telegram import constants from telegram._telegramobject import TelegramObject @@ -85,9 +85,9 @@ class ForceReply(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.force_reply = True - self.selective = selective - self.input_field_placeholder = input_field_placeholder + self.force_reply: bool = True + self.selective: Optional[bool] = selective + self.input_field_placeholder: Optional[str] = input_field_placeholder self._id_attrs = (self.selective,) diff --git a/telegram/_forumtopic.py b/telegram/_forumtopic.py index e5897497b..d46c787e9 100644 --- a/telegram/_forumtopic.py +++ b/telegram/_forumtopic.py @@ -17,6 +17,7 @@ # 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 objects related to Telegram forum topics.""" +from typing import Optional from telegram._telegramobject import TelegramObject from telegram._utils.types import JSONDict @@ -59,10 +60,10 @@ class ForumTopic(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.message_thread_id = message_thread_id - self.name = name - self.icon_color = icon_color - self.icon_custom_emoji_id = icon_custom_emoji_id + self.message_thread_id: int = message_thread_id + self.name: str = name + self.icon_color: int = icon_color + self.icon_custom_emoji_id: Optional[str] = icon_custom_emoji_id self._id_attrs = (self.message_thread_id, self.name, self.icon_color) @@ -103,9 +104,9 @@ class ForumTopicCreated(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.name = name - self.icon_color = icon_color - self.icon_custom_emoji_id = icon_custom_emoji_id + self.name: str = name + self.icon_color: int = icon_color + self.icon_custom_emoji_id: Optional[str] = icon_custom_emoji_id self._id_attrs = (self.name, self.icon_color) @@ -174,8 +175,8 @@ class ForumTopicEdited(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.name = name - self.icon_custom_emoji_id = icon_custom_emoji_id + self.name: Optional[str] = name + self.icon_custom_emoji_id: Optional[str] = icon_custom_emoji_id self._id_attrs = (self.name, self.icon_custom_emoji_id) diff --git a/telegram/_games/game.py b/telegram/_games/game.py index d46622385..d5a6b3875 100644 --- a/telegram/_games/game.py +++ b/telegram/_games/game.py @@ -18,7 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram Game.""" import sys -from typing import TYPE_CHECKING, Dict, List, Optional, Sequence +from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Tuple from telegram._files.animation import Animation from telegram._files.photosize import PhotoSize @@ -110,13 +110,13 @@ class Game(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.title = title - self.description = description - self.photo = parse_sequence_arg(photo) + self.title: str = title + self.description: str = description + self.photo: Tuple[PhotoSize, ...] = parse_sequence_arg(photo) # Optionals - self.text = text - self.text_entities = parse_sequence_arg(text_entities) - self.animation = animation + self.text: Optional[str] = text + self.text_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(text_entities) + self.animation: Optional[Animation] = animation self._id_attrs = (self.title, self.description, self.photo) diff --git a/telegram/_games/gamehighscore.py b/telegram/_games/gamehighscore.py index af5d71a74..8f5622190 100644 --- a/telegram/_games/gamehighscore.py +++ b/telegram/_games/gamehighscore.py @@ -50,9 +50,9 @@ class GameHighScore(TelegramObject): def __init__(self, position: int, user: User, score: int, *, api_kwargs: JSONDict = None): super().__init__(api_kwargs=api_kwargs) - self.position = position - self.user = user - self.score = score + self.position: int = position + self.user: User = user + self.score: int = score self._id_attrs = (self.position, self.user, self.score) diff --git a/telegram/_inline/inlinekeyboardbutton.py b/telegram/_inline/inlinekeyboardbutton.py index 07e08c40e..33759e731 100644 --- a/telegram/_inline/inlinekeyboardbutton.py +++ b/telegram/_inline/inlinekeyboardbutton.py @@ -196,17 +196,17 @@ class InlineKeyboardButton(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.text = text + self.text: str = text # Optionals - self.url = url - self.login_url = login_url - self.callback_data = callback_data - self.switch_inline_query = switch_inline_query - self.switch_inline_query_current_chat = switch_inline_query_current_chat - self.callback_game = callback_game - self.pay = pay - self.web_app = web_app + self.url: Optional[str] = url + self.login_url: Optional[LoginUrl] = login_url + self.callback_data: Optional[Union[str, object]] = callback_data + self.switch_inline_query: Optional[str] = switch_inline_query + self.switch_inline_query_current_chat: Optional[str] = switch_inline_query_current_chat + self.callback_game: Optional[CallbackGame] = callback_game + self.pay: Optional[bool] = pay + self.web_app: Optional[WebAppInfo] = web_app self._id_attrs = () self._set_id_attrs() diff --git a/telegram/_inline/inlinekeyboardmarkup.py b/telegram/_inline/inlinekeyboardmarkup.py index db5117aed..e1ac2fb51 100644 --- a/telegram/_inline/inlinekeyboardmarkup.py +++ b/telegram/_inline/inlinekeyboardmarkup.py @@ -17,7 +17,7 @@ # 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 InlineKeyboardMarkup.""" -from typing import TYPE_CHECKING, Optional, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._inline.inlinekeyboardbutton import InlineKeyboardButton from telegram._telegramobject import TelegramObject @@ -72,7 +72,9 @@ class InlineKeyboardMarkup(TelegramObject): "InlineKeyboardButtons" ) # Required - self.inline_keyboard = tuple(tuple(row) for row in inline_keyboard) + self.inline_keyboard: Tuple[Tuple[InlineKeyboardButton, ...], ...] = tuple( + tuple(row) for row in inline_keyboard + ) self._id_attrs = (self.inline_keyboard,) diff --git a/telegram/_inline/inlinequery.py b/telegram/_inline/inlinequery.py index cdfaad685..12fb2b400 100644 --- a/telegram/_inline/inlinequery.py +++ b/telegram/_inline/inlinequery.py @@ -100,14 +100,14 @@ class InlineQuery(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.id = id # pylint: disable=invalid-name - self.from_user = from_user - self.query = query - self.offset = offset + self.id: str = id # pylint: disable=invalid-name + self.from_user: User = from_user + self.query: str = query + self.offset: str = offset # Optional - self.location = location - self.chat_type = chat_type + self.location: Optional[Location] = location + self.chat_type: Optional[str] = chat_type self._id_attrs = (self.id,) diff --git a/telegram/_inline/inlinequeryresult.py b/telegram/_inline/inlinequeryresult.py index 25de51d44..14149682f 100644 --- a/telegram/_inline/inlinequeryresult.py +++ b/telegram/_inline/inlinequeryresult.py @@ -59,8 +59,8 @@ class InlineQueryResult(TelegramObject): super().__init__(api_kwargs=api_kwargs) # Required - self.type = type - self.id = str(id) # pylint: disable=invalid-name + self.type: str = type + self.id: str = str(id) # pylint: disable=invalid-name self._id_attrs = (self.id,) diff --git a/telegram/_inline/inlinequeryresultarticle.py b/telegram/_inline/inlinequeryresultarticle.py index 1398de734..9b803ac09 100644 --- a/telegram/_inline/inlinequeryresultarticle.py +++ b/telegram/_inline/inlinequeryresultarticle.py @@ -18,7 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the classes that represent Telegram InlineQueryResultArticle.""" -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -103,14 +103,14 @@ class InlineQueryResultArticle(InlineQueryResult): # Required super().__init__(InlineQueryResultType.ARTICLE, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.title = title - self.input_message_content = input_message_content + self.title: str = title + self.input_message_content: InputMessageContent = input_message_content # Optional - self.reply_markup = reply_markup - self.url = url - self.hide_url = hide_url - self.description = description - self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.url: Optional[str] = url + self.hide_url: Optional[bool] = hide_url + self.description: Optional[str] = description + self.thumb_url: Optional[str] = thumb_url + self.thumb_width: Optional[int] = thumb_width + self.thumb_height: Optional[int] = thumb_height diff --git a/telegram/_inline/inlinequeryresultaudio.py b/telegram/_inline/inlinequeryresultaudio.py index 7edd8b3fe..5be232084 100644 --- a/telegram/_inline/inlinequeryresultaudio.py +++ b/telegram/_inline/inlinequeryresultaudio.py @@ -17,7 +17,7 @@ # 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 classes that represent Telegram InlineQueryResultAudio.""" -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -117,14 +117,14 @@ class InlineQueryResultAudio(InlineQueryResult): # Required super().__init__(InlineQueryResultType.AUDIO, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.audio_url = audio_url - self.title = title + self.audio_url: str = audio_url + self.title: str = title # Optionals - self.performer = performer - self.audio_duration = audio_duration - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = parse_sequence_arg(caption_entities) - self.reply_markup = reply_markup - self.input_message_content = input_message_content + self.performer: Optional[str] = performer + self.audio_duration: Optional[int] = audio_duration + self.caption: Optional[str] = caption + self.parse_mode: ODVInput[str] = parse_mode + self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content diff --git a/telegram/_inline/inlinequeryresultcachedaudio.py b/telegram/_inline/inlinequeryresultcachedaudio.py index 51878f03a..92a6c94ea 100644 --- a/telegram/_inline/inlinequeryresultcachedaudio.py +++ b/telegram/_inline/inlinequeryresultcachedaudio.py @@ -17,7 +17,7 @@ # 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 classes that represent Telegram InlineQueryResultCachedAudio.""" -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -105,11 +105,11 @@ class InlineQueryResultCachedAudio(InlineQueryResult): # Required super().__init__(InlineQueryResultType.AUDIO, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.audio_file_id = audio_file_id + self.audio_file_id: str = audio_file_id # Optionals - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = parse_sequence_arg(caption_entities) - self.reply_markup = reply_markup - self.input_message_content = input_message_content + self.caption: Optional[str] = caption + self.parse_mode: ODVInput[str] = parse_mode + self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content diff --git a/telegram/_inline/inlinequeryresultcacheddocument.py b/telegram/_inline/inlinequeryresultcacheddocument.py index 374f62045..000196fd7 100644 --- a/telegram/_inline/inlinequeryresultcacheddocument.py +++ b/telegram/_inline/inlinequeryresultcacheddocument.py @@ -17,7 +17,7 @@ # 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 classes that represent Telegram InlineQueryResultCachedDocument.""" -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -113,13 +113,13 @@ class InlineQueryResultCachedDocument(InlineQueryResult): # Required super().__init__(InlineQueryResultType.DOCUMENT, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.title = title - self.document_file_id = document_file_id + self.title: str = title + self.document_file_id: str = document_file_id # Optionals - self.description = description - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = parse_sequence_arg(caption_entities) - self.reply_markup = reply_markup - self.input_message_content = input_message_content + self.description: Optional[str] = description + self.caption: Optional[str] = caption + self.parse_mode: ODVInput[str] = parse_mode + self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content diff --git a/telegram/_inline/inlinequeryresultcachedgif.py b/telegram/_inline/inlinequeryresultcachedgif.py index 172496ce8..a590f1f30 100644 --- a/telegram/_inline/inlinequeryresultcachedgif.py +++ b/telegram/_inline/inlinequeryresultcachedgif.py @@ -17,7 +17,7 @@ # 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 classes that represent Telegram InlineQueryResultCachedGif.""" -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -110,12 +110,12 @@ class InlineQueryResultCachedGif(InlineQueryResult): # Required super().__init__(InlineQueryResultType.GIF, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.gif_file_id = gif_file_id + self.gif_file_id: str = gif_file_id # Optionals - self.title = title - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = parse_sequence_arg(caption_entities) - self.reply_markup = reply_markup - self.input_message_content = input_message_content + self.title: Optional[str] = title + self.caption: Optional[str] = caption + self.parse_mode: ODVInput[str] = parse_mode + self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content diff --git a/telegram/_inline/inlinequeryresultcachedmpeg4gif.py b/telegram/_inline/inlinequeryresultcachedmpeg4gif.py index f9dbc2279..b741e7b73 100644 --- a/telegram/_inline/inlinequeryresultcachedmpeg4gif.py +++ b/telegram/_inline/inlinequeryresultcachedmpeg4gif.py @@ -17,7 +17,7 @@ # 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 classes that represent Telegram InlineQueryResultMpeg4Gif.""" -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -110,12 +110,12 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult): # Required super().__init__(InlineQueryResultType.MPEG4GIF, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.mpeg4_file_id = mpeg4_file_id + self.mpeg4_file_id: str = mpeg4_file_id # Optionals - self.title = title - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = parse_sequence_arg(caption_entities) - self.reply_markup = reply_markup - self.input_message_content = input_message_content + self.title: Optional[str] = title + self.caption: Optional[str] = caption + self.parse_mode: ODVInput[str] = parse_mode + self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content diff --git a/telegram/_inline/inlinequeryresultcachedphoto.py b/telegram/_inline/inlinequeryresultcachedphoto.py index 057db26d1..1197a8d2a 100644 --- a/telegram/_inline/inlinequeryresultcachedphoto.py +++ b/telegram/_inline/inlinequeryresultcachedphoto.py @@ -17,7 +17,7 @@ # 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 classes that represent Telegram InlineQueryResultPhoto""" -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -114,13 +114,13 @@ class InlineQueryResultCachedPhoto(InlineQueryResult): # Required super().__init__(InlineQueryResultType.PHOTO, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.photo_file_id = photo_file_id + self.photo_file_id: str = photo_file_id # Optionals - self.title = title - self.description = description - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = parse_sequence_arg(caption_entities) - self.reply_markup = reply_markup - self.input_message_content = input_message_content + self.title: Optional[str] = title + self.description: Optional[str] = description + self.caption: Optional[str] = caption + self.parse_mode: ODVInput[str] = parse_mode + self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content diff --git a/telegram/_inline/inlinequeryresultcachedsticker.py b/telegram/_inline/inlinequeryresultcachedsticker.py index 0be10f87d..c1e3bdab5 100644 --- a/telegram/_inline/inlinequeryresultcachedsticker.py +++ b/telegram/_inline/inlinequeryresultcachedsticker.py @@ -18,7 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the classes that represent Telegram InlineQueryResultCachedSticker.""" -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -74,8 +74,8 @@ class InlineQueryResultCachedSticker(InlineQueryResult): # Required super().__init__(InlineQueryResultType.STICKER, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.sticker_file_id = sticker_file_id + self.sticker_file_id: str = sticker_file_id # Optionals - self.reply_markup = reply_markup - self.input_message_content = input_message_content + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content diff --git a/telegram/_inline/inlinequeryresultcachedvideo.py b/telegram/_inline/inlinequeryresultcachedvideo.py index 611c8da07..de1ede6a2 100644 --- a/telegram/_inline/inlinequeryresultcachedvideo.py +++ b/telegram/_inline/inlinequeryresultcachedvideo.py @@ -17,7 +17,7 @@ # 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 classes that represent Telegram InlineQueryResultCachedVideo.""" -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -110,13 +110,13 @@ class InlineQueryResultCachedVideo(InlineQueryResult): # Required super().__init__(InlineQueryResultType.VIDEO, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.video_file_id = video_file_id - self.title = title + self.video_file_id: str = video_file_id + self.title: str = title # Optionals - self.description = description - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = parse_sequence_arg(caption_entities) - self.reply_markup = reply_markup - self.input_message_content = input_message_content + self.description: Optional[str] = description + self.caption: Optional[str] = caption + self.parse_mode: ODVInput[str] = parse_mode + self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content diff --git a/telegram/_inline/inlinequeryresultcachedvoice.py b/telegram/_inline/inlinequeryresultcachedvoice.py index a2f9d127b..6ca2482b1 100644 --- a/telegram/_inline/inlinequeryresultcachedvoice.py +++ b/telegram/_inline/inlinequeryresultcachedvoice.py @@ -17,7 +17,7 @@ # 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 classes that represent Telegram InlineQueryResultCachedVoice.""" -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -109,12 +109,12 @@ class InlineQueryResultCachedVoice(InlineQueryResult): # Required super().__init__(InlineQueryResultType.VOICE, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.voice_file_id = voice_file_id - self.title = title + self.voice_file_id: str = voice_file_id + self.title: str = title # Optionals - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = parse_sequence_arg(caption_entities) - self.reply_markup = reply_markup - self.input_message_content = input_message_content + self.caption: Optional[str] = caption + self.parse_mode: ODVInput[str] = parse_mode + self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content diff --git a/telegram/_inline/inlinequeryresultcontact.py b/telegram/_inline/inlinequeryresultcontact.py index 0d59be20b..8d10227fa 100644 --- a/telegram/_inline/inlinequeryresultcontact.py +++ b/telegram/_inline/inlinequeryresultcontact.py @@ -18,7 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the classes that represent Telegram InlineQueryResultContact.""" -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -102,14 +102,14 @@ class InlineQueryResultContact(InlineQueryResult): # Required super().__init__(InlineQueryResultType.CONTACT, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.phone_number = phone_number - self.first_name = first_name + self.phone_number: str = phone_number + self.first_name: str = first_name # Optionals - self.last_name = last_name - self.vcard = vcard - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height + self.last_name: Optional[str] = last_name + self.vcard: Optional[str] = vcard + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content + self.thumb_url: Optional[str] = thumb_url + self.thumb_width: Optional[int] = thumb_width + self.thumb_height: Optional[int] = thumb_height diff --git a/telegram/_inline/inlinequeryresultdocument.py b/telegram/_inline/inlinequeryresultdocument.py index 56966ae67..7b519d5f5 100644 --- a/telegram/_inline/inlinequeryresultdocument.py +++ b/telegram/_inline/inlinequeryresultdocument.py @@ -17,7 +17,7 @@ # 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 classes that represent Telegram InlineQueryResultDocument""" -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -132,17 +132,17 @@ class InlineQueryResultDocument(InlineQueryResult): # Required super().__init__(InlineQueryResultType.DOCUMENT, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.document_url = document_url - self.title = title - self.mime_type = mime_type + self.document_url: str = document_url + self.title: str = title + self.mime_type: str = mime_type # Optionals - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = parse_sequence_arg(caption_entities) - self.description = description - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height + self.caption: Optional[str] = caption + self.parse_mode: ODVInput[str] = parse_mode + self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) + self.description: Optional[str] = description + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content + self.thumb_url: Optional[str] = thumb_url + self.thumb_width: Optional[int] = thumb_width + self.thumb_height: Optional[int] = thumb_height diff --git a/telegram/_inline/inlinequeryresultgame.py b/telegram/_inline/inlinequeryresultgame.py index 27af924a4..513372f44 100644 --- a/telegram/_inline/inlinequeryresultgame.py +++ b/telegram/_inline/inlinequeryresultgame.py @@ -17,6 +17,7 @@ # 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 classes that represent Telegram InlineQueryResultGame.""" +from typing import Optional from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -59,7 +60,7 @@ class InlineQueryResultGame(InlineQueryResult): # Required super().__init__(InlineQueryResultType.GAME, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.id = id - self.game_short_name = game_short_name + self.id: str = id + self.game_short_name: str = game_short_name - self.reply_markup = reply_markup + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup diff --git a/telegram/_inline/inlinequeryresultgif.py b/telegram/_inline/inlinequeryresultgif.py index acf2421c0..7c4f80dc7 100644 --- a/telegram/_inline/inlinequeryresultgif.py +++ b/telegram/_inline/inlinequeryresultgif.py @@ -17,7 +17,7 @@ # 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 classes that represent Telegram InlineQueryResultGif.""" -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -134,17 +134,17 @@ class InlineQueryResultGif(InlineQueryResult): # Required super().__init__(InlineQueryResultType.GIF, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.gif_url = gif_url - self.thumb_url = thumb_url + self.gif_url: str = gif_url + self.thumb_url: str = thumb_url # Optionals - self.gif_width = gif_width - self.gif_height = gif_height - self.gif_duration = gif_duration - self.title = title - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = parse_sequence_arg(caption_entities) - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.thumb_mime_type = thumb_mime_type + self.gif_width: Optional[int] = gif_width + self.gif_height: Optional[int] = gif_height + self.gif_duration: Optional[int] = gif_duration + self.title: Optional[str] = title + self.caption: Optional[str] = caption + self.parse_mode: ODVInput[str] = parse_mode + self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content + self.thumb_mime_type: Optional[str] = thumb_mime_type diff --git a/telegram/_inline/inlinequeryresultlocation.py b/telegram/_inline/inlinequeryresultlocation.py index 6719ab4b9..2632a66c7 100644 --- a/telegram/_inline/inlinequeryresultlocation.py +++ b/telegram/_inline/inlinequeryresultlocation.py @@ -18,7 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the classes that represent Telegram InlineQueryResultLocation.""" -from typing import TYPE_CHECKING, ClassVar +from typing import TYPE_CHECKING, ClassVar, Optional from telegram import constants from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup @@ -136,20 +136,20 @@ class InlineQueryResultLocation(InlineQueryResult): # Required super().__init__(constants.InlineQueryResultType.LOCATION, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.latitude = latitude - self.longitude = longitude - self.title = title + self.latitude: float = latitude + self.longitude: float = longitude + self.title: str = title # Optionals - self.live_period = live_period - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height - self.horizontal_accuracy = horizontal_accuracy - self.heading = heading - self.proximity_alert_radius = ( + self.live_period: Optional[int] = live_period + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content + self.thumb_url: Optional[str] = thumb_url + self.thumb_width: Optional[int] = thumb_width + self.thumb_height: Optional[int] = thumb_height + self.horizontal_accuracy: Optional[float] = horizontal_accuracy + self.heading: Optional[int] = heading + self.proximity_alert_radius: Optional[int] = ( int(proximity_alert_radius) if proximity_alert_radius else None ) diff --git a/telegram/_inline/inlinequeryresultmpeg4gif.py b/telegram/_inline/inlinequeryresultmpeg4gif.py index f162f4eed..c9a979a19 100644 --- a/telegram/_inline/inlinequeryresultmpeg4gif.py +++ b/telegram/_inline/inlinequeryresultmpeg4gif.py @@ -17,7 +17,7 @@ # 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 classes that represent Telegram InlineQueryResultMpeg4Gif.""" -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -137,17 +137,17 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult): # Required super().__init__(InlineQueryResultType.MPEG4GIF, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.mpeg4_url = mpeg4_url - self.thumb_url = thumb_url + self.mpeg4_url: str = mpeg4_url + self.thumb_url: str = thumb_url # Optional - self.mpeg4_width = mpeg4_width - self.mpeg4_height = mpeg4_height - self.mpeg4_duration = mpeg4_duration - self.title = title - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = parse_sequence_arg(caption_entities) - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.thumb_mime_type = thumb_mime_type + self.mpeg4_width: Optional[int] = mpeg4_width + self.mpeg4_height: Optional[int] = mpeg4_height + self.mpeg4_duration: Optional[int] = mpeg4_duration + self.title: Optional[str] = title + self.caption: Optional[str] = caption + self.parse_mode: ODVInput[str] = parse_mode + self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content + self.thumb_mime_type: Optional[str] = thumb_mime_type diff --git a/telegram/_inline/inlinequeryresultphoto.py b/telegram/_inline/inlinequeryresultphoto.py index 6f0eacd80..e632808ef 100644 --- a/telegram/_inline/inlinequeryresultphoto.py +++ b/telegram/_inline/inlinequeryresultphoto.py @@ -17,7 +17,7 @@ # 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 classes that represent Telegram InlineQueryResultPhoto.""" -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -127,16 +127,16 @@ class InlineQueryResultPhoto(InlineQueryResult): # Required super().__init__(InlineQueryResultType.PHOTO, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.photo_url = photo_url - self.thumb_url = thumb_url + self.photo_url: str = photo_url + self.thumb_url: str = thumb_url # Optionals - self.photo_width = photo_width - self.photo_height = photo_height - self.title = title - self.description = description - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = parse_sequence_arg(caption_entities) - self.reply_markup = reply_markup - self.input_message_content = input_message_content + self.photo_width: Optional[int] = photo_width + self.photo_height: Optional[int] = photo_height + self.title: Optional[str] = title + self.description: Optional[str] = description + self.caption: Optional[str] = caption + self.parse_mode: ODVInput[str] = parse_mode + self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content diff --git a/telegram/_inline/inlinequeryresultvenue.py b/telegram/_inline/inlinequeryresultvenue.py index f0bb90a9e..216cc142f 100644 --- a/telegram/_inline/inlinequeryresultvenue.py +++ b/telegram/_inline/inlinequeryresultvenue.py @@ -18,7 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the classes that represent Telegram InlineQueryResultVenue.""" -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -129,18 +129,18 @@ class InlineQueryResultVenue(InlineQueryResult): # Required super().__init__(InlineQueryResultType.VENUE, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.latitude = latitude - self.longitude = longitude - self.title = title - self.address = address + self.latitude: float = latitude + self.longitude: float = longitude + self.title: str = title + self.address: str = address # Optional - self.foursquare_id = foursquare_id - self.foursquare_type = foursquare_type - self.google_place_id = google_place_id - self.google_place_type = google_place_type - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height + self.foursquare_id: Optional[str] = foursquare_id + self.foursquare_type: Optional[str] = foursquare_type + self.google_place_id: Optional[str] = google_place_id + self.google_place_type: Optional[str] = google_place_type + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content + self.thumb_url: Optional[str] = thumb_url + self.thumb_width: Optional[int] = thumb_width + self.thumb_height: Optional[int] = thumb_height diff --git a/telegram/_inline/inlinequeryresultvideo.py b/telegram/_inline/inlinequeryresultvideo.py index 2358dd060..972e319c3 100644 --- a/telegram/_inline/inlinequeryresultvideo.py +++ b/telegram/_inline/inlinequeryresultvideo.py @@ -17,7 +17,7 @@ # 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 classes that represent Telegram InlineQueryResultVideo.""" -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -145,18 +145,18 @@ class InlineQueryResultVideo(InlineQueryResult): # Required super().__init__(InlineQueryResultType.VIDEO, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.video_url = video_url - self.mime_type = mime_type - self.thumb_url = thumb_url - self.title = title + self.video_url: str = video_url + self.mime_type: str = mime_type + self.thumb_url: str = thumb_url + self.title: str = title # Optional - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = parse_sequence_arg(caption_entities) - self.video_width = video_width - self.video_height = video_height - self.video_duration = video_duration - self.description = description - self.reply_markup = reply_markup - self.input_message_content = input_message_content + self.caption: Optional[str] = caption + self.parse_mode: ODVInput[str] = parse_mode + self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) + self.video_width: Optional[int] = video_width + self.video_height: Optional[int] = video_height + self.video_duration: Optional[int] = video_duration + self.description: Optional[str] = description + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content diff --git a/telegram/_inline/inlinequeryresultvoice.py b/telegram/_inline/inlinequeryresultvoice.py index 46cf2b2a8..6d5263724 100644 --- a/telegram/_inline/inlinequeryresultvoice.py +++ b/telegram/_inline/inlinequeryresultvoice.py @@ -17,7 +17,7 @@ # 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 classes that represent Telegram InlineQueryResultVoice.""" -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinequeryresult import InlineQueryResult @@ -115,13 +115,13 @@ class InlineQueryResultVoice(InlineQueryResult): # Required super().__init__(InlineQueryResultType.VOICE, id, api_kwargs=api_kwargs) with self._unfrozen(): - self.voice_url = voice_url - self.title = title + self.voice_url: str = voice_url + self.title: str = title # Optional - self.voice_duration = voice_duration - self.caption = caption - self.parse_mode = parse_mode - self.caption_entities = parse_sequence_arg(caption_entities) - self.reply_markup = reply_markup - self.input_message_content = input_message_content + self.voice_duration: Optional[int] = voice_duration + self.caption: Optional[str] = caption + self.parse_mode: ODVInput[str] = parse_mode + self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.input_message_content: Optional[InputMessageContent] = input_message_content diff --git a/telegram/_inline/inputcontactmessagecontent.py b/telegram/_inline/inputcontactmessagecontent.py index a798ecb7a..ef24f5a95 100644 --- a/telegram/_inline/inputcontactmessagecontent.py +++ b/telegram/_inline/inputcontactmessagecontent.py @@ -17,6 +17,7 @@ # 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 classes that represent Telegram InputContactMessageContent.""" +from typing import Optional from telegram._inline.inputmessagecontent import InputMessageContent from telegram._utils.types import JSONDict @@ -58,10 +59,10 @@ class InputContactMessageContent(InputMessageContent): super().__init__(api_kwargs=api_kwargs) with self._unfrozen(): # Required - self.phone_number = phone_number - self.first_name = first_name + self.phone_number: str = phone_number + self.first_name: str = first_name # Optionals - self.last_name = last_name - self.vcard = vcard + self.last_name: Optional[str] = last_name + self.vcard: Optional[str] = vcard self._id_attrs = (self.phone_number,) diff --git a/telegram/_inline/inputinvoicemessagecontent.py b/telegram/_inline/inputinvoicemessagecontent.py index 502273c0c..aea0ea3c7 100644 --- a/telegram/_inline/inputinvoicemessagecontent.py +++ b/telegram/_inline/inputinvoicemessagecontent.py @@ -17,7 +17,7 @@ # 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 a class that represents a Telegram InputInvoiceMessageContent.""" -from typing import TYPE_CHECKING, Optional, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._inline.inputmessagecontent import InputMessageContent from telegram._payment.labeledprice import LabeledPrice @@ -213,27 +213,27 @@ class InputInvoiceMessageContent(InputMessageContent): super().__init__(api_kwargs=api_kwargs) with self._unfrozen(): # Required - self.title = title - self.description = description - self.payload = payload - self.provider_token = provider_token - self.currency = currency - self.prices = parse_sequence_arg(prices) + self.title: str = title + self.description: str = description + self.payload: str = payload + self.provider_token: str = provider_token + self.currency: str = currency + self.prices: Tuple[LabeledPrice, ...] = parse_sequence_arg(prices) # Optionals - self.max_tip_amount = max_tip_amount - self.suggested_tip_amounts = parse_sequence_arg(suggested_tip_amounts) - self.provider_data = provider_data - self.photo_url = photo_url - self.photo_size = photo_size - self.photo_width = photo_width - self.photo_height = photo_height - self.need_name = need_name - self.need_phone_number = need_phone_number - self.need_email = need_email - self.need_shipping_address = need_shipping_address - self.send_phone_number_to_provider = send_phone_number_to_provider - self.send_email_to_provider = send_email_to_provider - self.is_flexible = is_flexible + self.max_tip_amount: Optional[int] = max_tip_amount + self.suggested_tip_amounts: Tuple[int, ...] = parse_sequence_arg(suggested_tip_amounts) + self.provider_data: Optional[str] = provider_data + self.photo_url: Optional[str] = photo_url + self.photo_size: Optional[int] = photo_size + self.photo_width: Optional[int] = photo_width + self.photo_height: Optional[int] = photo_height + self.need_name: Optional[bool] = need_name + self.need_phone_number: Optional[bool] = need_phone_number + self.need_email: Optional[bool] = need_email + self.need_shipping_address: Optional[bool] = need_shipping_address + self.send_phone_number_to_provider: Optional[bool] = send_phone_number_to_provider + self.send_email_to_provider: Optional[bool] = send_email_to_provider + self.is_flexible: Optional[bool] = is_flexible self._id_attrs = ( self.title, diff --git a/telegram/_inline/inputlocationmessagecontent.py b/telegram/_inline/inputlocationmessagecontent.py index 9a8fdfb23..1f852600e 100644 --- a/telegram/_inline/inputlocationmessagecontent.py +++ b/telegram/_inline/inputlocationmessagecontent.py @@ -18,7 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the classes that represent Telegram InputLocationMessageContent.""" -from typing import ClassVar +from typing import ClassVar, Optional from telegram import constants from telegram._inline.inputmessagecontent import InputMessageContent @@ -93,14 +93,14 @@ class InputLocationMessageContent(InputMessageContent): super().__init__(api_kwargs=api_kwargs) with self._unfrozen(): # Required - self.latitude = latitude - self.longitude = longitude + self.latitude: float = latitude + self.longitude: float = longitude # Optionals - self.live_period = live_period - self.horizontal_accuracy = horizontal_accuracy - self.heading = heading - self.proximity_alert_radius = ( + self.live_period: Optional[int] = live_period + self.horizontal_accuracy: Optional[float] = horizontal_accuracy + self.heading: Optional[int] = heading + self.proximity_alert_radius: Optional[int] = ( int(proximity_alert_radius) if proximity_alert_radius else None ) diff --git a/telegram/_inline/inputtextmessagecontent.py b/telegram/_inline/inputtextmessagecontent.py index 82e5dc8ba..9724f6ef5 100644 --- a/telegram/_inline/inputtextmessagecontent.py +++ b/telegram/_inline/inputtextmessagecontent.py @@ -17,7 +17,7 @@ # 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 classes that represent Telegram InputTextMessageContent.""" -from typing import Sequence +from typing import Sequence, Tuple from telegram._inline.inputmessagecontent import InputMessageContent from telegram._messageentity import MessageEntity @@ -81,10 +81,10 @@ class InputTextMessageContent(InputMessageContent): super().__init__(api_kwargs=api_kwargs) with self._unfrozen(): # Required - self.message_text = message_text + self.message_text: str = message_text # Optionals - self.parse_mode = parse_mode - self.entities = parse_sequence_arg(entities) - self.disable_web_page_preview = disable_web_page_preview + self.parse_mode: ODVInput[str] = parse_mode + self.entities: Tuple[MessageEntity, ...] = parse_sequence_arg(entities) + self.disable_web_page_preview: ODVInput[bool] = disable_web_page_preview self._id_attrs = (self.message_text,) diff --git a/telegram/_inline/inputvenuemessagecontent.py b/telegram/_inline/inputvenuemessagecontent.py index 48b2cbfce..0e6b9335a 100644 --- a/telegram/_inline/inputvenuemessagecontent.py +++ b/telegram/_inline/inputvenuemessagecontent.py @@ -17,6 +17,7 @@ # 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 classes that represent Telegram InputVenueMessageContent.""" +from typing import Optional from telegram._inline.inputmessagecontent import InputMessageContent from telegram._utils.types import JSONDict @@ -90,15 +91,15 @@ class InputVenueMessageContent(InputMessageContent): super().__init__(api_kwargs=api_kwargs) with self._unfrozen(): # Required - self.latitude = latitude - self.longitude = longitude - self.title = title - self.address = address + self.latitude: float = latitude + self.longitude: float = longitude + self.title: str = title + self.address: str = address # Optionals - self.foursquare_id = foursquare_id - self.foursquare_type = foursquare_type - self.google_place_id = google_place_id - self.google_place_type = google_place_type + self.foursquare_id: Optional[str] = foursquare_id + self.foursquare_type: Optional[str] = foursquare_type + self.google_place_id: Optional[str] = google_place_id + self.google_place_type: Optional[str] = google_place_type self._id_attrs = ( self.latitude, diff --git a/telegram/_keyboardbutton.py b/telegram/_keyboardbutton.py index 976d4938d..6fbdfc8aa 100644 --- a/telegram/_keyboardbutton.py +++ b/telegram/_keyboardbutton.py @@ -99,12 +99,12 @@ class KeyboardButton(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.text = text + self.text: str = text # Optionals - self.request_contact = request_contact - self.request_location = request_location - self.request_poll = request_poll - self.web_app = web_app + self.request_contact: Optional[bool] = request_contact + self.request_location: Optional[bool] = request_location + self.request_poll: Optional[KeyboardButtonPollType] = request_poll + self.web_app: Optional[WebAppInfo] = web_app self._id_attrs = ( self.text, diff --git a/telegram/_keyboardbuttonpolltype.py b/telegram/_keyboardbuttonpolltype.py index 80a1f2e40..b54c21537 100644 --- a/telegram/_keyboardbuttonpolltype.py +++ b/telegram/_keyboardbuttonpolltype.py @@ -17,6 +17,7 @@ # 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 type of a Telegram Poll.""" +from typing import Optional from telegram._telegramobject import TelegramObject from telegram._utils.types import JSONDict @@ -50,7 +51,7 @@ class KeyboardButtonPollType(TelegramObject): self, type: str = None, *, api_kwargs: JSONDict = None # skipcq: PYL-W0622 ): # pylint: disable=redefined-builtin super().__init__(api_kwargs=api_kwargs) - self.type = type + self.type: Optional[str] = type self._id_attrs = (self.type,) diff --git a/telegram/_loginurl.py b/telegram/_loginurl.py index b87929d5e..2fe54d4cc 100644 --- a/telegram/_loginurl.py +++ b/telegram/_loginurl.py @@ -17,6 +17,7 @@ # 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 LoginUrl.""" +from typing import Optional from telegram._telegramobject import TelegramObject from telegram._utils.types import JSONDict @@ -93,11 +94,11 @@ class LoginUrl(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.url = url + self.url: str = url # Optional - self.forward_text = forward_text - self.bot_username = bot_username - self.request_write_access = request_write_access + self.forward_text: Optional[bool] = forward_text + self.bot_username: Optional[str] = bot_username + self.request_write_access: Optional[bool] = request_write_access self._id_attrs = (self.url,) diff --git a/telegram/_menubutton.py b/telegram/_menubutton.py index 2c544cad2..3fb3d51ab 100644 --- a/telegram/_menubutton.py +++ b/telegram/_menubutton.py @@ -58,7 +58,7 @@ class MenuButton(TelegramObject): self, type: str, *, api_kwargs: JSONDict = None # skipcq: PYL-W0622 ): # pylint: disable=redefined-builtin super().__init__(api_kwargs=api_kwargs) - self.type = type + self.type: str = type self._id_attrs = (self.type,) @@ -150,8 +150,8 @@ class MenuButtonWebApp(MenuButton): def __init__(self, text: str, web_app: WebAppInfo, *, api_kwargs: JSONDict = None): super().__init__(type=constants.MenuButtonType.WEB_APP, api_kwargs=api_kwargs) with self._unfrozen(): - self.text = text - self.web_app = web_app + self.text: str = text + self.web_app: WebAppInfo = web_app self._id_attrs = (self.type, self.text, self.web_app) diff --git a/telegram/_message.py b/telegram/_message.py index f0644256c..9c4b78ee8 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -699,76 +699,86 @@ class Message(TelegramObject): super().__init__(api_kwargs=api_kwargs) # Required - self.message_id = message_id + self.message_id: int = message_id # Optionals - self.from_user = from_user - self.sender_chat = sender_chat - self.date = date - self.chat = chat - self.forward_from = forward_from - self.forward_from_chat = forward_from_chat - self.forward_date = forward_date - self.is_automatic_forward = is_automatic_forward - self.reply_to_message = reply_to_message - self.edit_date = edit_date - self.has_protected_content = has_protected_content - self.text = text - self.entities = parse_sequence_arg(entities) - self.caption_entities = parse_sequence_arg(caption_entities) - self.audio = audio - self.game = game - self.document = document - self.photo = parse_sequence_arg(photo) - self.sticker = sticker - self.video = video - self.voice = voice - self.video_note = video_note - self.caption = caption - self.contact = contact - self.location = location - self.venue = venue - self.new_chat_members = parse_sequence_arg(new_chat_members) - self.left_chat_member = left_chat_member - self.new_chat_title = new_chat_title - self.new_chat_photo = parse_sequence_arg(new_chat_photo) - self.delete_chat_photo = bool(delete_chat_photo) - self.group_chat_created = bool(group_chat_created) - self.supergroup_chat_created = bool(supergroup_chat_created) - self.migrate_to_chat_id = migrate_to_chat_id - self.migrate_from_chat_id = migrate_from_chat_id - self.channel_chat_created = bool(channel_chat_created) - self.message_auto_delete_timer_changed = message_auto_delete_timer_changed - self.pinned_message = pinned_message - self.forward_from_message_id = forward_from_message_id - self.invoice = invoice - self.successful_payment = successful_payment - self.connected_website = connected_website - self.forward_signature = forward_signature - self.forward_sender_name = forward_sender_name - self.author_signature = author_signature - self.media_group_id = media_group_id - self.animation = animation - self.passport_data = passport_data - self.poll = poll - self.dice = dice - self.via_bot = via_bot - self.proximity_alert_triggered = proximity_alert_triggered - self.video_chat_scheduled = video_chat_scheduled - self.video_chat_started = video_chat_started - self.video_chat_ended = video_chat_ended - self.video_chat_participants_invited = video_chat_participants_invited - self.reply_markup = reply_markup - self.web_app_data = web_app_data - self.is_topic_message = is_topic_message - self.message_thread_id = message_thread_id - self.forum_topic_created = forum_topic_created - self.forum_topic_closed = forum_topic_closed - self.forum_topic_reopened = forum_topic_reopened - self.forum_topic_edited = forum_topic_edited - self.general_forum_topic_hidden = general_forum_topic_hidden - self.general_forum_topic_unhidden = general_forum_topic_unhidden - self.write_access_allowed = write_access_allowed - self.has_media_spoiler = has_media_spoiler + self.from_user: Optional[User] = from_user + self.sender_chat: Optional[Chat] = sender_chat + self.date: datetime.datetime = date + self.chat: Chat = chat + self.forward_from: Optional[User] = forward_from + self.forward_from_chat: Optional[Chat] = forward_from_chat + self.forward_date: Optional[datetime.datetime] = forward_date + self.is_automatic_forward: Optional[bool] = is_automatic_forward + self.reply_to_message: Optional[Message] = reply_to_message + self.edit_date: Optional[datetime.datetime] = edit_date + self.has_protected_content: Optional[bool] = has_protected_content + self.text: Optional[str] = text + self.entities: Tuple["MessageEntity", ...] = parse_sequence_arg(entities) + self.caption_entities: Tuple["MessageEntity", ...] = parse_sequence_arg(caption_entities) + self.audio: Optional[Audio] = audio + self.game: Optional[Game] = game + self.document: Optional[Document] = document + self.photo: Tuple[PhotoSize, ...] = parse_sequence_arg(photo) + self.sticker: Optional[Sticker] = sticker + self.video: Optional[Video] = video + self.voice: Optional[Voice] = voice + self.video_note: Optional[VideoNote] = video_note + self.caption: Optional[str] = caption + self.contact: Optional[Contact] = contact + self.location: Optional[Location] = location + self.venue: Optional[Venue] = venue + self.new_chat_members: Tuple[User, ...] = parse_sequence_arg(new_chat_members) + self.left_chat_member: Optional[User] = left_chat_member + self.new_chat_title: Optional[str] = new_chat_title + self.new_chat_photo: Tuple[PhotoSize, ...] = parse_sequence_arg(new_chat_photo) + self.delete_chat_photo: Optional[bool] = bool(delete_chat_photo) + self.group_chat_created: Optional[bool] = bool(group_chat_created) + self.supergroup_chat_created: Optional[bool] = bool(supergroup_chat_created) + self.migrate_to_chat_id: Optional[int] = migrate_to_chat_id + self.migrate_from_chat_id: Optional[int] = migrate_from_chat_id + self.channel_chat_created: Optional[bool] = bool(channel_chat_created) + self.message_auto_delete_timer_changed: Optional[ + MessageAutoDeleteTimerChanged + ] = message_auto_delete_timer_changed + self.pinned_message: Optional[Message] = pinned_message + self.forward_from_message_id: Optional[int] = forward_from_message_id + self.invoice: Optional[Invoice] = invoice + self.successful_payment: Optional[SuccessfulPayment] = successful_payment + self.connected_website: Optional[str] = connected_website + self.forward_signature: Optional[str] = forward_signature + self.forward_sender_name: Optional[str] = forward_sender_name + self.author_signature: Optional[str] = author_signature + self.media_group_id: Optional[str] = media_group_id + self.animation: Optional[Animation] = animation + self.passport_data: Optional[PassportData] = passport_data + self.poll: Optional[Poll] = poll + self.dice: Optional[Dice] = dice + self.via_bot: Optional[User] = via_bot + self.proximity_alert_triggered: Optional[ + ProximityAlertTriggered + ] = proximity_alert_triggered + self.video_chat_scheduled: Optional[VideoChatScheduled] = video_chat_scheduled + self.video_chat_started: Optional[VideoChatStarted] = video_chat_started + self.video_chat_ended: Optional[VideoChatEnded] = video_chat_ended + self.video_chat_participants_invited: Optional[ + VideoChatParticipantsInvited + ] = video_chat_participants_invited + self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup + self.web_app_data: Optional[WebAppData] = web_app_data + self.is_topic_message: Optional[bool] = is_topic_message + self.message_thread_id: Optional[int] = message_thread_id + self.forum_topic_created: Optional[ForumTopicCreated] = forum_topic_created + self.forum_topic_closed: Optional[ForumTopicClosed] = forum_topic_closed + self.forum_topic_reopened: Optional[ForumTopicReopened] = forum_topic_reopened + self.forum_topic_edited: Optional[ForumTopicEdited] = forum_topic_edited + self.general_forum_topic_hidden: Optional[ + GeneralForumTopicHidden + ] = general_forum_topic_hidden + self.general_forum_topic_unhidden: Optional[ + GeneralForumTopicUnhidden + ] = general_forum_topic_unhidden + self.write_access_allowed: Optional[WriteAccessAllowed] = write_access_allowed + self.has_media_spoiler: Optional[bool] = has_media_spoiler self._effective_attachment = DEFAULT_NONE diff --git a/telegram/_messageautodeletetimerchanged.py b/telegram/_messageautodeletetimerchanged.py index d41656c1b..ada024563 100644 --- a/telegram/_messageautodeletetimerchanged.py +++ b/telegram/_messageautodeletetimerchanged.py @@ -51,7 +51,7 @@ class MessageAutoDeleteTimerChanged(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.message_auto_delete_time = message_auto_delete_time + self.message_auto_delete_time: int = message_auto_delete_time self._id_attrs = (self.message_auto_delete_time,) diff --git a/telegram/_messageentity.py b/telegram/_messageentity.py index 8e823c109..3edddf27b 100644 --- a/telegram/_messageentity.py +++ b/telegram/_messageentity.py @@ -105,14 +105,14 @@ class MessageEntity(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.type = enum.get_member(constants.MessageEntityType, type, type) - self.offset = offset - self.length = length + self.type: str = enum.get_member(constants.MessageEntityType, type, type) + self.offset: int = offset + self.length: int = length # Optionals - self.url = url - self.user = user - self.language = language - self.custom_emoji_id = custom_emoji_id + self.url: Optional[str] = url + self.user: Optional[User] = user + self.language: Optional[str] = language + self.custom_emoji_id: Optional[str] = custom_emoji_id self._id_attrs = (self.type, self.offset, self.length) diff --git a/telegram/_messageid.py b/telegram/_messageid.py index 4b5d827b1..4ffa5d3cf 100644 --- a/telegram/_messageid.py +++ b/telegram/_messageid.py @@ -39,7 +39,7 @@ class MessageId(TelegramObject): def __init__(self, message_id: int, *, api_kwargs: JSONDict = None): super().__init__(api_kwargs=api_kwargs) - self.message_id = message_id + self.message_id: int = message_id self._id_attrs = (self.message_id,) diff --git a/telegram/_passport/credentials.py b/telegram/_passport/credentials.py index 892b8e337..df64d9ece 100644 --- a/telegram/_passport/credentials.py +++ b/telegram/_passport/credentials.py @@ -19,7 +19,7 @@ # pylint: disable=missing-module-docstring, redefined-builtin import json from base64 import b64decode -from typing import TYPE_CHECKING, Optional, Sequence, no_type_check +from typing import TYPE_CHECKING, Optional, Sequence, Tuple, no_type_check try: from cryptography.hazmat.backends import default_backend @@ -147,9 +147,9 @@ class EncryptedCredentials(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.data = data - self.hash = hash - self.secret = secret + self.data: str = data + self.hash: str = hash + self.secret: str = secret self._id_attrs = (self.data, self.hash, self.secret) @@ -226,8 +226,8 @@ class Credentials(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.secure_data = secure_data - self.nonce = nonce + self.secure_data: SecureData = secure_data + self.nonce: str = nonce self._freeze() @@ -327,17 +327,17 @@ class SecureData(TelegramObject): super().__init__(api_kwargs=api_kwargs) # Optionals - self.temporary_registration = temporary_registration - self.passport_registration = passport_registration - self.rental_agreement = rental_agreement - self.bank_statement = bank_statement - self.utility_bill = utility_bill - self.address = address - self.identity_card = identity_card - self.driver_license = driver_license - self.internal_passport = internal_passport - self.passport = passport - self.personal_details = personal_details + self.temporary_registration: Optional[SecureValue] = temporary_registration + self.passport_registration: Optional[SecureValue] = passport_registration + self.rental_agreement: Optional[SecureValue] = rental_agreement + self.bank_statement: Optional[SecureValue] = bank_statement + self.utility_bill: Optional[SecureValue] = utility_bill + self.address: Optional[SecureValue] = address + self.identity_card: Optional[SecureValue] = identity_card + self.driver_license: Optional[SecureValue] = driver_license + self.internal_passport: Optional[SecureValue] = internal_passport + self.passport: Optional[SecureValue] = passport + self.personal_details: Optional[SecureValue] = personal_details self._freeze() @@ -438,12 +438,12 @@ class SecureValue(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.data = data - self.front_side = front_side - self.reverse_side = reverse_side - self.selfie = selfie - self.files = parse_sequence_arg(files) - self.translation = parse_sequence_arg(translation) + self.data: Optional[DataCredentials] = data + self.front_side: Optional[FileCredentials] = front_side + self.reverse_side: Optional[FileCredentials] = reverse_side + self.selfie: Optional[FileCredentials] = selfie + self.files: Tuple["FileCredentials", ...] = parse_sequence_arg(files) + self.translation: Tuple["FileCredentials", ...] = parse_sequence_arg(translation) self._freeze() @@ -475,12 +475,12 @@ class _CredentialsBase(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) with self._unfrozen(): - self.hash = hash - self.secret = secret + self.hash: str = hash + self.secret: str = secret # Aliases just to be sure - self.file_hash = self.hash - self.data_hash = self.hash + self.file_hash: str = self.hash + self.data_hash: str = self.hash class DataCredentials(_CredentialsBase): diff --git a/telegram/_passport/data.py b/telegram/_passport/data.py index e90b66f52..d58f9c05a 100644 --- a/telegram/_passport/data.py +++ b/telegram/_passport/data.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. # pylint: disable=missing-module-docstring +from typing import Optional from telegram._telegramobject import TelegramObject from telegram._utils.types import JSONDict @@ -89,16 +90,16 @@ class PersonalDetails(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.first_name = first_name - self.last_name = last_name - self.middle_name = middle_name - self.birth_date = birth_date - self.gender = gender - self.country_code = country_code - self.residence_country_code = residence_country_code - self.first_name_native = first_name_native - self.last_name_native = last_name_native - self.middle_name_native = middle_name_native + self.first_name: str = first_name + self.last_name: str = last_name + self.middle_name: Optional[str] = middle_name + self.birth_date: str = birth_date + self.gender: str = gender + self.country_code: str = country_code + self.residence_country_code: str = residence_country_code + self.first_name_native: Optional[str] = first_name_native + self.last_name_native: Optional[str] = last_name_native + self.middle_name_native: Optional[str] = middle_name_native self._freeze() @@ -146,12 +147,12 @@ class ResidentialAddress(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.street_line1 = street_line1 - self.street_line2 = street_line2 - self.city = city - self.state = state - self.country_code = country_code - self.post_code = post_code + self.street_line1: str = street_line1 + self.street_line2: str = street_line2 + self.city: str = city + self.state: str = state + self.country_code: str = country_code + self.post_code: str = post_code self._freeze() @@ -179,7 +180,7 @@ class IdDocumentData(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.document_no = document_no - self.expiry_date = expiry_date + self.document_no: str = document_no + self.expiry_date: str = expiry_date self._freeze() diff --git a/telegram/_passport/encryptedpassportelement.py b/telegram/_passport/encryptedpassportelement.py index 1e7034827..d1dc7a926 100644 --- a/telegram/_passport/encryptedpassportelement.py +++ b/telegram/_passport/encryptedpassportelement.py @@ -18,7 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram EncryptedPassportElement.""" from base64 import b64decode -from typing import TYPE_CHECKING, Optional, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._passport.credentials import decrypt_json from telegram._passport.data import IdDocumentData, PersonalDetails, ResidentialAddress @@ -166,17 +166,17 @@ class EncryptedPassportElement(TelegramObject): super().__init__(api_kwargs=api_kwargs) # Required - self.type = type + self.type: str = type # Optionals - self.data = data - self.phone_number = phone_number - self.email = email - self.files = parse_sequence_arg(files) - self.front_side = front_side - self.reverse_side = reverse_side - self.selfie = selfie - self.translation = parse_sequence_arg(translation) - self.hash = hash + self.data: Optional[PersonalDetails] = data + self.phone_number: Optional[str] = phone_number + self.email: Optional[str] = email + self.files: Tuple[PassportFile, ...] = parse_sequence_arg(files) + self.front_side: Optional[PassportFile] = front_side + self.reverse_side: Optional[PassportFile] = reverse_side + self.selfie: Optional[PassportFile] = selfie + self.translation: Tuple[PassportFile, ...] = parse_sequence_arg(translation) + self.hash: str = hash self._id_attrs = ( self.type, diff --git a/telegram/_passport/passportdata.py b/telegram/_passport/passportdata.py index 8f1f38cff..92d395118 100644 --- a/telegram/_passport/passportdata.py +++ b/telegram/_passport/passportdata.py @@ -72,8 +72,8 @@ class PassportData(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) - self.data = parse_sequence_arg(data) - self.credentials = credentials + self.data: Tuple[EncryptedPassportElement, ...] = parse_sequence_arg(data) + self.credentials: EncryptedCredentials = credentials self._decrypted_data: Optional[Tuple[EncryptedPassportElement]] = None self._id_attrs = tuple([x.type for x in data] + [credentials.hash]) diff --git a/telegram/_passport/passportelementerrors.py b/telegram/_passport/passportelementerrors.py index 42c86ba24..f615c1ea8 100644 --- a/telegram/_passport/passportelementerrors.py +++ b/telegram/_passport/passportelementerrors.py @@ -49,9 +49,9 @@ class PassportElementError(TelegramObject): def __init__(self, source: str, type: str, message: str, *, api_kwargs: JSONDict = None): super().__init__(api_kwargs=api_kwargs) # Required - self.source = str(source) - self.type = str(type) - self.message = str(message) + self.source: str = str(source) + self.type: str = str(type) + self.message: str = str(message) self._id_attrs = (self.source, self.type) @@ -99,8 +99,8 @@ class PassportElementErrorDataField(PassportElementError): # Required super().__init__("data", type, message, api_kwargs=api_kwargs) with self._unfrozen(): - self.field_name = field_name - self.data_hash = data_hash + self.field_name: str = field_name + self.data_hash: str = data_hash self._id_attrs = ( self.source, @@ -142,7 +142,7 @@ class PassportElementErrorFile(PassportElementError): # Required super().__init__("file", type, message, api_kwargs=api_kwargs) with self._unfrozen(): - self.file_hash = file_hash + self.file_hash: str = file_hash self._id_attrs = (self.source, self.type, self.file_hash, self.message) @@ -178,7 +178,7 @@ class PassportElementErrorFiles(PassportElementError): # Required super().__init__("files", type, message, api_kwargs=api_kwargs) with self._unfrozen(): - self.file_hashes = file_hashes + self.file_hashes: str = file_hashes self._id_attrs = (self.source, self.type, self.message) + tuple(file_hashes) @@ -214,7 +214,7 @@ class PassportElementErrorFrontSide(PassportElementError): # Required super().__init__("front_side", type, message, api_kwargs=api_kwargs) with self._unfrozen(): - self.file_hash = file_hash + self.file_hash: str = file_hash self._id_attrs = (self.source, self.type, self.file_hash, self.message) @@ -250,7 +250,7 @@ class PassportElementErrorReverseSide(PassportElementError): # Required super().__init__("reverse_side", type, message, api_kwargs=api_kwargs) with self._unfrozen(): - self.file_hash = file_hash + self.file_hash: str = file_hash self._id_attrs = (self.source, self.type, self.file_hash, self.message) @@ -284,7 +284,7 @@ class PassportElementErrorSelfie(PassportElementError): # Required super().__init__("selfie", type, message, api_kwargs=api_kwargs) with self._unfrozen(): - self.file_hash = file_hash + self.file_hash: str = file_hash self._id_attrs = (self.source, self.type, self.file_hash, self.message) @@ -322,7 +322,7 @@ class PassportElementErrorTranslationFile(PassportElementError): # Required super().__init__("translation_file", type, message, api_kwargs=api_kwargs) with self._unfrozen(): - self.file_hash = file_hash + self.file_hash: str = file_hash self._id_attrs = (self.source, self.type, self.file_hash, self.message) @@ -360,7 +360,7 @@ class PassportElementErrorTranslationFiles(PassportElementError): # Required super().__init__("translation_files", type, message, api_kwargs=api_kwargs) with self._unfrozen(): - self.file_hashes = file_hashes + self.file_hashes: str = file_hashes self._id_attrs = (self.source, self.type, self.message) + tuple(file_hashes) @@ -392,6 +392,6 @@ class PassportElementErrorUnspecified(PassportElementError): # Required super().__init__("unspecified", type, message, api_kwargs=api_kwargs) with self._unfrozen(): - self.element_hash = element_hash + self.element_hash: str = element_hash self._id_attrs = (self.source, self.type, self.element_hash, self.message) diff --git a/telegram/_passport/passportfile.py b/telegram/_passport/passportfile.py index 0915969b8..e3d12ee81 100644 --- a/telegram/_passport/passportfile.py +++ b/telegram/_passport/passportfile.py @@ -78,13 +78,13 @@ class PassportFile(TelegramObject): super().__init__(api_kwargs=api_kwargs) # Required - self.file_id = file_id - self.file_unique_id = file_unique_id - self.file_size = file_size - self.file_date = file_date + self.file_id: str = file_id + self.file_unique_id: str = file_unique_id + self.file_size: int = file_size + self.file_date: int = file_date # Optionals - self._credentials = credentials + self._credentials: Optional[FileCredentials] = credentials self._id_attrs = (self.file_unique_id,) diff --git a/telegram/_payment/invoice.py b/telegram/_payment/invoice.py index d91ab6188..031f20f26 100644 --- a/telegram/_payment/invoice.py +++ b/telegram/_payment/invoice.py @@ -79,11 +79,11 @@ class Invoice(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.title = title - self.description = description - self.start_parameter = start_parameter - self.currency = currency - self.total_amount = total_amount + self.title: str = title + self.description: str = description + self.start_parameter: str = start_parameter + self.currency: str = currency + self.total_amount: int = total_amount self._id_attrs = ( self.title, diff --git a/telegram/_payment/labeledprice.py b/telegram/_payment/labeledprice.py index 8974c2352..ec205b4b6 100644 --- a/telegram/_payment/labeledprice.py +++ b/telegram/_payment/labeledprice.py @@ -55,8 +55,8 @@ class LabeledPrice(TelegramObject): def __init__(self, label: str, amount: int, *, api_kwargs: JSONDict = None): super().__init__(api_kwargs=api_kwargs) - self.label = label - self.amount = amount + self.label: str = label + self.amount: int = amount self._id_attrs = (self.label, self.amount) diff --git a/telegram/_payment/orderinfo.py b/telegram/_payment/orderinfo.py index c43ac2a04..d6b8d2942 100644 --- a/telegram/_payment/orderinfo.py +++ b/telegram/_payment/orderinfo.py @@ -61,10 +61,10 @@ class OrderInfo(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.name = name - self.phone_number = phone_number - self.email = email - self.shipping_address = shipping_address + self.name: Optional[str] = name + self.phone_number: Optional[str] = phone_number + self.email: Optional[str] = email + self.shipping_address: Optional[str] = shipping_address self._id_attrs = (self.name, self.phone_number, self.email, self.shipping_address) diff --git a/telegram/_payment/precheckoutquery.py b/telegram/_payment/precheckoutquery.py index a8a032e68..e5965d988 100644 --- a/telegram/_payment/precheckoutquery.py +++ b/telegram/_payment/precheckoutquery.py @@ -95,13 +95,13 @@ class PreCheckoutQuery(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.id = id # pylint: disable=invalid-name - self.from_user = from_user - self.currency = currency - self.total_amount = total_amount - self.invoice_payload = invoice_payload - self.shipping_option_id = shipping_option_id - self.order_info = order_info + self.id: str = id # pylint: disable=invalid-name + self.from_user: User = from_user + self.currency: str = currency + self.total_amount: int = total_amount + self.invoice_payload: str = invoice_payload + self.shipping_option_id: Optional[str] = shipping_option_id + self.order_info: Optional[OrderInfo] = order_info self._id_attrs = (self.id,) diff --git a/telegram/_payment/shippingaddress.py b/telegram/_payment/shippingaddress.py index 2398a8cb9..d56db45e6 100644 --- a/telegram/_payment/shippingaddress.py +++ b/telegram/_payment/shippingaddress.py @@ -68,12 +68,12 @@ class ShippingAddress(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.country_code = country_code - self.state = state - self.city = city - self.street_line1 = street_line1 - self.street_line2 = street_line2 - self.post_code = post_code + self.country_code: str = country_code + self.state: str = state + self.city: str = city + self.street_line1: str = street_line1 + self.street_line2: str = street_line2 + self.post_code: str = post_code self._id_attrs = ( self.country_code, diff --git a/telegram/_payment/shippingoption.py b/telegram/_payment/shippingoption.py index a055bf984..95572abc4 100644 --- a/telegram/_payment/shippingoption.py +++ b/telegram/_payment/shippingoption.py @@ -17,7 +17,7 @@ # 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 ShippingOption.""" -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING, Sequence, Tuple from telegram._telegramobject import TelegramObject from telegram._utils.argumentparsing import parse_sequence_arg @@ -66,9 +66,9 @@ class ShippingOption(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) - self.id = id # pylint: disable=invalid-name - self.title = title - self.prices = parse_sequence_arg(prices) + self.id: str = id # pylint: disable=invalid-name + self.title: str = title + self.prices: Tuple["LabeledPrice", ...] = parse_sequence_arg(prices) self._id_attrs = (self.id,) diff --git a/telegram/_payment/shippingquery.py b/telegram/_payment/shippingquery.py index d7a647443..7e13f84a5 100644 --- a/telegram/_payment/shippingquery.py +++ b/telegram/_payment/shippingquery.py @@ -67,10 +67,10 @@ class ShippingQuery(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.id = id # pylint: disable=invalid-name - self.from_user = from_user - self.invoice_payload = invoice_payload - self.shipping_address = shipping_address + self.id: str = id # pylint: disable=invalid-name + self.from_user: User = from_user + self.invoice_payload: str = invoice_payload + self.shipping_address: ShippingAddress = shipping_address self._id_attrs = (self.id,) diff --git a/telegram/_payment/successfulpayment.py b/telegram/_payment/successfulpayment.py index db8363a16..8d01f8ca2 100644 --- a/telegram/_payment/successfulpayment.py +++ b/telegram/_payment/successfulpayment.py @@ -90,13 +90,13 @@ class SuccessfulPayment(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.currency = currency - self.total_amount = total_amount - self.invoice_payload = invoice_payload - self.shipping_option_id = shipping_option_id - self.order_info = order_info - self.telegram_payment_charge_id = telegram_payment_charge_id - self.provider_payment_charge_id = provider_payment_charge_id + self.currency: str = currency + self.total_amount: int = total_amount + self.invoice_payload: str = invoice_payload + self.shipping_option_id: Optional[str] = shipping_option_id + self.order_info: Optional[OrderInfo] = order_info + self.telegram_payment_charge_id: str = telegram_payment_charge_id + self.provider_payment_charge_id: str = provider_payment_charge_id self._id_attrs = (self.telegram_payment_charge_id, self.provider_payment_charge_id) diff --git a/telegram/_poll.py b/telegram/_poll.py index 3f5a8df71..f983e6acc 100644 --- a/telegram/_poll.py +++ b/telegram/_poll.py @@ -19,7 +19,7 @@ """This module contains an object that represents a Telegram Poll.""" import datetime import sys -from typing import TYPE_CHECKING, ClassVar, Dict, List, Optional, Sequence +from typing import TYPE_CHECKING, ClassVar, Dict, List, Optional, Sequence, Tuple from telegram import constants from telegram._messageentity import MessageEntity @@ -59,8 +59,8 @@ class PollOption(TelegramObject): def __init__(self, text: str, voter_count: int, *, api_kwargs: JSONDict = None): super().__init__(api_kwargs=api_kwargs) - self.text = text - self.voter_count = voter_count + self.text: str = text + self.voter_count: int = voter_count self._id_attrs = (self.text, self.voter_count) @@ -111,9 +111,9 @@ class PollAnswer(TelegramObject): self, poll_id: str, user: User, option_ids: Sequence[int], *, api_kwargs: JSONDict = None ): super().__init__(api_kwargs=api_kwargs) - self.poll_id = poll_id - self.user = user - self.option_ids = parse_sequence_arg(option_ids) + self.poll_id: str = poll_id + self.user: User = user + self.option_ids: Tuple[int, ...] = parse_sequence_arg(option_ids) self._id_attrs = (self.poll_id, self.user, tuple(self.option_ids)) @@ -244,19 +244,21 @@ class Poll(TelegramObject): api_kwargs: JSONDict = None, ): super().__init__(api_kwargs=api_kwargs) - self.id = id # pylint: disable=invalid-name - self.question = question - self.options = parse_sequence_arg(options) - self.total_voter_count = total_voter_count - self.is_closed = is_closed - self.is_anonymous = is_anonymous - self.type = enum.get_member(constants.PollType, type, type) - self.allows_multiple_answers = allows_multiple_answers - self.correct_option_id = correct_option_id - self.explanation = explanation - self.explanation_entities = parse_sequence_arg(explanation_entities) - self.open_period = open_period - self.close_date = close_date + self.id: str = id # pylint: disable=invalid-name + self.question: str = question + self.options: Tuple[PollOption, ...] = parse_sequence_arg(options) + self.total_voter_count: int = total_voter_count + self.is_closed: bool = is_closed + self.is_anonymous: bool = is_anonymous + self.type: str = enum.get_member(constants.PollType, type, type) + self.allows_multiple_answers: bool = allows_multiple_answers + self.correct_option_id: Optional[int] = correct_option_id + self.explanation: Optional[str] = explanation + self.explanation_entities: Tuple[MessageEntity, ...] = parse_sequence_arg( + explanation_entities + ) + self.open_period: Optional[int] = open_period + self.close_date: Optional[datetime.datetime] = close_date self._id_attrs = (self.id,) diff --git a/telegram/_proximityalerttriggered.py b/telegram/_proximityalerttriggered.py index 37057dfb3..c297d22b8 100644 --- a/telegram/_proximityalerttriggered.py +++ b/telegram/_proximityalerttriggered.py @@ -53,9 +53,9 @@ class ProximityAlertTriggered(TelegramObject): self, traveler: User, watcher: User, distance: int, *, api_kwargs: JSONDict = None ): super().__init__(api_kwargs=api_kwargs) - self.traveler = traveler - self.watcher = watcher - self.distance = distance + self.traveler: User = traveler + self.watcher: User = watcher + self.distance: int = distance self._id_attrs = (self.traveler, self.watcher, self.distance) diff --git a/telegram/_replykeyboardmarkup.py b/telegram/_replykeyboardmarkup.py index 0543adc9a..a2834e4e9 100644 --- a/telegram/_replykeyboardmarkup.py +++ b/telegram/_replykeyboardmarkup.py @@ -18,7 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram ReplyKeyboardMarkup.""" -from typing import ClassVar, Sequence, Union +from typing import ClassVar, Optional, Sequence, Tuple, Union from telegram import constants from telegram._keyboardbutton import KeyboardButton @@ -138,17 +138,17 @@ class ReplyKeyboardMarkup(TelegramObject): ) # Required - self.keyboard = tuple( + self.keyboard: Tuple[Tuple[KeyboardButton, ...], ...] = tuple( tuple(KeyboardButton(button) if isinstance(button, str) else button for button in row) for row in keyboard ) # Optionals - self.resize_keyboard = resize_keyboard - self.one_time_keyboard = one_time_keyboard - self.selective = selective - self.input_field_placeholder = input_field_placeholder - self.is_persistent = is_persistent + self.resize_keyboard: Optional[bool] = resize_keyboard + self.one_time_keyboard: Optional[bool] = one_time_keyboard + self.selective: Optional[bool] = selective + self.input_field_placeholder: Optional[str] = input_field_placeholder + self.is_persistent: Optional[bool] = is_persistent self._id_attrs = (self.keyboard,) diff --git a/telegram/_replykeyboardremove.py b/telegram/_replykeyboardremove.py index 21cb53205..1508ac39a 100644 --- a/telegram/_replykeyboardremove.py +++ b/telegram/_replykeyboardremove.py @@ -17,6 +17,7 @@ # 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 ReplyKeyboardRemove.""" +from typing import Optional from telegram._telegramobject import TelegramObject from telegram._utils.types import JSONDict @@ -64,8 +65,8 @@ class ReplyKeyboardRemove(TelegramObject): def __init__(self, selective: bool = None, *, api_kwargs: JSONDict = None): super().__init__(api_kwargs=api_kwargs) # Required - self.remove_keyboard = True + self.remove_keyboard: bool = True # Optionals - self.selective = selective + self.selective: Optional[bool] = selective self._freeze() diff --git a/telegram/_sentwebappmessage.py b/telegram/_sentwebappmessage.py index cd4875e77..409818bbc 100644 --- a/telegram/_sentwebappmessage.py +++ b/telegram/_sentwebappmessage.py @@ -17,6 +17,7 @@ # 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 Sent Web App Message.""" +from typing import Optional from telegram._telegramobject import TelegramObject from telegram._utils.types import JSONDict @@ -46,7 +47,7 @@ class SentWebAppMessage(TelegramObject): def __init__(self, inline_message_id: str = None, *, api_kwargs: JSONDict = None): super().__init__(api_kwargs=api_kwargs) # Optionals - self.inline_message_id = inline_message_id + self.inline_message_id: Optional[str] = inline_message_id self._id_attrs = (self.inline_message_id,) diff --git a/telegram/_telegramobject.py b/telegram/_telegramobject.py index b9ca06e17..afcf15642 100644 --- a/telegram/_telegramobject.py +++ b/telegram/_telegramobject.py @@ -38,6 +38,7 @@ from typing import ( Type, TypeVar, Union, + cast, ) from telegram._utils.datetime import to_timestamp @@ -259,7 +260,7 @@ class TelegramObject: out["api_kwargs"] = dict(self.api_kwargs) return out - def __setstate__(self, state: dict) -> None: + def __setstate__(self, state: Dict[str, object]) -> None: """ Overrides :meth:`object.__setstate__` to customize the unpickling process of objects of this type. Modifies the object in-place. @@ -283,7 +284,7 @@ class TelegramObject: setattr(self, "_bot", None) # get api_kwargs first because we may need to add entries to it (see try-except below) - api_kwargs = state.pop("api_kwargs", {}) + api_kwargs = cast(Dict[str, object], state.pop("api_kwargs", {})) # get _frozen before the loop to avoid setting it to True in the loop frozen = state.pop("_frozen", False) @@ -307,7 +308,7 @@ class TelegramObject: if frozen: self._freeze() - def __deepcopy__(self: Tele_co, memodict: dict) -> Tele_co: + def __deepcopy__(self: Tele_co, memodict: Dict[int, object]) -> Tele_co: """ Customizes how :func:`copy.deepcopy` processes objects of this type. The only difference to the default implementation is that the :class:`telegram.Bot` diff --git a/telegram/_update.py b/telegram/_update.py index 4d7fd1a6d..143986fa6 100644 --- a/telegram/_update.py +++ b/telegram/_update.py @@ -254,22 +254,22 @@ class Update(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.update_id = update_id + self.update_id: int = update_id # Optionals - self.message = message - self.edited_message = edited_message - self.inline_query = inline_query - self.chosen_inline_result = chosen_inline_result - self.callback_query = callback_query - self.shipping_query = shipping_query - self.pre_checkout_query = pre_checkout_query - self.channel_post = channel_post - self.edited_channel_post = edited_channel_post - self.poll = poll - self.poll_answer = poll_answer - self.my_chat_member = my_chat_member - self.chat_member = chat_member - self.chat_join_request = chat_join_request + self.message: Optional[Message] = message + self.edited_message: Optional[Message] = edited_message + self.inline_query: Optional[InlineQuery] = inline_query + self.chosen_inline_result: Optional[ChosenInlineResult] = chosen_inline_result + self.callback_query: Optional[CallbackQuery] = callback_query + self.shipping_query: Optional[ShippingQuery] = shipping_query + self.pre_checkout_query: Optional[PreCheckoutQuery] = pre_checkout_query + self.channel_post: Optional[Message] = channel_post + self.edited_channel_post: Optional[Message] = edited_channel_post + self.poll: Optional[Poll] = poll + self.poll_answer: Optional[PollAnswer] = poll_answer + self.my_chat_member: Optional[ChatMemberUpdated] = my_chat_member + self.chat_member: Optional[ChatMemberUpdated] = chat_member + self.chat_join_request: Optional[ChatJoinRequest] = chat_join_request self._effective_user: Optional["User"] = None self._effective_chat: Optional["Chat"] = None diff --git a/telegram/_user.py b/telegram/_user.py index fa197c2d2..780977c30 100644 --- a/telegram/_user.py +++ b/telegram/_user.py @@ -143,18 +143,18 @@ class User(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.id = id # pylint: disable=invalid-name - self.first_name = first_name - self.is_bot = is_bot + self.id: int = id # pylint: disable=invalid-name + self.first_name: str = first_name + self.is_bot: bool = is_bot # Optionals - self.last_name = last_name - self.username = username - self.language_code = language_code - self.can_join_groups = can_join_groups - self.can_read_all_group_messages = can_read_all_group_messages - self.supports_inline_queries = supports_inline_queries - self.is_premium = is_premium - self.added_to_attachment_menu = added_to_attachment_menu + self.last_name: Optional[str] = last_name + self.username: Optional[str] = username + self.language_code: Optional[str] = language_code + self.can_join_groups: Optional[bool] = can_join_groups + self.can_read_all_group_messages: Optional[bool] = can_read_all_group_messages + self.supports_inline_queries: Optional[bool] = supports_inline_queries + self.is_premium: Optional[bool] = is_premium + self.added_to_attachment_menu: Optional[bool] = added_to_attachment_menu self._id_attrs = (self.id,) diff --git a/telegram/_userprofilephotos.py b/telegram/_userprofilephotos.py index 76906c92c..6c1a519ec 100644 --- a/telegram/_userprofilephotos.py +++ b/telegram/_userprofilephotos.py @@ -17,7 +17,7 @@ # 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 UserProfilePhotos.""" -from typing import TYPE_CHECKING, Optional, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._files.photosize import PhotoSize from telegram._telegramobject import TelegramObject @@ -62,8 +62,8 @@ class UserProfilePhotos(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.total_count = total_count - self.photos = tuple(tuple(sizes) for sizes in photos) + self.total_count: int = total_count + self.photos: Tuple[Tuple[PhotoSize, ...], ...] = tuple(tuple(sizes) for sizes in photos) self._id_attrs = (self.total_count, self.photos) diff --git a/telegram/_utils/defaultvalue.py b/telegram/_utils/defaultvalue.py index d4460be6a..6a8084ff1 100644 --- a/telegram/_utils/defaultvalue.py +++ b/telegram/_utils/defaultvalue.py @@ -82,8 +82,8 @@ class DefaultValue(Generic[DVType]): __slots__ = ("value",) - def __init__(self, value: DVType = None): - self.value = value + def __init__(self, value: DVType): + self.value: DVType = value def __bool__(self) -> bool: return bool(self.value) @@ -110,7 +110,7 @@ class DefaultValue(Generic[DVType]): Returns: Same type as input, or the value of the input: The value """ - return obj.value if isinstance(obj, DefaultValue) else obj # type: ignore[return-value] + return obj.value if isinstance(obj, DefaultValue) else obj # This is mostly here for readability during debugging def __str__(self) -> str: @@ -121,17 +121,17 @@ class DefaultValue(Generic[DVType]): return repr(self.value) -DEFAULT_NONE: DefaultValue = DefaultValue(None) +DEFAULT_NONE: DefaultValue[None] = DefaultValue(None) """:class:`DefaultValue`: Default :obj:`None`""" -DEFAULT_FALSE: DefaultValue = DefaultValue(False) +DEFAULT_FALSE: DefaultValue[bool] = DefaultValue(False) """:class:`DefaultValue`: Default :obj:`False`""" -DEFAULT_TRUE: DefaultValue = DefaultValue(True) +DEFAULT_TRUE: DefaultValue[bool] = DefaultValue(True) """:class:`DefaultValue`: Default :obj:`True` .. versionadded:: 20.0 """ -DEFAULT_20: DefaultValue = DefaultValue(20) +DEFAULT_20: DefaultValue[int] = DefaultValue(20) """:class:`DefaultValue`: Default :obj:`20`""" diff --git a/telegram/_utils/types.py b/telegram/_utils/types.py index f629b349d..d7310fd77 100644 --- a/telegram/_utils/types.py +++ b/telegram/_utils/types.py @@ -49,13 +49,15 @@ a local file path as string, :class:`pathlib.Path` or the file contents as :obj: JSONDict = Dict[str, Any] """Dictionary containing response from Telegram or data to send to the API.""" -DVType = TypeVar("DVType") # pylint: disable=invalid-name -ODVInput = Optional[Union["DefaultValue[DVType]", DVType]] +DVValueType = TypeVar("DVValueType") # pylint: disable=invalid-name +DVType = Union[DVValueType, "DefaultValue[DVValueType]"] +"""Generic type for a variable which can be either `type` or `DefaultVaule[type]`.""" +ODVInput = Optional[Union["DefaultValue[DVValueType]", DVValueType, "DefaultValue[None]"]] """Generic type for bot method parameters which can have defaults. ``ODVInput[type]`` is the same -as ``Optional[Union[DefaultValue, type]]``.""" -DVInput = Union["DefaultValue[DVType]", DVType] +as ``Optional[Union[DefaultValue[type], type, DefaultValue[None]]``.""" +DVInput = Union["DefaultValue[DVValueType]", DVValueType, "DefaultValue[None]"] """Generic type for bot method parameters which can have defaults. ``DVInput[type]`` is the same -as ``Union[DefaultValue, type]``.""" +as ``Union[DefaultValue[type], type, DefaultValue[None]]``.""" RT = TypeVar("RT") SCT = Union[RT, Collection[RT]] diff --git a/telegram/_videochat.py b/telegram/_videochat.py index 9d50c1469..a025809e5 100644 --- a/telegram/_videochat.py +++ b/telegram/_videochat.py @@ -18,7 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains objects related to Telegram video chats.""" import datetime as dtm -from typing import TYPE_CHECKING, Optional, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._telegramobject import TelegramObject from telegram._user import User @@ -78,7 +78,7 @@ class VideoChatEnded(TelegramObject): api_kwargs: JSONDict = None, ) -> None: super().__init__(api_kwargs=api_kwargs) - self.duration = duration + self.duration: int = duration self._id_attrs = (self.duration,) self._freeze() @@ -118,7 +118,7 @@ class VideoChatParticipantsInvited(TelegramObject): api_kwargs: JSONDict = None, ) -> None: super().__init__(api_kwargs=api_kwargs) - self.users = parse_sequence_arg(users) + self.users: Tuple[User, ...] = parse_sequence_arg(users) self._id_attrs = (self.users,) self._freeze() @@ -164,7 +164,7 @@ class VideoChatScheduled(TelegramObject): api_kwargs: JSONDict = None, ) -> None: super().__init__(api_kwargs=api_kwargs) - self.start_date = start_date + self.start_date: dtm.datetime = start_date self._id_attrs = (self.start_date,) diff --git a/telegram/_webappdata.py b/telegram/_webappdata.py index 0c6a2e1b3..c40e0ba3d 100644 --- a/telegram/_webappdata.py +++ b/telegram/_webappdata.py @@ -54,8 +54,8 @@ class WebAppData(TelegramObject): def __init__(self, data: str, button_text: str, *, api_kwargs: JSONDict = None): super().__init__(api_kwargs=api_kwargs) # Required - self.data = data - self.button_text = button_text + self.data: str = data + self.button_text: str = button_text self._id_attrs = (self.data, self.button_text) diff --git a/telegram/_webappinfo.py b/telegram/_webappinfo.py index b721c4d19..bef08a748 100644 --- a/telegram/_webappinfo.py +++ b/telegram/_webappinfo.py @@ -50,7 +50,7 @@ class WebAppInfo(TelegramObject): def __init__(self, url: str, *, api_kwargs: JSONDict = None): super().__init__(api_kwargs=api_kwargs) # Required - self.url = url + self.url: str = url self._id_attrs = (self.url,) diff --git a/telegram/_webhookinfo.py b/telegram/_webhookinfo.py index e1e316fb0..2b444c410 100644 --- a/telegram/_webhookinfo.py +++ b/telegram/_webhookinfo.py @@ -17,7 +17,7 @@ # 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 WebhookInfo.""" -from typing import TYPE_CHECKING, Optional, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._telegramobject import TelegramObject from telegram._utils.argumentparsing import parse_sequence_arg @@ -120,17 +120,17 @@ class WebhookInfo(TelegramObject): ): super().__init__(api_kwargs=api_kwargs) # Required - self.url = url - self.has_custom_certificate = has_custom_certificate - self.pending_update_count = pending_update_count + self.url: str = url + self.has_custom_certificate: bool = has_custom_certificate + self.pending_update_count: int = pending_update_count # Optional - self.ip_address = ip_address - self.last_error_date = last_error_date - self.last_error_message = last_error_message - self.max_connections = max_connections - self.allowed_updates = parse_sequence_arg(allowed_updates) - self.last_synchronization_error_date = last_synchronization_error_date + self.ip_address: Optional[str] = ip_address + self.last_error_date: Optional[int] = last_error_date + self.last_error_message: Optional[str] = last_error_message + self.max_connections: Optional[int] = max_connections + self.allowed_updates: Tuple[str, ...] = parse_sequence_arg(allowed_updates) + self.last_synchronization_error_date: Optional[int] = last_synchronization_error_date self._id_attrs = ( self.url, diff --git a/telegram/error.py b/telegram/error.py index 6702e0a17..717ac05d3 100644 --- a/telegram/error.py +++ b/telegram/error.py @@ -73,7 +73,7 @@ class TelegramError(Exception): if msg != message: # api_error - capitalize the msg... msg = msg.capitalize() - self.message = msg + self.message: str = msg def __str__(self) -> str: return self.message @@ -163,7 +163,7 @@ class ChatMigrated(TelegramError): def __init__(self, new_chat_id: int): super().__init__(f"Group migrated to supergroup. New chat id: {new_chat_id}") - self.new_chat_id = new_chat_id + self.new_chat_id: int = new_chat_id def __reduce__(self) -> Tuple[type, Tuple[int]]: # type: ignore[override] return self.__class__, (self.new_chat_id,) @@ -188,7 +188,7 @@ class RetryAfter(TelegramError): def __init__(self, retry_after: int): super().__init__(f"Flood control exceeded. Retry in {retry_after} seconds") - self.retry_after = retry_after + self.retry_after: int = retry_after def __reduce__(self) -> Tuple[type, Tuple[float]]: # type: ignore[override] return self.__class__, (self.retry_after,) diff --git a/telegram/ext/_aioratelimiter.py b/telegram/ext/_aioratelimiter.py index 9d27dc3f2..64251ef1a 100644 --- a/telegram/ext/_aioratelimiter.py +++ b/telegram/ext/_aioratelimiter.py @@ -144,14 +144,14 @@ class AIORateLimiter(BaseRateLimiter[int]): self._base_limiter = None if group_max_rate and group_time_period: - self._group_max_rate = group_max_rate - self._group_time_period = group_time_period + self._group_max_rate: float = group_max_rate + self._group_time_period: float = group_time_period else: self._group_max_rate = 0 self._group_time_period = 0 self._group_limiters: Dict[Union[str, int], AsyncLimiter] = {} - self._max_retries = max_retries + self._max_retries: int = max_retries self._logger = logging.getLogger(__name__) self._retry_after_event = asyncio.Event() self._retry_after_event.set() diff --git a/telegram/ext/_application.py b/telegram/ext/_application.py index 07056003a..6b73aea94 100644 --- a/telegram/ext/_application.py +++ b/telegram/ext/_application.py @@ -24,13 +24,13 @@ import logging import platform import signal from collections import defaultdict -from contextlib import AbstractAsyncContextManager from copy import deepcopy from pathlib import Path from types import MappingProxyType, TracebackType from typing import ( TYPE_CHECKING, Any, + AsyncContextManager, Callable, Coroutine, DefaultDict, @@ -50,7 +50,7 @@ from typing import ( from telegram._update import Update from telegram._utils.defaultvalue import DEFAULT_NONE, DEFAULT_TRUE, DefaultValue -from telegram._utils.types import DVInput, ODVInput +from telegram._utils.types import DVType, ODVInput from telegram._utils.warnings import warn from telegram.error import TelegramError from telegram.ext._basepersistence import BasePersistence @@ -60,7 +60,7 @@ from telegram.ext._handler import BaseHandler from telegram.ext._updater import Updater from telegram.ext._utils.stack import was_called_by from telegram.ext._utils.trackingdict import TrackingDict -from telegram.ext._utils.types import BD, BT, CCT, CD, JQ, UD, ConversationKey, HandlerCallback +from telegram.ext._utils.types import BD, BT, CCT, CD, JQ, RT, UD, ConversationKey, HandlerCallback if TYPE_CHECKING: from telegram import Message @@ -105,10 +105,10 @@ class ApplicationHandlerStop(Exception): def __init__(self, state: object = None) -> None: super().__init__() - self.state = state + self.state: Optional[object] = state -class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager): +class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Application"]): """This class dispatches all kinds of updates to its registered handlers, and is the entry point to a PTB application. @@ -212,14 +212,16 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager) """ - # Allowing '__weakref__' creation here since we need it for the JobQueue __slots__ = ( "__create_task_tasks", "__update_fetcher_task", "__update_persistence_event", "__update_persistence_lock", "__update_persistence_task", - "__weakref__", + # Allowing '__weakref__' creation here since we need it for the JobQueue + # Uncomment if necessary - currently the __weakref__ slot is already created + # in the AsyncContextManager base class + # "__weakref__", "_chat_data", "_chat_ids_to_be_deleted_in_persistence", "_chat_ids_to_be_updated_in_persistence", @@ -251,11 +253,11 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager) self: "Application[BT, CCT, UD, CD, BD, JQ]", *, bot: BT, - update_queue: asyncio.Queue, + update_queue: "asyncio.Queue[object]", updater: Optional[Updater], job_queue: JQ, concurrent_updates: Union[bool, int], - persistence: Optional[BasePersistence], + persistence: Optional[BasePersistence[UD, CD, BD]], context_types: ContextTypes[CCT, UD, CD, BD], post_init: Optional[ Callable[["Application[BT, CCT, UD, CD, BD, JQ]"], Coroutine[Any, Any, None]] @@ -275,15 +277,23 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager) stacklevel=2, ) - self.bot = bot - self.update_queue = update_queue - self.context_types = context_types - self.updater = updater - self.handlers: Dict[int, List[BaseHandler]] = {} - self.error_handlers: Dict[Callable, Union[bool, DefaultValue]] = {} - self.post_init = post_init - self.post_shutdown = post_shutdown - self.post_stop = post_stop + self.bot: BT = bot + self.update_queue: "asyncio.Queue[object]" = update_queue + self.context_types: ContextTypes[CCT, UD, CD, BD] = context_types + self.updater: Optional[Updater] = updater + self.handlers: Dict[int, List[BaseHandler[Any, CCT]]] = {} + self.error_handlers: Dict[ + HandlerCallback[object, CCT, None], Union[bool, DefaultValue[bool]] + ] = {} + self.post_init: Optional[ + Callable[["Application[BT, CCT, UD, CD, BD, JQ]"], Coroutine[Any, Any, None]] + ] = post_init + self.post_shutdown: Optional[ + Callable[["Application[BT, CCT, UD, CD, BD, JQ]"], Coroutine[Any, Any, None]] + ] = post_shutdown + self.post_stop: Optional[ + Callable[["Application[BT, CCT, UD, CD, BD, JQ]"], Coroutine[Any, Any, None]] + ] = post_stop if isinstance(concurrent_updates, int) and concurrent_updates < 0: raise ValueError("`concurrent_updates` must be a non-negative integer!") @@ -292,14 +302,14 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager) self._concurrent_updates_sem = asyncio.BoundedSemaphore(concurrent_updates or 1) self._concurrent_updates: int = concurrent_updates or 0 - self.bot_data = self.context_types.bot_data() + self.bot_data: BD = self.context_types.bot_data() self._user_data: DefaultDict[int, UD] = defaultdict(self.context_types.user_data) self._chat_data: DefaultDict[int, CD] = defaultdict(self.context_types.chat_data) # Read only mapping self.user_data: Mapping[int, UD] = MappingProxyType(self._user_data) self.chat_data: Mapping[int, CD] = MappingProxyType(self._chat_data) - self.persistence: Optional[BasePersistence] = None + self.persistence: Optional[BasePersistence[UD, CD, BD]] = None if persistence and not isinstance(persistence, BasePersistence): raise TypeError("persistence must be based on telegram.ext.BasePersistence") self.persistence = persistence @@ -319,7 +329,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager) # A number of low-level helpers for the internal logic self._initialized = False self._running = False - self._job_queue = job_queue + self._job_queue: JQ = job_queue self.__update_fetcher_task: Optional[asyncio.Task] = None self.__update_persistence_task: Optional[asyncio.Task] = None self.__update_persistence_event = asyncio.Event() @@ -351,7 +361,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager) return self._concurrent_updates @property - def job_queue(self) -> Optional["JobQueue"]: + def job_queue(self) -> Optional["JobQueue[CCT]"]: """ :class:`telegram.ext.JobQueue`: The :class:`JobQueue` used by the :class:`telegram.ext.Application`. @@ -922,7 +932,9 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager) if close_loop: loop.close() - def create_task(self, coroutine: Coroutine, update: object = None) -> asyncio.Task: + def create_task( + self, coroutine: Coroutine[Any, Any, RT], update: object = None + ) -> "asyncio.Task[RT]": """Thin wrapper around :func:`asyncio.create_task` that handles exceptions raised by the :paramref:`coroutine` with :meth:`process_error`. @@ -1173,10 +1185,10 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager) def add_handlers( self, handlers: Union[ - Union[List[BaseHandler], Tuple[BaseHandler]], - Dict[int, Union[List[BaseHandler], Tuple[BaseHandler]]], + Union[List[BaseHandler[Any, CCT]], Tuple[BaseHandler[Any, CCT]]], + Dict[int, Union[List[BaseHandler[Any, CCT]], Tuple[BaseHandler[Any, CCT]]]], ], - group: DVInput[int] = DefaultValue(0), + group: Union[int, DefaultValue[int]] = DefaultValue(0), ) -> None: """Registers multiple handlers at once. The order of the handlers in the passed sequence(s) matters. See :meth:`add_handler` for details. @@ -1220,7 +1232,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager) "dictionary where the keys are groups and values are sequences of handlers." ) - def remove_handler(self, handler: BaseHandler, group: int = DEFAULT_GROUP) -> None: + def remove_handler(self, handler: BaseHandler[Any, CCT], group: int = DEFAULT_GROUP) -> None: """Remove a handler from the specified group. Args: @@ -1503,7 +1515,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager) def add_error_handler( self, callback: HandlerCallback[object, CCT, None], - block: DVInput[bool] = DEFAULT_TRUE, + block: DVType[bool] = DEFAULT_TRUE, ) -> None: """Registers an error handler in the Application. This handler will receive every error which happens in your bot. See the docs of :meth:`process_error` for more details on how @@ -1535,7 +1547,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager) self.error_handlers[callback] = block - def remove_error_handler(self, callback: Callable[[object, CCT], None]) -> None: + def remove_error_handler(self, callback: HandlerCallback[object, CCT, None]) -> None: """Removes an error handler. Args: @@ -1548,8 +1560,8 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager) self, update: Optional[object], error: Exception, - job: "Job" = None, - coroutine: Coroutine = None, + job: "Job[CCT]" = None, + coroutine: Coroutine[Any, Any, Any] = None, ) -> bool: """Processes an error by passing it to all error handlers registered with :meth:`add_error_handler`. If one of the error handlers raises diff --git a/telegram/ext/_applicationbuilder.py b/telegram/ext/_applicationbuilder.py index 998e9428d..e3ecf1c45 100644 --- a/telegram/ext/_applicationbuilder.py +++ b/telegram/ext/_applicationbuilder.py @@ -34,7 +34,7 @@ from typing import ( from telegram._bot import Bot from telegram._utils.defaultvalue import DEFAULT_FALSE, DEFAULT_NONE, DefaultValue -from telegram._utils.types import DVInput, FilePathInput, ODVInput +from telegram._utils.types import DVInput, DVType, FilePathInput, ODVInput from telegram.ext._application import Application from telegram.ext._contexttypes import ContextTypes from telegram.ext._extbot import ExtBot @@ -161,9 +161,9 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]): ) def __init__(self: "InitApplicationBuilder"): - self._token: DVInput[str] = DefaultValue("") - self._base_url: DVInput[str] = DefaultValue("https://api.telegram.org/bot") - self._base_file_url: DVInput[str] = DefaultValue("https://api.telegram.org/file/bot") + self._token: DVType[str] = DefaultValue("") + self._base_url: DVType[str] = DefaultValue("https://api.telegram.org/bot") + self._base_file_url: DVType[str] = DefaultValue("https://api.telegram.org/file/bot") self._connection_pool_size: DVInput[int] = DEFAULT_NONE self._proxy_url: DVInput[str] = DEFAULT_NONE self._connect_timeout: ODVInput[float] = DEFAULT_NONE @@ -182,10 +182,10 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]): self._private_key: ODVInput[bytes] = DEFAULT_NONE self._private_key_password: ODVInput[bytes] = DEFAULT_NONE self._defaults: ODVInput["Defaults"] = DEFAULT_NONE - self._arbitrary_callback_data: DVInput[Union[bool, int]] = DEFAULT_FALSE - self._local_mode: DVInput[bool] = DEFAULT_FALSE + self._arbitrary_callback_data: Union[DefaultValue[bool], int] = DEFAULT_FALSE + self._local_mode: DVType[bool] = DEFAULT_FALSE self._bot: DVInput[Bot] = DEFAULT_NONE - self._update_queue: DVInput[Queue] = DefaultValue(Queue()) + self._update_queue: DVType[Queue] = DefaultValue(Queue()) try: self._job_queue: ODVInput["JobQueue"] = DefaultValue(JobQueue()) @@ -195,10 +195,10 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]): self._job_queue = DEFAULT_NONE self._persistence: ODVInput["BasePersistence"] = DEFAULT_NONE - self._context_types: DVInput[ContextTypes] = DefaultValue(ContextTypes()) - self._application_class: DVInput[Type[Application]] = DefaultValue(Application) + self._context_types: DVType[ContextTypes] = DefaultValue(ContextTypes()) + self._application_class: DVType[Type[Application]] = DefaultValue(Application) self._application_kwargs: Dict[str, object] = {} - self._concurrent_updates: DVInput[Union[int, bool]] = DEFAULT_FALSE + self._concurrent_updates: Union[int, DefaultValue[bool]] = DEFAULT_FALSE self._updater: ODVInput[Updater] = DEFAULT_NONE self._post_init: Optional[Callable[[Application], Coroutine[Any, Any, None]]] = None self._post_shutdown: Optional[Callable[[Application], Coroutine[Any, Any, None]]] = None @@ -317,7 +317,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]): ) if job_queue is not None: - job_queue.set_application(application) + job_queue.set_application(application) # type: ignore[arg-type] if persistence is not None: # This raises an exception if persistence.store_data.callback_data is True @@ -327,7 +327,9 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]): return application def application_class( - self: BuilderType, application_class: Type[Application], kwargs: Dict[str, object] = None + self: BuilderType, + application_class: Type[Application[Any, Any, Any, Any, Any, Any]], + kwargs: Dict[str, object] = None, ) -> BuilderType: """Sets a custom subclass instead of :class:`telegram.ext.Application`. The subclass's ``__init__`` should look like this @@ -847,7 +849,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]): self._bot = bot return self # type: ignore[return-value] - def update_queue(self: BuilderType, update_queue: Queue) -> BuilderType: + def update_queue(self: BuilderType, update_queue: "Queue[object]") -> BuilderType: """Sets a :class:`asyncio.Queue` instance for :attr:`telegram.ext.Application.update_queue`, i.e. the queue that the application will fetch updates from. Will also be used for the :attr:`telegram.ext.Application.updater`. @@ -923,7 +925,9 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]): self._job_queue = job_queue return self # type: ignore[return-value] - def persistence(self: BuilderType, persistence: "BasePersistence") -> BuilderType: + def persistence( + self: BuilderType, persistence: "BasePersistence[Any, Any, Any]" + ) -> BuilderType: """Sets a :class:`telegram.ext.BasePersistence` instance for :attr:`telegram.ext.Application.persistence`. @@ -1143,9 +1147,9 @@ InitApplicationBuilder = ( # This is defined all the way down here so that its ApplicationBuilder[ # by Pylance correctly. ExtBot[None], ContextTypes.DEFAULT_TYPE, - Dict, - Dict, - Dict, - JobQueue, + Dict[Any, Any], + Dict[Any, Any], + Dict[Any, Any], + JobQueue[ContextTypes.DEFAULT_TYPE], ] ) diff --git a/telegram/ext/_basepersistence.py b/telegram/ext/_basepersistence.py index 1f15a5dd8..fac198d3d 100644 --- a/telegram/ext/_basepersistence.py +++ b/telegram/ext/_basepersistence.py @@ -148,8 +148,8 @@ class BasePersistence(Generic[UD, CD, BD], ABC): store_data: PersistenceInput = None, update_interval: float = 60, ): - self.store_data = store_data or PersistenceInput() - self._update_interval = update_interval + self.store_data: PersistenceInput = store_data or PersistenceInput() + self._update_interval: float = update_interval self.bot: Bot = None # type: ignore[assignment] diff --git a/telegram/ext/_callbackcontext.py b/telegram/ext/_callbackcontext.py index 1fb1c6308..682196f3a 100644 --- a/telegram/ext/_callbackcontext.py +++ b/telegram/ext/_callbackcontext.py @@ -17,7 +17,18 @@ # 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 typing import TYPE_CHECKING, Coroutine, Dict, Generic, List, Match, NoReturn, Optional, Type +from typing import ( + TYPE_CHECKING, + Any, + Coroutine, + Dict, + Generic, + List, + Match, + NoReturn, + Optional, + Type, +) from telegram._callbackquery import CallbackQuery from telegram._update import Update @@ -27,7 +38,6 @@ from telegram.ext._utils.types import BD, BT, CD, UD if TYPE_CHECKING: from asyncio import Queue - from typing import Any from telegram.ext import Application, Job, JobQueue # noqa: F401 from telegram.ext._utils.types import CCT @@ -126,14 +136,14 @@ class CallbackContext(Generic[BT, UD, CD, BD]): chat_id: int = None, user_id: int = None, ): - self._application = application - self._chat_id = chat_id - self._user_id = user_id + self._application: Application[BT, CCT, UD, CD, BD, Any] = application + self._chat_id: Optional[int] = chat_id + self._user_id: Optional[int] = user_id self.args: Optional[List[str]] = None - self.matches: Optional[List[Match]] = None + self.matches: Optional[List[Match[str]]] = None self.error: Optional[Exception] = None - self.job: Optional["Job"] = None - self.coroutine: Optional[Coroutine] = None + self.job: Optional["Job[CCT]"] = None + self.coroutine: Optional[Coroutine[Any, Any, Any]] = None @property def application(self) -> "Application[BT, CCT, UD, CD, BD, Any]": @@ -222,11 +232,11 @@ class CallbackContext(Generic[BT, UD, CD, BD]): await self.application.persistence.refresh_bot_data(self.bot_data) if self.application.persistence.store_data.chat_data and self._chat_id is not None: await self.application.persistence.refresh_chat_data( - chat_id=self._chat_id, chat_data=self.chat_data + chat_id=self._chat_id, chat_data=self.chat_data # type: ignore[arg-type] ) if self.application.persistence.store_data.user_data and self._user_id is not None: await self.application.persistence.refresh_user_data( - user_id=self._user_id, user_data=self.user_data + user_id=self._user_id, user_data=self.user_data # type: ignore[arg-type] ) def drop_callback_data(self, callback_query: CallbackQuery) -> None: @@ -264,8 +274,8 @@ class CallbackContext(Generic[BT, UD, CD, BD]): update: object, error: Exception, application: "Application[BT, CCT, UD, CD, BD, Any]", - job: "Job" = None, - coroutine: Coroutine = None, + job: "Job[Any]" = None, + coroutine: Coroutine[Any, Any, Any] = None, ) -> "CCT": """ Constructs an instance of :class:`telegram.ext.CallbackContext` to be passed to the error @@ -340,7 +350,7 @@ class CallbackContext(Generic[BT, UD, CD, BD]): @classmethod def from_job( cls: Type["CCT"], - job: "Job", + job: "Job[CCT]", application: "Application[Any, CCT, Any, Any, Any, Any]", ) -> "CCT": """ @@ -376,7 +386,7 @@ class CallbackContext(Generic[BT, UD, CD, BD]): return self._application.bot @property - def job_queue(self) -> Optional["JobQueue"]: + def job_queue(self) -> Optional["JobQueue[CCT]"]: """ :class:`telegram.ext.JobQueue`: The :class:`JobQueue` used by the :class:`telegram.ext.Application`. diff --git a/telegram/ext/_callbackdatacache.py b/telegram/ext/_callbackdatacache.py index 59e9f2256..5fc854324 100644 --- a/telegram/ext/_callbackdatacache.py +++ b/telegram/ext/_callbackdatacache.py @@ -17,10 +17,9 @@ # 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 CallbackDataCache class.""" -import logging import time from datetime import datetime -from typing import TYPE_CHECKING, Dict, MutableMapping, Optional, Tuple, Union, cast +from typing import TYPE_CHECKING, Any, Dict, MutableMapping, Optional, Tuple, Union, cast from uuid import uuid4 try: @@ -67,7 +66,7 @@ class InvalidCallbackData(TelegramError): "The object belonging to this callback_data was deleted or the callback_data was " "manipulated." ) - self.callback_data = callback_data + self.callback_data: Optional[str] = callback_data def __reduce__(self) -> Tuple[type, Tuple[Optional[str]]]: # type: ignore[override] return self.__class__, (self.callback_data,) @@ -141,11 +140,11 @@ class CallbackDataCache: """ - __slots__ = ("bot", "_maxsize", "_keyboard_data", "_callback_queries", "logger") + __slots__ = ("bot", "_maxsize", "_keyboard_data", "_callback_queries") def __init__( self, - bot: "ExtBot", + bot: "ExtBot[Any]", maxsize: int = 1024, persistent_data: CDCData = None, ): @@ -155,10 +154,8 @@ class CallbackDataCache: "python-telegram-bot[callback-data]`." ) - self.logger = logging.getLogger(__name__) - - self.bot = bot - self._maxsize = maxsize + self.bot: ExtBot[Any] = bot + self._maxsize: int = maxsize self._keyboard_data: MutableMapping[str, _KeyboardData] = LRUCache(maxsize=maxsize) self._callback_queries: MutableMapping[str, str] = LRUCache(maxsize=maxsize) diff --git a/telegram/ext/_callbackqueryhandler.py b/telegram/ext/_callbackqueryhandler.py index 76a950eb2..93fb18601 100644 --- a/telegram/ext/_callbackqueryhandler.py +++ b/telegram/ext/_callbackqueryhandler.py @@ -19,11 +19,11 @@ """This module contains the CallbackQueryHandler class.""" import asyncio import re -from typing import TYPE_CHECKING, Callable, Match, Optional, Pattern, TypeVar, Union, cast +from typing import TYPE_CHECKING, Any, Callable, Match, Optional, Pattern, TypeVar, Union, cast from telegram import Update from telegram._utils.defaultvalue import DEFAULT_TRUE -from telegram._utils.types import DVInput +from telegram._utils.types import DVType from telegram.ext._handler import BaseHandler from telegram.ext._utils.types import CCT, HandlerCallback @@ -109,8 +109,8 @@ class CallbackQueryHandler(BaseHandler[Update, CCT]): def __init__( self, callback: HandlerCallback[Update, CCT, RT], - pattern: Union[str, Pattern, type, Callable[[object], Optional[bool]]] = None, - block: DVInput[bool] = DEFAULT_TRUE, + pattern: Union[str, Pattern[str], type, Callable[[object], Optional[bool]]] = None, + block: DVType[bool] = DEFAULT_TRUE, ): super().__init__(callback, block=block) @@ -122,7 +122,9 @@ class CallbackQueryHandler(BaseHandler[Update, CCT]): if isinstance(pattern, str): pattern = re.compile(pattern) - self.pattern = pattern + self.pattern: Optional[ + Union[str, Pattern[str], type, Callable[[object], Optional[bool]]] + ] = pattern def check_update(self, update: object) -> Optional[Union[bool, object]]: """Determines whether an update should be passed to this handler's :attr:`callback`. @@ -157,8 +159,8 @@ class CallbackQueryHandler(BaseHandler[Update, CCT]): self, context: CCT, update: Update, # skipcq: BAN-B301 - application: "Application", # skipcq: BAN-B301 - check_result: Union[bool, Match], + application: "Application[Any, CCT, Any, Any, Any, Any]", # skipcq: BAN-B301 + check_result: Union[bool, Match[str]], ) -> None: """Add the result of ``re.match(pattern, update.callback_query.data)`` to :attr:`CallbackContext.matches` as list with one element. diff --git a/telegram/ext/_chatjoinrequesthandler.py b/telegram/ext/_chatjoinrequesthandler.py index 015117a99..2204136af 100644 --- a/telegram/ext/_chatjoinrequesthandler.py +++ b/telegram/ext/_chatjoinrequesthandler.py @@ -22,7 +22,7 @@ from typing import FrozenSet, Optional from telegram import Update from telegram._utils.defaultvalue import DEFAULT_TRUE -from telegram._utils.types import RT, SCT, DVInput +from telegram._utils.types import RT, SCT, DVType from telegram.ext._handler import BaseHandler from telegram.ext._utils.types import CCT, HandlerCallback @@ -84,7 +84,7 @@ class ChatJoinRequestHandler(BaseHandler[Update, CCT]): callback: HandlerCallback[Update, CCT, RT], chat_id: SCT[int] = None, username: SCT[str] = None, - block: DVInput[bool] = DEFAULT_TRUE, + block: DVType[bool] = DEFAULT_TRUE, ): super().__init__(callback, block=block) diff --git a/telegram/ext/_chatmemberhandler.py b/telegram/ext/_chatmemberhandler.py index 46fa717dc..2267ee593 100644 --- a/telegram/ext/_chatmemberhandler.py +++ b/telegram/ext/_chatmemberhandler.py @@ -17,11 +17,11 @@ # 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 ChatMemberHandler class.""" -from typing import ClassVar, TypeVar +from typing import ClassVar, Optional, TypeVar from telegram import Update from telegram._utils.defaultvalue import DEFAULT_TRUE -from telegram._utils.types import DVInput +from telegram._utils.types import DVType from telegram.ext._handler import BaseHandler from telegram.ext._utils.types import CCT, HandlerCallback @@ -83,11 +83,11 @@ class ChatMemberHandler(BaseHandler[Update, CCT]): self, callback: HandlerCallback[Update, CCT, RT], chat_member_types: int = MY_CHAT_MEMBER, - block: DVInput[bool] = DEFAULT_TRUE, + block: DVType[bool] = DEFAULT_TRUE, ): super().__init__(callback, block=block) - self.chat_member_types = chat_member_types + self.chat_member_types: Optional[int] = chat_member_types def check_update(self, update: object) -> bool: """Determines whether an update should be passed to this handler's :attr:`callback`. diff --git a/telegram/ext/_choseninlineresulthandler.py b/telegram/ext/_choseninlineresulthandler.py index 652677618..80669590f 100644 --- a/telegram/ext/_choseninlineresulthandler.py +++ b/telegram/ext/_choseninlineresulthandler.py @@ -18,11 +18,11 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the ChosenInlineResultHandler class.""" import re -from typing import TYPE_CHECKING, Match, Optional, Pattern, TypeVar, Union, cast +from typing import TYPE_CHECKING, Any, Match, Optional, Pattern, TypeVar, Union, cast from telegram import Update from telegram._utils.defaultvalue import DEFAULT_TRUE -from telegram._utils.types import DVInput +from telegram._utils.types import DVType from telegram.ext._handler import BaseHandler from telegram.ext._utils.types import CCT, HandlerCallback @@ -78,15 +78,15 @@ class ChosenInlineResultHandler(BaseHandler[Update, CCT]): def __init__( self, callback: HandlerCallback[Update, CCT, RT], - block: DVInput[bool] = DEFAULT_TRUE, - pattern: Union[str, Pattern] = None, + block: DVType[bool] = DEFAULT_TRUE, + pattern: Union[str, Pattern[str]] = None, ): super().__init__(callback, block=block) if isinstance(pattern, str): pattern = re.compile(pattern) - self.pattern = pattern + self.pattern: Optional[Union[str, Pattern[str]]] = pattern def check_update(self, update: object) -> Optional[Union[bool, object]]: """Determines whether an update should be passed to this handler's :attr:`callback`. @@ -111,8 +111,8 @@ class ChosenInlineResultHandler(BaseHandler[Update, CCT]): self, context: CCT, update: Update, # skipcq: BAN-B301 - application: "Application", # skipcq: BAN-B301 - check_result: Union[bool, Match], + application: "Application[Any, CCT, Any, Any, Any, Any]", # skipcq: BAN-B301 + check_result: Union[bool, Match[str]], ) -> None: """This function adds the matched regex pattern result to :attr:`telegram.ext.CallbackContext.matches`. diff --git a/telegram/ext/_commandhandler.py b/telegram/ext/_commandhandler.py index db05faf49..a6e5f3898 100644 --- a/telegram/ext/_commandhandler.py +++ b/telegram/ext/_commandhandler.py @@ -18,14 +18,14 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the CommandHandler class.""" import re -from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, TypeVar, Union +from typing import TYPE_CHECKING, Any, FrozenSet, List, Optional, Tuple, TypeVar, Union from telegram import MessageEntity, Update from telegram._utils.defaultvalue import DEFAULT_TRUE -from telegram._utils.types import SCT, DVInput +from telegram._utils.types import SCT, DVType from telegram.ext import filters as filters_module from telegram.ext._handler import BaseHandler -from telegram.ext._utils.types import CCT, HandlerCallback +from telegram.ext._utils.types import CCT, FilterDataDict, HandlerCallback if TYPE_CHECKING: from telegram.ext import Application @@ -109,7 +109,7 @@ class CommandHandler(BaseHandler[Update, CCT]): command: SCT[str], callback: HandlerCallback[Update, CCT, RT], filters: filters_module.BaseFilter = None, - block: DVInput[bool] = DEFAULT_TRUE, + block: DVType[bool] = DEFAULT_TRUE, ): super().__init__(callback, block=block) @@ -120,13 +120,15 @@ class CommandHandler(BaseHandler[Update, CCT]): for comm in commands: if not re.match(r"^[\da-z_]{1,32}$", comm): raise ValueError(f"Command `{comm}` is not a valid bot command") - self.commands = commands + self.commands: FrozenSet[str] = commands - self.filters = filters if filters is not None else filters_module.UpdateType.MESSAGES + self.filters: filters_module.BaseFilter = ( + filters if filters is not None else filters_module.UpdateType.MESSAGES + ) def check_update( self, update: object - ) -> Optional[Union[bool, Tuple[List[str], Optional[Union[bool, Dict]]]]]: + ) -> Optional[Union[bool, Tuple[List[str], Optional[Union[bool, FilterDataDict]]]]]: """Determines whether an update should be passed to this handler's :attr:`callback`. Args: @@ -167,7 +169,7 @@ class CommandHandler(BaseHandler[Update, CCT]): self, context: CCT, update: Update, # skipcq: BAN-B301 - application: "Application", # skipcq: BAN-B301 + application: "Application[Any, CCT, Any, Any, Any, Any]", # skipcq: BAN-B301 check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]], ) -> None: """Add text after the command to :attr:`CallbackContext.args` as list, split on single diff --git a/telegram/ext/_contexttypes.py b/telegram/ext/_contexttypes.py index 17099f6eb..e46ea4e8f 100644 --- a/telegram/ext/_contexttypes.py +++ b/telegram/ext/_contexttypes.py @@ -17,15 +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 auxiliary class ContextTypes.""" -from typing import TYPE_CHECKING, Generic, Type, overload +from typing import Any, Dict, Generic, Type, overload from telegram.ext._callbackcontext import CallbackContext +from telegram.ext._extbot import ExtBot from telegram.ext._utils.types import BD, CCT, CD, UD -if TYPE_CHECKING: - from typing import Dict - - from telegram.ext._extbot import ExtBot +ADict = Dict[Any, Any] class ContextTypes(Generic[CCT, UD, CD, BD]): @@ -61,7 +59,7 @@ class ContextTypes(Generic[CCT, UD, CD, BD]): """ - DEFAULT_TYPE = CallbackContext["ExtBot", dict, dict, dict] + DEFAULT_TYPE = CallbackContext[ExtBot[None], ADict, ADict, ADict] """Shortcut for the type annotation for the ``context`` argument that's correct for the default settings, i.e. if :class:`telegram.ext.ContextTypes` is not used. @@ -81,56 +79,56 @@ class ContextTypes(Generic[CCT, UD, CD, BD]): @overload def __init__( - self: "ContextTypes[CallbackContext[ExtBot, Dict, Dict, Dict], Dict, Dict, Dict]", + self: "ContextTypes[CallbackContext[ExtBot[Any], ADict, ADict, ADict], ADict, ADict, ADict]", # pylint: disable=line-too-long # noqa: E501 ): ... @overload - def __init__(self: "ContextTypes[CCT, Dict, Dict, Dict]", context: Type[CCT]): + def __init__(self: "ContextTypes[CCT, ADict, ADict, ADict]", context: Type[CCT]): ... @overload def __init__( - self: "ContextTypes[CallbackContext[ExtBot, UD, Dict, Dict], UD, Dict, Dict]", + self: "ContextTypes[CallbackContext[ExtBot[Any], UD, ADict, ADict], UD, ADict, ADict]", user_data: Type[UD], ): ... @overload def __init__( - self: "ContextTypes[CallbackContext[ExtBot, Dict, CD, Dict], Dict, CD, Dict]", + self: "ContextTypes[CallbackContext[ExtBot[Any], ADict, CD, ADict], ADict, CD, ADict]", chat_data: Type[CD], ): ... @overload def __init__( - self: "ContextTypes[CallbackContext[ExtBot, Dict, Dict, BD], Dict, Dict, BD]", + self: "ContextTypes[CallbackContext[ExtBot[Any], ADict, ADict, BD], ADict, ADict, BD]", bot_data: Type[BD], ): ... @overload def __init__( - self: "ContextTypes[CCT, UD, Dict, Dict]", context: Type[CCT], user_data: Type[UD] + self: "ContextTypes[CCT, UD, ADict, ADict]", context: Type[CCT], user_data: Type[UD] ): ... @overload def __init__( - self: "ContextTypes[CCT, Dict, CD, Dict]", context: Type[CCT], chat_data: Type[CD] + self: "ContextTypes[CCT, ADict, CD, ADict]", context: Type[CCT], chat_data: Type[CD] ): ... @overload def __init__( - self: "ContextTypes[CCT, Dict, Dict, BD]", context: Type[CCT], bot_data: Type[BD] + self: "ContextTypes[CCT, ADict, ADict, BD]", context: Type[CCT], bot_data: Type[BD] ): ... @overload def __init__( - self: "ContextTypes[CallbackContext[ExtBot, UD, CD, Dict], UD, CD, Dict]", + self: "ContextTypes[CallbackContext[ExtBot[Any], UD, CD, ADict], UD, CD, ADict]", user_data: Type[UD], chat_data: Type[CD], ): @@ -138,7 +136,7 @@ class ContextTypes(Generic[CCT, UD, CD, BD]): @overload def __init__( - self: "ContextTypes[CallbackContext[ExtBot, UD, Dict, BD], UD, Dict, BD]", + self: "ContextTypes[CallbackContext[ExtBot[Any], UD, ADict, BD], UD, ADict, BD]", user_data: Type[UD], bot_data: Type[BD], ): @@ -146,7 +144,7 @@ class ContextTypes(Generic[CCT, UD, CD, BD]): @overload def __init__( - self: "ContextTypes[CallbackContext[ExtBot, Dict, CD, BD], Dict, CD, BD]", + self: "ContextTypes[CallbackContext[ExtBot[Any], ADict, CD, BD], ADict, CD, BD]", chat_data: Type[CD], bot_data: Type[BD], ): @@ -154,7 +152,7 @@ class ContextTypes(Generic[CCT, UD, CD, BD]): @overload def __init__( - self: "ContextTypes[CCT, UD, CD, Dict]", + self: "ContextTypes[CCT, UD, CD, ADict]", context: Type[CCT], user_data: Type[UD], chat_data: Type[CD], @@ -163,7 +161,7 @@ class ContextTypes(Generic[CCT, UD, CD, BD]): @overload def __init__( - self: "ContextTypes[CCT, UD, Dict, BD]", + self: "ContextTypes[CCT, UD, ADict, BD]", context: Type[CCT], user_data: Type[UD], bot_data: Type[BD], @@ -172,7 +170,7 @@ class ContextTypes(Generic[CCT, UD, CD, BD]): @overload def __init__( - self: "ContextTypes[CCT, Dict, CD, BD]", + self: "ContextTypes[CCT, ADict, CD, BD]", context: Type[CCT], chat_data: Type[CD], bot_data: Type[BD], @@ -181,7 +179,7 @@ class ContextTypes(Generic[CCT, UD, CD, BD]): @overload def __init__( - self: "ContextTypes[CallbackContext[ExtBot, UD, CD, BD], UD, CD, BD]", + self: "ContextTypes[CallbackContext[ExtBot[Any], UD, CD, BD], UD, CD, BD]", user_data: Type[UD], chat_data: Type[CD], bot_data: Type[BD], @@ -198,12 +196,12 @@ class ContextTypes(Generic[CCT, UD, CD, BD]): ): ... - def __init__( # type: ignore[no-untyped-def] + def __init__( # type: ignore[misc] self, - context=CallbackContext, - bot_data=dict, - chat_data=dict, - user_data=dict, + context: "Type[CallbackContext[ExtBot[Any], ADict, ADict, ADict]]" = CallbackContext, + bot_data: Type[ADict] = dict, + chat_data: Type[ADict] = dict, + user_data: Type[ADict] = dict, ): if not issubclass(context, CallbackContext): raise ValueError("context must be a subclass of CallbackContext.") @@ -220,25 +218,25 @@ class ContextTypes(Generic[CCT, UD, CD, BD]): """The type of the ``context`` argument of all (error-)handler callbacks and job callbacks. """ - return self._context + return self._context # type: ignore[return-value] @property def bot_data(self) -> Type[BD]: """The type of :attr:`context.bot_data ` of all (error-)handler callbacks and job callbacks. """ - return self._bot_data + return self._bot_data # type: ignore[return-value] @property def chat_data(self) -> Type[CD]: """The type of :attr:`context.chat_data ` of all (error-)handler callbacks and job callbacks. """ - return self._chat_data + return self._chat_data # type: ignore[return-value] @property def user_data(self) -> Type[UD]: """The type of :attr:`context.user_data ` of all (error-)handler callbacks and job callbacks. """ - return self._user_data + return self._user_data # type: ignore[return-value] diff --git a/telegram/ext/_conversationhandler.py b/telegram/ext/_conversationhandler.py index 156d1c6ee..1ce3a0831 100644 --- a/telegram/ext/_conversationhandler.py +++ b/telegram/ext/_conversationhandler.py @@ -38,10 +38,9 @@ from typing import ( from telegram import Update from telegram._utils.defaultvalue import DEFAULT_TRUE, DefaultValue -from telegram._utils.types import DVInput +from telegram._utils.types import DVType from telegram._utils.warnings import warn from telegram.ext._application import ApplicationHandlerStop -from telegram.ext._callbackcontext import CallbackContext from telegram.ext._callbackqueryhandler import CallbackQueryHandler from telegram.ext._choseninlineresulthandler import ChosenInlineResultHandler from telegram.ext._extbot import ExtBot @@ -55,7 +54,7 @@ from telegram.ext._utils.types import CCT, ConversationDict, ConversationKey if TYPE_CHECKING: from telegram.ext import Application, Job, JobQueue -_CheckUpdateType = Tuple[object, ConversationKey, BaseHandler, object] +_CheckUpdateType = Tuple[object, ConversationKey, BaseHandler[Update, CCT], object] _logger = logging.getLogger(__name__) @@ -71,7 +70,7 @@ class _ConversationTimeoutContext(Generic[CCT]): conversation_key: ConversationKey update: Update application: "Application[Any, CCT, Any, Any, Any, JobQueue]" - callback_context: CallbackContext + callback_context: CCT @dataclass @@ -307,7 +306,7 @@ class ConversationHandler(BaseHandler[Update, CCT]): name: str = None, persistent: bool = False, map_to_parent: Dict[object, object] = None, - block: DVInput[bool] = DEFAULT_TRUE, + block: DVType[bool] = DEFAULT_TRUE, ): # these imports need to be here because of circular import error otherwise from telegram.ext import ( # pylint: disable=import-outside-toplevel @@ -319,25 +318,27 @@ class ConversationHandler(BaseHandler[Update, CCT]): # self.block is what the Application checks and we want it to always run CH in a blocking # way so that CH can take care of any non-blocking logic internally - self.block = True + self.block: DVType[bool] = True # Store the actual setting in a protected variable instead - self._block = block + self._block: DVType[bool] = block - self._entry_points = entry_points - self._states = states - self._fallbacks = fallbacks + self._entry_points: List[BaseHandler[Update, CCT]] = entry_points + self._states: Dict[object, List[BaseHandler[Update, CCT]]] = states + self._fallbacks: List[BaseHandler[Update, CCT]] = fallbacks - self._allow_reentry = allow_reentry - self._per_user = per_user - self._per_chat = per_chat - self._per_message = per_message - self._conversation_timeout = conversation_timeout - self._name = name - self._map_to_parent = map_to_parent + self._allow_reentry: bool = allow_reentry + self._per_user: bool = per_user + self._per_chat: bool = per_chat + self._per_message: bool = per_message + self._conversation_timeout: Optional[ + Union[float, datetime.timedelta] + ] = conversation_timeout + self._name: Optional[str] = name + self._map_to_parent: Optional[Dict[object, object]] = map_to_parent # if conversation_timeout is used, this dict is used to schedule a job which runs when the # conv has timed out. - self.timeout_jobs: Dict[ConversationKey, "Job"] = {} + self.timeout_jobs: Dict[ConversationKey, "Job[Any]"] = {} self._timeout_jobs_lock = asyncio.Lock() self._conversations: ConversationDict = {} self._child_conversations: Set["ConversationHandler"] = set() @@ -356,7 +357,7 @@ class ConversationHandler(BaseHandler[Update, CCT]): stacklevel=2, ) - all_handlers: List[BaseHandler] = [] + all_handlers: List[BaseHandler[Update, CCT]] = [] all_handlers.extend(entry_points) all_handlers.extend(fallbacks) @@ -439,7 +440,7 @@ class ConversationHandler(BaseHandler[Update, CCT]): ) @property - def entry_points(self) -> List[BaseHandler]: + def entry_points(self) -> List[BaseHandler[Update, CCT]]: """List[:class:`telegram.ext.BaseHandler`]: A list of :obj:`BaseHandler` objects that can trigger the start of the conversation. """ @@ -452,7 +453,7 @@ class ConversationHandler(BaseHandler[Update, CCT]): ) @property - def states(self) -> Dict[object, List[BaseHandler]]: + def states(self) -> Dict[object, List[BaseHandler[Update, CCT]]]: """Dict[:obj:`object`, List[:class:`telegram.ext.BaseHandler`]]: A :obj:`dict` that defines the different states of conversation a user can be in and one or more associated :obj:`BaseHandler` objects that should be used in that state. @@ -464,7 +465,7 @@ class ConversationHandler(BaseHandler[Update, CCT]): raise AttributeError("You can not assign a new value to states after initialization.") @property - def fallbacks(self) -> List[BaseHandler]: + def fallbacks(self) -> List[BaseHandler[Update, CCT]]: """List[:class:`telegram.ext.BaseHandler`]: A list of handlers that might be used if the user is in a conversation, but every handler for their current state returned :obj:`False` on :meth:`check_update`. @@ -641,7 +642,7 @@ class ConversationHandler(BaseHandler[Update, CCT]): new_state: asyncio.Task, application: "Application[Any, CCT, Any, Any, Any, JobQueue]", update: Update, - context: CallbackContext, + context: CCT, conversation_key: ConversationKey, ) -> None: try: @@ -666,7 +667,7 @@ class ConversationHandler(BaseHandler[Update, CCT]): new_state: object, application: "Application[Any, CCT, Any, Any, Any, JobQueue]", update: Update, - context: CallbackContext, + context: CCT, conversation_key: ConversationKey, ) -> None: """Schedules a job which executes :meth:`_trigger_timeout` upon conversation timeout.""" @@ -685,7 +686,7 @@ class ConversationHandler(BaseHandler[Update, CCT]): _logger.exception("Failed to schedule timeout.", exc_info=exc) # pylint: disable=too-many-return-statements - def check_update(self, update: object) -> Optional[_CheckUpdateType]: + def check_update(self, update: object) -> Optional[_CheckUpdateType[CCT]]: """ Determines whether an update should be handled by this conversation handler, and if so in which state the conversation currently is. @@ -779,9 +780,9 @@ class ConversationHandler(BaseHandler[Update, CCT]): async def handle_update( # type: ignore[override] self, update: Update, - application: "Application", - check_result: _CheckUpdateType, - context: CallbackContext, + application: "Application[Any, CCT, Any, Any, Any, Any]", + check_result: _CheckUpdateType[CCT], + context: CCT, ) -> Optional[object]: """Send the update to the callback for the current state and BaseHandler @@ -899,7 +900,7 @@ class ConversationHandler(BaseHandler[Update, CCT]): ) self._conversations[key] = new_state - async def _trigger_timeout(self, context: CallbackContext) -> None: + async def _trigger_timeout(self, context: CCT) -> None: """This is run whenever a conversation has timed out. Also makes sure that all handlers which are in the :attr:`TIMEOUT` state and whose :meth:`BaseHandler.check_update` returns :obj:`True` is handled. diff --git a/telegram/ext/_defaults.py b/telegram/ext/_defaults.py index 5f0e325dd..695101381 100644 --- a/telegram/ext/_defaults.py +++ b/telegram/ext/_defaults.py @@ -80,14 +80,14 @@ class Defaults: allow_sending_without_reply: bool = None, protect_content: bool = None, ): - self._parse_mode = parse_mode - self._disable_notification = disable_notification - self._disable_web_page_preview = disable_web_page_preview - self._allow_sending_without_reply = allow_sending_without_reply - self._quote = quote - self._tzinfo = tzinfo - self._block = block - self._protect_content = protect_content + self._parse_mode: Optional[str] = parse_mode + self._disable_notification: Optional[bool] = disable_notification + self._disable_web_page_preview: Optional[bool] = disable_web_page_preview + self._allow_sending_without_reply: Optional[bool] = allow_sending_without_reply + self._quote: Optional[bool] = quote + self._tzinfo: datetime.tzinfo = tzinfo + self._block: bool = block + self._protect_content: Optional[bool] = protect_content # Gather all defaults that actually have a default value self._api_defaults = {} diff --git a/telegram/ext/_dictpersistence.py b/telegram/ext/_dictpersistence.py index da3cc16e9..04dcec698 100644 --- a/telegram/ext/_dictpersistence.py +++ b/telegram/ext/_dictpersistence.py @@ -19,14 +19,14 @@ """This module contains the DictPersistence class.""" import json from copy import deepcopy -from typing import Dict, Optional, cast +from typing import Any, Dict, Optional, cast from telegram._utils.types import JSONDict from telegram.ext import BasePersistence, PersistenceInput from telegram.ext._utils.types import CDCData, ConversationDict, ConversationKey -class DictPersistence(BasePersistence): +class DictPersistence(BasePersistence[Dict[Any, Any], Dict[Any, Any], Dict[Any, Any]]): """Using Python's :obj:`dict` and :mod:`json` for making your bot persistent. Attention: @@ -104,11 +104,11 @@ class DictPersistence(BasePersistence): self._bot_data = None self._callback_data = None self._conversations = None - self._user_data_json = None - self._chat_data_json = None - self._bot_data_json = None - self._callback_data_json = None - self._conversations_json = None + self._user_data_json: Optional[str] = None + self._chat_data_json: Optional[str] = None + self._bot_data_json: Optional[str] = None + self._callback_data_json: Optional[str] = None + self._conversations_json: Optional[str] = None if user_data_json: try: self._user_data = self._decode_user_chat_data_from_json(user_data_json) @@ -168,7 +168,7 @@ class DictPersistence(BasePersistence): ) from exc @property - def user_data(self) -> Optional[Dict[int, Dict]]: + def user_data(self) -> Optional[Dict[int, Dict[Any, Any]]]: """:obj:`dict`: The user_data as a dict.""" return self._user_data @@ -180,7 +180,7 @@ class DictPersistence(BasePersistence): return json.dumps(self.user_data) @property - def chat_data(self) -> Optional[Dict[int, Dict]]: + def chat_data(self) -> Optional[Dict[int, Dict[Any, Any]]]: """:obj:`dict`: The chat_data as a dict.""" return self._chat_data @@ -192,7 +192,7 @@ class DictPersistence(BasePersistence): return json.dumps(self.chat_data) @property - def bot_data(self) -> Optional[Dict]: + def bot_data(self) -> Optional[Dict[Any, Any]]: """:obj:`dict`: The bot_data as a dict.""" return self._bot_data @@ -309,7 +309,7 @@ class DictPersistence(BasePersistence): self._conversations[name][key] = new_state self._conversations_json = None - async def update_user_data(self, user_id: int, data: Dict) -> None: + async def update_user_data(self, user_id: int, data: Dict[Any, Any]) -> None: """Will update the user_data (if changed). Args: @@ -323,7 +323,7 @@ class DictPersistence(BasePersistence): self._user_data[user_id] = data self._user_data_json = None - async def update_chat_data(self, chat_id: int, data: Dict) -> None: + async def update_chat_data(self, chat_id: int, data: Dict[Any, Any]) -> None: """Will update the chat_data (if changed). Args: @@ -337,7 +337,7 @@ class DictPersistence(BasePersistence): self._chat_data[chat_id] = data self._chat_data_json = None - async def update_bot_data(self, data: Dict) -> None: + async def update_bot_data(self, data: Dict[Any, Any]) -> None: """Will update the bot_data (if changed). Args: @@ -389,21 +389,21 @@ class DictPersistence(BasePersistence): self._user_data.pop(user_id, None) self._user_data_json = None - async def refresh_user_data(self, user_id: int, user_data: Dict) -> None: + async def refresh_user_data(self, user_id: int, user_data: Dict[Any, Any]) -> None: """Does nothing. .. versionadded:: 13.6 .. seealso:: :meth:`telegram.ext.BasePersistence.refresh_user_data` """ - async def refresh_chat_data(self, chat_id: int, chat_data: Dict) -> None: + async def refresh_chat_data(self, chat_id: int, chat_data: Dict[Any, Any]) -> None: """Does nothing. .. versionadded:: 13.6 .. seealso:: :meth:`telegram.ext.BasePersistence.refresh_chat_data` """ - async def refresh_bot_data(self, bot_data: Dict) -> None: + async def refresh_bot_data(self, bot_data: Dict[Any, Any]) -> None: """Does nothing. .. versionadded:: 13.6 diff --git a/telegram/ext/_extbot.py b/telegram/ext/_extbot.py index ba850d250..d4346c61a 100644 --- a/telegram/ext/_extbot.py +++ b/telegram/ext/_extbot.py @@ -198,7 +198,7 @@ class ExtBot(Bot, Generic[RLARGS]): defaults: "Defaults" = None, arbitrary_callback_data: Union[bool, int] = False, local_mode: bool = False, - rate_limiter: "BaseRateLimiter" = None, + rate_limiter: "BaseRateLimiter[RLARGS]" = None, ): super().__init__( token=token, @@ -211,8 +211,8 @@ class ExtBot(Bot, Generic[RLARGS]): local_mode=local_mode, ) with self._unfrozen(): - self._defaults = defaults - self._rate_limiter = rate_limiter + self._defaults: Optional[Defaults] = defaults + self._rate_limiter: Optional[BaseRateLimiter] = rate_limiter self._callback_data_cache: Optional[CallbackDataCache] = None # set up callback_data @@ -339,7 +339,7 @@ class ExtBot(Bot, Generic[RLARGS]): return self._defaults @property - def rate_limiter(self) -> Optional["BaseRateLimiter"]: + def rate_limiter(self) -> Optional["BaseRateLimiter[RLARGS]"]: """The :class:`telegram.ext.BaseRateLimiter` used by this bot, if any. .. versionadded:: 20.0 diff --git a/telegram/ext/_handler.py b/telegram/ext/_handler.py index fe7ae26a9..1af83b88d 100644 --- a/telegram/ext/_handler.py +++ b/telegram/ext/_handler.py @@ -21,7 +21,7 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar, Union from telegram._utils.defaultvalue import DEFAULT_TRUE -from telegram._utils.types import DVInput +from telegram._utils.types import DVType from telegram.ext._utils.types import CCT, HandlerCallback if TYPE_CHECKING: @@ -90,10 +90,10 @@ class BaseHandler(Generic[UT, CCT], ABC): def __init__( self, callback: HandlerCallback[UT, CCT, RT], - block: DVInput[bool] = DEFAULT_TRUE, + block: DVType[bool] = DEFAULT_TRUE, ): - self.callback = callback - self.block = block + self.callback: HandlerCallback[UT, CCT, RT] = callback + self.block: DVType[bool] = block @abstractmethod def check_update(self, update: object) -> Optional[Union[bool, object]]: @@ -118,7 +118,7 @@ class BaseHandler(Generic[UT, CCT], ABC): async def handle_update( self, update: UT, - application: "Application", + application: "Application[Any, CCT, Any, Any, Any, Any]", check_result: object, context: CCT, ) -> RT: @@ -144,7 +144,7 @@ class BaseHandler(Generic[UT, CCT], ABC): self, context: CCT, update: UT, - application: "Application", + application: "Application[Any, CCT, Any, Any, Any, Any]", check_result: Any, ) -> None: """Prepares additional arguments for the context. Override if needed. diff --git a/telegram/ext/_inlinequeryhandler.py b/telegram/ext/_inlinequeryhandler.py index d5ac00a9d..20a8cb4fb 100644 --- a/telegram/ext/_inlinequeryhandler.py +++ b/telegram/ext/_inlinequeryhandler.py @@ -18,11 +18,11 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the InlineQueryHandler class.""" import re -from typing import TYPE_CHECKING, List, Match, Optional, Pattern, TypeVar, Union, cast +from typing import TYPE_CHECKING, Any, List, Match, Optional, Pattern, TypeVar, Union, cast from telegram import Update from telegram._utils.defaultvalue import DEFAULT_TRUE -from telegram._utils.types import DVInput +from telegram._utils.types import DVType from telegram.ext._handler import BaseHandler from telegram.ext._utils.types import CCT, HandlerCallback @@ -89,8 +89,8 @@ class InlineQueryHandler(BaseHandler[Update, CCT]): def __init__( self, callback: HandlerCallback[Update, CCT, RT], - pattern: Union[str, Pattern] = None, - block: DVInput[bool] = DEFAULT_TRUE, + pattern: Union[str, Pattern[str]] = None, + block: DVType[bool] = DEFAULT_TRUE, chat_types: List[str] = None, ): super().__init__(callback, block=block) @@ -98,10 +98,10 @@ class InlineQueryHandler(BaseHandler[Update, CCT]): if isinstance(pattern, str): pattern = re.compile(pattern) - self.pattern = pattern - self.chat_types = chat_types + self.pattern: Optional[Union[str, Pattern[str]]] = pattern + self.chat_types: Optional[List[str]] = chat_types - def check_update(self, update: object) -> Optional[Union[bool, Match]]: + def check_update(self, update: object) -> Optional[Union[bool, Match[str]]]: """ Determines whether an update should be passed to this handler's :attr:`callback`. @@ -130,8 +130,8 @@ class InlineQueryHandler(BaseHandler[Update, CCT]): self, context: CCT, update: Update, # skipcq: BAN-B301 - application: "Application", # skipcq: BAN-B301 - check_result: Optional[Union[bool, Match]], + application: "Application[Any, CCT, Any, Any, Any, Any]", # skipcq: BAN-B301 + check_result: Optional[Union[bool, Match[str]]], ) -> None: """Add the result of ``re.match(pattern, update.inline_query.query)`` to :attr:`CallbackContext.matches` as list with one element. diff --git a/telegram/ext/_jobqueue.py b/telegram/ext/_jobqueue.py index e02686607..3940a3c5b 100644 --- a/telegram/ext/_jobqueue.py +++ b/telegram/ext/_jobqueue.py @@ -20,7 +20,7 @@ import asyncio import datetime import weakref -from typing import TYPE_CHECKING, Optional, Tuple, Union, cast, overload +from typing import TYPE_CHECKING, Any, Generic, Optional, Tuple, Union, cast, overload try: import pytz @@ -35,16 +35,20 @@ except ImportError: from telegram._utils.types import JSONDict from telegram._utils.warnings import warn from telegram.ext._extbot import ExtBot -from telegram.ext._utils.types import JobCallback +from telegram.ext._utils.types import CCT, JobCallback if TYPE_CHECKING: from telegram.ext import Application -class JobQueue: +class JobQueue(Generic[CCT]): """This class allows you to periodically perform tasks with the bot. It is a convenience wrapper for the APScheduler library. + This class is a :class:`~typing.Generic` class and accepts one type variable that specifies + the type of the argument ``context`` of the job callbacks (:paramref:`~run_once.callback`) of + :meth:`run_once` and the other scheduling methods. + Important: If you want to use this class, you must install PTB with the optional requirement ``job-queue``, i.e. @@ -86,7 +90,9 @@ class JobQueue: self._application: "Optional[weakref.ReferenceType[Application]]" = None self._executor = AsyncIOExecutor() - self.scheduler = AsyncIOScheduler(timezone=pytz.utc, executors={"default": self._executor}) + self.scheduler: AsyncIOScheduler = AsyncIOScheduler( + timezone=pytz.utc, executors={"default": self._executor} + ) def _tz_now(self) -> datetime.datetime: return datetime.datetime.now(self.scheduler.timezone) @@ -125,7 +131,9 @@ class JobQueue: return date_time return time - def set_application(self, application: "Application") -> None: + def set_application( + self, application: "Application[Any, CCT, Any, Any, Any, JobQueue[CCT]]" + ) -> None: """Set the application to be used by this JobQueue. Args: @@ -140,7 +148,7 @@ class JobQueue: ) @property - def application(self) -> "Application": + def application(self) -> "Application[Any, CCT, Any, Any, Any, JobQueue[CCT]]": """The application this JobQueue is associated with.""" if self._application is None: raise RuntimeError("No application was set for this JobQueue.") @@ -151,14 +159,14 @@ class JobQueue: def run_once( self, - callback: JobCallback, + callback: JobCallback[CCT], when: Union[float, datetime.timedelta, datetime.datetime, datetime.time], data: object = None, name: str = None, chat_id: int = None, user_id: int = None, job_kwargs: JSONDict = None, - ) -> "Job": + ) -> "Job[CCT]": """Creates a new :class:`Job` instance that runs once and adds it to the queue. Args: @@ -235,7 +243,7 @@ class JobQueue: def run_repeating( self, - callback: JobCallback, + callback: JobCallback[CCT], 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, @@ -244,7 +252,7 @@ class JobQueue: chat_id: int = None, user_id: int = None, job_kwargs: JSONDict = None, - ) -> "Job": + ) -> "Job[CCT]": """Creates a new :class:`Job` instance that runs at specified intervals and adds it to the queue. @@ -350,7 +358,7 @@ class JobQueue: def run_monthly( self, - callback: JobCallback, + callback: JobCallback[CCT], when: datetime.time, day: int, data: object = None, @@ -358,7 +366,7 @@ class JobQueue: chat_id: int = None, user_id: int = None, job_kwargs: JSONDict = None, - ) -> "Job": + ) -> "Job[CCT]": """Creates a new :class:`Job` that runs on a monthly basis and adds it to the queue. .. versionchanged:: 20.0 @@ -428,7 +436,7 @@ class JobQueue: def run_daily( self, - callback: JobCallback, + callback: JobCallback[CCT], time: datetime.time, days: Tuple[int, ...] = tuple(range(7)), data: object = None, @@ -436,7 +444,7 @@ class JobQueue: chat_id: int = None, user_id: int = None, job_kwargs: JSONDict = None, - ) -> "Job": + ) -> "Job[CCT]": """Creates a new :class:`Job` that runs on a daily basis and adds it to the queue. Note: @@ -519,13 +527,13 @@ class JobQueue: def run_custom( self, - callback: JobCallback, + callback: JobCallback[CCT], job_kwargs: JSONDict, data: object = None, name: str = None, chat_id: int = None, user_id: int = None, - ) -> "Job": + ) -> "Job[CCT]": """Creates a new custom defined :class:`Job`. Args: @@ -598,7 +606,7 @@ class JobQueue: # so give it a tiny bit of time to actually shut down. await asyncio.sleep(0.01) - def jobs(self) -> Tuple["Job", ...]: + def jobs(self) -> Tuple["Job[CCT]", ...]: """Returns a tuple of all *scheduled* jobs that are currently in the :class:`JobQueue`. Returns: @@ -609,7 +617,7 @@ class JobQueue: for job in self.scheduler.get_jobs() ) - def get_jobs_by_name(self, name: str) -> Tuple["Job", ...]: + def get_jobs_by_name(self, name: str) -> Tuple["Job[CCT]", ...]: """Returns a tuple of all *pending/scheduled* jobs with the given name that are currently in the :class:`JobQueue`. @@ -619,7 +627,7 @@ class JobQueue: return tuple(job for job in self.jobs() if job.name == name) -class Job: +class Job(Generic[CCT]): """This class is a convenience wrapper for the jobs held in a :class:`telegram.ext.JobQueue`. With the current backend APScheduler, :attr:`job` holds a :class:`apscheduler.job.Job` instance. @@ -627,6 +635,9 @@ class Job: Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :class:`id ` is equal. + This class is a :class:`~typing.Generic` class and accepts one type variable that specifies + the type of the argument ``context`` of :paramref:`callback`. + Important: If you want to use this class, you must install PTB with the optional requirement ``job-queue``, i.e. @@ -695,7 +706,7 @@ class Job: def __init__( self, - callback: JobCallback, + callback: JobCallback[CCT], data: object = None, name: str = None, chat_id: int = None, @@ -707,11 +718,11 @@ class Job: "python-telegram-bot[job-queue]`." ) - self.callback = callback - self.data = data - self.name = name or callback.__name__ - self.chat_id = chat_id - self.user_id = user_id + self.callback: JobCallback[CCT] = callback + self.data: Optional[object] = data + self.name: Optional[str] = name or callback.__name__ + self.chat_id: Optional[int] = chat_id + self.user_id: Optional[int] = user_id self._removed = False self._enabled = False @@ -727,7 +738,9 @@ class Job: """ return self._job - async def run(self, application: "Application") -> None: + async def run( + self, application: "Application[Any, CCT, Any, Any, Any, JobQueue[CCT]]" + ) -> None: """Executes the callback function independently of the jobs schedule. Also calls :meth:`telegram.ext.Application.update_persistence`. @@ -741,7 +754,9 @@ class Job: # We shield the task such that the job isn't cancelled mid-run await asyncio.shield(self._run(application)) - async def _run(self, application: "Application") -> None: + async def _run( + self, application: "Application[Any, CCT, Any, Any, Any, JobQueue[CCT]]" + ) -> None: try: context = application.context_types.context.from_job(self, application) await context.refresh_data() @@ -792,7 +807,7 @@ class Job: return self.job.next_run_time @classmethod - def _from_aps_job(cls, job: "APSJob") -> "Job": + def _from_aps_job(cls, job: "APSJob") -> "Job[CCT]": return job.func.__self__ def __getattr__(self, item: str) -> object: diff --git a/telegram/ext/_messagehandler.py b/telegram/ext/_messagehandler.py index 62ebcf5f7..e2ed9ac77 100644 --- a/telegram/ext/_messagehandler.py +++ b/telegram/ext/_messagehandler.py @@ -17,11 +17,11 @@ # 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 MessageHandler class.""" -from typing import TYPE_CHECKING, Dict, Optional, TypeVar, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, TypeVar, Union from telegram import Update from telegram._utils.defaultvalue import DEFAULT_TRUE -from telegram._utils.types import DVInput +from telegram._utils.types import DVType from telegram.ext import filters as filters_module from telegram.ext._handler import BaseHandler from telegram.ext._utils.types import CCT, HandlerCallback @@ -78,13 +78,15 @@ class MessageHandler(BaseHandler[Update, CCT]): self, filters: filters_module.BaseFilter, callback: HandlerCallback[Update, CCT, RT], - block: DVInput[bool] = DEFAULT_TRUE, + block: DVType[bool] = DEFAULT_TRUE, ): super().__init__(callback, block=block) - self.filters = filters if filters is not None else filters_module.ALL + self.filters: filters_module.BaseFilter = ( + filters if filters is not None else filters_module.ALL + ) - def check_update(self, update: object) -> Optional[Union[bool, Dict[str, list]]]: + def check_update(self, update: object) -> Optional[Union[bool, Dict[str, List[Any]]]]: """Determines whether an update should be passed to this handler's :attr:`callback`. Args: @@ -102,7 +104,7 @@ class MessageHandler(BaseHandler[Update, CCT]): self, context: CCT, update: Update, # skipcq: BAN-B301 - application: "Application", # skipcq: BAN-B301 + application: "Application[Any, CCT, Any, Any, Any, Any]", # skipcq: BAN-B301 check_result: Optional[Union[bool, Dict[str, object]]], ) -> None: """Adds possible output of data filters to the :class:`CallbackContext`.""" diff --git a/telegram/ext/_picklepersistence.py b/telegram/ext/_picklepersistence.py index d4ca43d65..0b3632253 100644 --- a/telegram/ext/_picklepersistence.py +++ b/telegram/ext/_picklepersistence.py @@ -22,7 +22,7 @@ import pickle from copy import deepcopy from pathlib import Path from sys import version_info as py_ver -from typing import Any, Callable, Dict, Optional, Set, Tuple, Type, TypeVar, cast, overload +from typing import Any, Callable, Dict, Optional, Set, Tuple, Type, TypeVar, Union, cast, overload from telegram import Bot, TelegramObject from telegram._utils.types import FilePathInput @@ -201,7 +201,7 @@ class PicklePersistence(BasePersistence[UD, CD, BD]): @overload def __init__( - self: "PicklePersistence[Dict, Dict, Dict]", + self: "PicklePersistence[Dict[Any, Any], Dict[Any, Any], Dict[Any, Any]]", filepath: FilePathInput, store_data: PersistenceInput = None, single_file: bool = True, @@ -232,15 +232,17 @@ class PicklePersistence(BasePersistence[UD, CD, BD]): context_types: ContextTypes[Any, UD, CD, BD] = None, ): super().__init__(store_data=store_data, update_interval=update_interval) - self.filepath = Path(filepath) - self.single_file = single_file - self.on_flush = on_flush + self.filepath: Path = Path(filepath) + self.single_file: Optional[bool] = single_file + self.on_flush: Optional[bool] = on_flush self.user_data: Optional[Dict[int, UD]] = None self.chat_data: Optional[Dict[int, CD]] = None self.bot_data: Optional[BD] = None self.callback_data: Optional[CDCData] = None - self.conversations: Optional[Dict[str, Dict[Tuple, object]]] = None - self.context_types = cast(ContextTypes[Any, UD, CD, BD], context_types or ContextTypes()) + self.conversations: Optional[Dict[str, Dict[Tuple[Union[int, str], ...], object]]] = None + self.context_types: ContextTypes[Any, UD, CD, BD] = cast( + ContextTypes[Any, UD, CD, BD], context_types or ContextTypes() + ) def _load_singlefile(self) -> None: try: diff --git a/telegram/ext/_prefixhandler.py b/telegram/ext/_prefixhandler.py index fa4311b11..ef42c6b91 100644 --- a/telegram/ext/_prefixhandler.py +++ b/telegram/ext/_prefixhandler.py @@ -18,11 +18,11 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the PrefixHandler class.""" import itertools -from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, TypeVar, Union +from typing import TYPE_CHECKING, Any, Dict, FrozenSet, List, Optional, Tuple, TypeVar, Union from telegram import Update from telegram._utils.defaultvalue import DEFAULT_TRUE -from telegram._utils.types import SCT, DVInput +from telegram._utils.types import SCT, DVType from telegram.ext import filters as filters_module from telegram.ext._handler import BaseHandler from telegram.ext._utils.types import CCT, HandlerCallback @@ -128,7 +128,7 @@ class PrefixHandler(BaseHandler[Update, CCT]): command: SCT[str], callback: HandlerCallback[Update, CCT, RT], filters: filters_module.BaseFilter = None, - block: DVInput[bool] = DEFAULT_TRUE, + block: DVType[bool] = DEFAULT_TRUE, ): super().__init__(callback=callback, block=block) @@ -143,12 +143,16 @@ class PrefixHandler(BaseHandler[Update, CCT]): else: commands = {x.lower() for x in command} - self.commands = frozenset(p + c for p, c in itertools.product(prefixes, commands)) - self.filters = filters if filters is not None else filters_module.UpdateType.MESSAGES + self.commands: FrozenSet[str] = frozenset( + p + c for p, c in itertools.product(prefixes, commands) + ) + self.filters: filters_module.BaseFilter = ( + filters if filters is not None else filters_module.UpdateType.MESSAGES + ) def check_update( self, update: object - ) -> Optional[Union[bool, Tuple[List[str], Optional[Union[bool, Dict]]]]]: + ) -> Optional[Union[bool, Tuple[List[str], Optional[Union[bool, Dict[Any, Any]]]]]]: """Determines whether an update should be passed to this handler's :attr:`callback`. Args: @@ -175,7 +179,7 @@ class PrefixHandler(BaseHandler[Update, CCT]): self, context: CCT, update: Update, # skipcq: BAN-B301 - application: "Application", # skipcq: BAN-B301 + application: "Application[Any, CCT, Any, Any, Any, Any]", # skipcq: BAN-B301 check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]], ) -> None: """Add text after the command to :attr:`CallbackContext.args` as list, split on single diff --git a/telegram/ext/_stringcommandhandler.py b/telegram/ext/_stringcommandhandler.py index dac604569..632f3be04 100644 --- a/telegram/ext/_stringcommandhandler.py +++ b/telegram/ext/_stringcommandhandler.py @@ -18,10 +18,10 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the StringCommandHandler class.""" -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING, Any, List, Optional from telegram._utils.defaultvalue import DEFAULT_TRUE -from telegram._utils.types import DVInput +from telegram._utils.types import DVType from telegram.ext._handler import BaseHandler from telegram.ext._utils.types import CCT, RT, HandlerCallback @@ -74,10 +74,10 @@ class StringCommandHandler(BaseHandler[str, CCT]): self, command: str, callback: HandlerCallback[str, CCT, RT], - block: DVInput[bool] = DEFAULT_TRUE, + block: DVType[bool] = DEFAULT_TRUE, ): super().__init__(callback, block=block) - self.command = command + self.command: str = command def check_update(self, update: object) -> Optional[List[str]]: """Determines whether an update should be passed to this handler's :attr:`callback`. @@ -99,7 +99,7 @@ class StringCommandHandler(BaseHandler[str, CCT]): self, context: CCT, update: str, # skipcq: BAN-B301 - application: "Application", # skipcq: BAN-B301 + application: "Application[Any, CCT, Any, Any, Any, Any]", # skipcq: BAN-B301 check_result: Optional[List[str]], ) -> None: """Add text after the command to :attr:`CallbackContext.args` as list, split on single diff --git a/telegram/ext/_stringregexhandler.py b/telegram/ext/_stringregexhandler.py index aa9da3759..f3abc17b4 100644 --- a/telegram/ext/_stringregexhandler.py +++ b/telegram/ext/_stringregexhandler.py @@ -19,10 +19,10 @@ """This module contains the StringRegexHandler class.""" import re -from typing import TYPE_CHECKING, Match, Optional, Pattern, TypeVar, Union +from typing import TYPE_CHECKING, Any, Match, Optional, Pattern, TypeVar, Union from telegram._utils.defaultvalue import DEFAULT_TRUE -from telegram._utils.types import DVInput +from telegram._utils.types import DVType from telegram.ext._handler import BaseHandler from telegram.ext._utils.types import CCT, HandlerCallback @@ -75,18 +75,18 @@ class StringRegexHandler(BaseHandler[str, CCT]): def __init__( self, - pattern: Union[str, Pattern], + pattern: Union[str, Pattern[str]], callback: HandlerCallback[str, CCT, RT], - block: DVInput[bool] = DEFAULT_TRUE, + block: DVType[bool] = DEFAULT_TRUE, ): super().__init__(callback, block=block) if isinstance(pattern, str): pattern = re.compile(pattern) - self.pattern = pattern + self.pattern: Union[str, Pattern[str]] = pattern - def check_update(self, update: object) -> Optional[Match]: + def check_update(self, update: object) -> Optional[Match[str]]: """Determines whether an update should be passed to this handler's :attr:`callback`. Args: @@ -106,8 +106,8 @@ class StringRegexHandler(BaseHandler[str, CCT]): self, context: CCT, update: str, # skipcq: BAN-B301 - application: "Application", # skipcq: BAN-B301 - check_result: Optional[Match], + application: "Application[Any, CCT, Any, Any, Any, Any]", # skipcq: BAN-B301 + check_result: Optional[Match[str]], ) -> None: """Add the result of ``re.match(pattern, update)`` to :attr:`CallbackContext.matches` as list with one element. diff --git a/telegram/ext/_typehandler.py b/telegram/ext/_typehandler.py index 7dc6dd624..477e2f6f5 100644 --- a/telegram/ext/_typehandler.py +++ b/telegram/ext/_typehandler.py @@ -18,10 +18,10 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains the TypeHandler class.""" -from typing import Type, TypeVar +from typing import Optional, Type, TypeVar from telegram._utils.defaultvalue import DEFAULT_TRUE -from telegram._utils.types import DVInput +from telegram._utils.types import DVType from telegram.ext._handler import BaseHandler from telegram.ext._utils.types import CCT, HandlerCallback @@ -74,11 +74,11 @@ class TypeHandler(BaseHandler[UT, CCT]): type: Type[UT], # pylint: disable=redefined-builtin callback: HandlerCallback[UT, CCT, RT], strict: bool = False, - block: DVInput[bool] = DEFAULT_TRUE, + block: DVType[bool] = DEFAULT_TRUE, ): super().__init__(callback, block=block) - self.type = type - self.strict = strict + self.type: Type[UT] = type + self.strict: Optional[bool] = strict def check_update(self, update: object) -> bool: """Determines whether an update should be passed to this handler's :attr:`callback`. diff --git a/telegram/ext/_updater.py b/telegram/ext/_updater.py index 802314851..50ea3c59d 100644 --- a/telegram/ext/_updater.py +++ b/telegram/ext/_updater.py @@ -20,10 +20,19 @@ import asyncio import logging import ssl -from contextlib import AbstractAsyncContextManager from pathlib import Path from types import TracebackType -from typing import TYPE_CHECKING, Callable, Coroutine, List, Optional, Type, TypeVar, Union +from typing import ( + TYPE_CHECKING, + AsyncContextManager, + Callable, + Coroutine, + List, + Optional, + Type, + TypeVar, + Union, +) from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import ODVInput @@ -43,7 +52,7 @@ if TYPE_CHECKING: _UpdaterType = TypeVar("_UpdaterType", bound="Updater") # pylint: disable=invalid-name -class Updater(AbstractAsyncContextManager): +class Updater(AsyncContextManager["Updater"]): """This class fetches updates for the bot either via long polling or by starting a webhook server. Received updates are enqueued into the :attr:`update_queue` and may be fetched from there to handle them appropriately. @@ -100,10 +109,10 @@ class Updater(AbstractAsyncContextManager): def __init__( self, bot: "Bot", - update_queue: asyncio.Queue, + update_queue: "asyncio.Queue[object]", ): - self.bot = bot - self.update_queue = update_queue + self.bot: Bot = bot + self.update_queue: "asyncio.Queue[object]" = update_queue self._last_update_id = 0 self._running = False @@ -184,7 +193,7 @@ class Updater(AbstractAsyncContextManager): allowed_updates: List[str] = None, drop_pending_updates: bool = None, error_callback: Callable[[TelegramError], None] = None, - ) -> asyncio.Queue: + ) -> "asyncio.Queue[object]": """Starts polling updates from Telegram. .. versionchanged:: 20.0 @@ -379,7 +388,7 @@ class Updater(AbstractAsyncContextManager): ip_address: str = None, max_connections: int = 40, secret_token: str = None, - ) -> asyncio.Queue: + ) -> "asyncio.Queue[object]": """ Starts a small http server to listen for updates via webhook. If :paramref:`cert` and :paramref:`key` are not provided, the webhook will be started directly on diff --git a/telegram/ext/_utils/types.py b/telegram/ext/_utils/types.py index 8cbc89d9d..12a64ff92 100644 --- a/telegram/ext/_utils/types.py +++ b/telegram/ext/_utils/types.py @@ -44,7 +44,7 @@ if TYPE_CHECKING: from telegram import Bot from telegram.ext import BaseRateLimiter, CallbackContext, JobQueue -CCT = TypeVar("CCT", bound="CallbackContext") +CCT = TypeVar("CCT", bound="CallbackContext[Any, Any, Any, Any]") """An instance of :class:`telegram.ext.CallbackContext` or a custom subclass. .. versionadded:: 13.6 @@ -113,3 +113,4 @@ RLARGS = TypeVar("RLARGS") """Type of the rate limiter arguments. .. versionadded:: 20.0""" +FilterDataDict = Dict[str, List[Any]] diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index f4167dcbe..63df85eed 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -95,6 +95,7 @@ import mimetypes import re from abc import ABC, abstractmethod from typing import ( + Collection, Dict, FrozenSet, List, @@ -102,6 +103,7 @@ from typing import ( NoReturn, Optional, Pattern, + Sequence, Set, Tuple, Union, @@ -113,8 +115,7 @@ from telegram import Message, MessageEntity, Update from telegram import User as TGUser from telegram._utils.types import SCT from telegram.constants import DiceEmoji as DiceEmojiEnum - -DataDict = Dict[str, list] +from telegram.ext._utils.types import FilterDataDict class BaseFilter: @@ -172,10 +173,6 @@ class BaseFilter: return a dict with lists. The dict will be merged with :class:`telegram.ext.CallbackContext`'s internal dict in most cases (depends on the handler). - - Attributes: - name (:obj:`str`): Name for this filter. - data_filter (:obj:`bool`): Whether this filter is a data filter. """ __slots__ = ("_name", "_data_filter") @@ -184,8 +181,20 @@ class BaseFilter: self._name = self.__class__.__name__ if name is None else name self._data_filter = data_filter - def check_update(self, update: Update) -> Optional[Union[bool, DataDict]]: # skipcq: PYL-R0201 - """Checks if the specified update is a message.""" + def check_update( # skipcq: PYL-R0201 + self, update: Update + ) -> Optional[Union[bool, FilterDataDict]]: + """Checks if the specified update should be handled by this filter. + + Args: + update (:class:`telegram.Update`): The update to check. + + Returns: + :obj:`bool`: :obj:`True` if the update contains one of + :attr:`~telegram.Update.channel_post`, :attr:`~telegram.Update.message`, + :attr:`~telegram.Update.edited_channel_post` or + :attr:`~telegram.Update.edited_message`, :obj:`False` otherwise. + """ if ( # Only message updates should be handled. update.channel_post or update.message @@ -209,6 +218,7 @@ class BaseFilter: @property def data_filter(self) -> bool: + """:obj:`bool`: Whether this filter is a data filter.""" return self._data_filter @data_filter.setter @@ -217,6 +227,7 @@ class BaseFilter: @property def name(self) -> str: + """:obj:`str`: Name for this filter.""" return self._name @name.setter @@ -235,24 +246,29 @@ class MessageFilter(BaseFilter): .. seealso:: :wiki:`Advanced Filters ` - Attributes: - name (:obj:`str`): Name for this filter. Defaults to the type of filter. - data_filter (:obj:`bool`): Whether this filter is a data filter. A data filter should - return a dict with lists. The dict will be merged with - :class:`telegram.ext.CallbackContext`'s internal dict in most cases - (depends on the handler). - """ __slots__ = () - def check_update(self, update: Update) -> Optional[Union[bool, DataDict]]: + def check_update(self, update: Update) -> Optional[Union[bool, FilterDataDict]]: + """Checks if the specified update should be handled by this filter by passing + :attr:`~telegram.Update.effective_message` to :meth:`filter`. + + Args: + update (:class:`telegram.Update`): The update to check. + + Returns: + :obj:`bool` | Dict[:obj:`str`, :obj:`list`] | :obj:`None`: If the update should be + handled by this filter, returns :obj:`True` or a dict with lists, in case the filter + is a data filter. If the update should not be handled by this filter, :obj:`False` or + :obj:`None`. + """ if super().check_update(update): return self.filter(update.effective_message) # type: ignore[arg-type] return False @abstractmethod - def filter(self, message: Message) -> Optional[Union[bool, DataDict]]: + def filter(self, message: Message) -> Optional[Union[bool, FilterDataDict]]: """This method must be overwritten. Args: @@ -272,22 +288,26 @@ class UpdateFilter(BaseFilter): Please see :class:`telegram.ext.filters.BaseFilter` for details on how to create custom filters. - Attributes: - name (:obj:`str`): Name for this filter. Defaults to the type of filter. - data_filter (:obj:`bool`): Whether this filter is a data filter. A data filter should - return a dict with lists. The dict will be merged with - :class:`telegram.ext.CallbackContext`'s internal dict in most cases - (depends on the handler). - """ __slots__ = () - def check_update(self, update: Update) -> Optional[Union[bool, DataDict]]: + def check_update(self, update: Update) -> Optional[Union[bool, FilterDataDict]]: + """Checks if the specified update should be handled by this filter. + + Args: + update (:class:`telegram.Update`): The update to check. + + Returns: + :obj:`bool` | Dict[:obj:`str`, :obj:`list`] | :obj:`None`: If the update should be + handled by this filter, returns :obj:`True` or a dict with lists, in case the filter + is a data filter. If the update should not be handled by this filter, :obj:`False` or + :obj:`None`. + """ return self.filter(update) if super().check_update(update) else False @abstractmethod - def filter(self, update: Update) -> Optional[Union[bool, DataDict]]: + def filter(self, update: Update) -> Optional[Union[bool, FilterDataDict]]: """This method must be overwritten. Args: @@ -356,7 +376,7 @@ class _MergedFilter(UpdateFilter): self.data_filter = True @staticmethod - def _merge(base_output: Union[bool, Dict], comp_output: Union[bool, Dict]) -> DataDict: + def _merge(base_output: Union[bool, Dict], comp_output: Union[bool, Dict]) -> FilterDataDict: base = base_output if isinstance(base_output, dict) else {} comp = comp_output if isinstance(comp_output, dict) else {} for k in comp.keys(): @@ -373,7 +393,7 @@ class _MergedFilter(UpdateFilter): return base # pylint: disable=too-many-return-statements - def filter(self, update: Update) -> Union[bool, DataDict]: + def filter(self, update: Update) -> Union[bool, FilterDataDict]: base_output = self.base_filter.check_update(update) # We need to check if the filters are data filters and if so return the merged data. # If it's not a data filter or an or_filter but no matches return bool @@ -431,7 +451,7 @@ class _XORFilter(UpdateFilter): self.xor_filter = xor_filter self.merged_filter = (base_filter & ~xor_filter) | (~base_filter & xor_filter) - def filter(self, update: Update) -> Optional[Union[bool, DataDict]]: + def filter(self, update: Update) -> Optional[Union[bool, FilterDataDict]]: return self.merged_filter.check_update(update) @property @@ -507,7 +527,7 @@ class Caption(MessageFilter): __slots__ = ("strings",) def __init__(self, strings: Union[List[str], Tuple[str, ...]] = None): - self.strings = strings + self.strings: Optional[Sequence[str]] = strings super().__init__(name=f"filters.Caption({strings})" if strings else "filters.CAPTION") def filter(self, message: Message) -> bool: @@ -541,7 +561,7 @@ class CaptionEntity(MessageFilter): __slots__ = ("entity_type",) def __init__(self, entity_type: str): - self.entity_type = entity_type + self.entity_type: str = entity_type super().__init__(name=f"filters.CaptionEntity({self.entity_type})") def filter(self, message: Message) -> bool: @@ -569,13 +589,13 @@ class CaptionRegex(MessageFilter): __slots__ = ("pattern",) - def __init__(self, pattern: Union[str, Pattern]): + def __init__(self, pattern: Union[str, Pattern[str]]): if isinstance(pattern, str): pattern = re.compile(pattern) - self.pattern: Pattern = pattern + self.pattern: Pattern[str] = pattern super().__init__(name=f"filters.CaptionRegex({self.pattern})", data_filter=True) - def filter(self, message: Message) -> Optional[Dict[str, List[Match]]]: + def filter(self, message: Message) -> Optional[Dict[str, List[Match[str]]]]: if message.caption: match = self.pattern.search(message.caption) if match: @@ -599,9 +619,9 @@ class _ChatUserBaseFilter(MessageFilter, ABC): allow_empty: bool = False, ): super().__init__() - self._chat_id_name = "chat_id" - self._username_name = "username" - self.allow_empty = allow_empty + self._chat_id_name: str = "chat_id" + self._username_name: str = "username" + self.allow_empty: bool = allow_empty self._chat_ids: Set[int] = set() self._usernames: Set[str] = set() @@ -610,7 +630,7 @@ class _ChatUserBaseFilter(MessageFilter, ABC): self._set_usernames(username) @abstractmethod - def get_chat_or_user(self, message: Message) -> Union[TGChat, TGUser, None]: + def _get_chat_or_user(self, message: Message) -> Union[TGChat, TGUser, None]: ... @staticmethod @@ -728,7 +748,7 @@ class _ChatUserBaseFilter(MessageFilter, ABC): self._chat_ids -= parsed_chat_id def filter(self, message: Message) -> bool: - chat_or_user = self.get_chat_or_user(message) + chat_or_user = self._get_chat_or_user(message) if chat_or_user: if self.chat_ids: return chat_or_user.id in self.chat_ids @@ -782,7 +802,7 @@ class Chat(_ChatUserBaseFilter): __slots__ = () - def get_chat_or_user(self, message: Message) -> Optional[TGChat]: + def _get_chat_or_user(self, message: Message) -> Optional[TGChat]: return message.chat def add_chat_ids(self, chat_id: SCT[int]) -> None: @@ -899,7 +919,7 @@ class Command(MessageFilter): __slots__ = ("only_start",) def __init__(self, only_start: bool = True): - self.only_start = only_start + self.only_start: bool = only_start super().__init__(f"filters.Command({only_start})" if not only_start else "filters.COMMAND") def filter(self, message: Message) -> bool: @@ -938,8 +958,8 @@ class _Dice(MessageFilter): def __init__(self, values: SCT[int] = None, emoji: DiceEmojiEnum = None): super().__init__() - self.emoji = emoji - self.values = [values] if isinstance(values, int) else values + self.emoji: Optional[DiceEmojiEnum] = emoji + self.values: Optional[Collection[int]] = [values] if isinstance(values, int) else values if emoji: # for filters.Dice.BASKETBALL self.name = f"filters.Dice.{emoji.name}" @@ -1186,7 +1206,7 @@ class Document: def __init__(self, file_extension: Optional[str], case_sensitive: bool = False): super().__init__() - self.is_case_sensitive = case_sensitive + self.is_case_sensitive: bool = case_sensitive if file_extension is None: self._file_extension = None self.name = "filters.Document.FileExtension(None)" @@ -1228,7 +1248,7 @@ class Document: __slots__ = ("mimetype",) def __init__(self, mimetype: str): - self.mimetype = mimetype # skipcq: PTC-W0052 + self.mimetype: str = mimetype # skipcq: PTC-W0052 super().__init__(name=f"filters.Document.MimeType('{self.mimetype}')") def filter(self, message: Message) -> bool: @@ -1287,7 +1307,7 @@ class Entity(MessageFilter): __slots__ = ("entity_type",) def __init__(self, entity_type: str): - self.entity_type = entity_type + self.entity_type: str = entity_type super().__init__(name=f"filters.Entity({self.entity_type})") def filter(self, message: Message) -> bool: @@ -1349,7 +1369,7 @@ class ForwardedFrom(_ChatUserBaseFilter): __slots__ = () - def get_chat_or_user(self, message: Message) -> Union[TGUser, TGChat, None]: + def _get_chat_or_user(self, message: Message) -> Union[TGUser, TGChat, None]: return message.forward_from or message.forward_from_chat def add_chat_ids(self, chat_id: SCT[int]) -> None: @@ -1474,7 +1494,7 @@ class Language(MessageFilter): def __init__(self, lang: SCT[str]): if isinstance(lang, str): lang = cast(str, lang) - self.lang = [lang] + self.lang: Sequence[str] = [lang] else: lang = cast(List[str], lang) self.lang = lang @@ -1567,13 +1587,13 @@ class Regex(MessageFilter): __slots__ = ("pattern",) - def __init__(self, pattern: Union[str, Pattern]): + def __init__(self, pattern: Union[str, Pattern[str]]): if isinstance(pattern, str): pattern = re.compile(pattern) - self.pattern: Pattern = pattern + self.pattern: Pattern[str] = pattern super().__init__(name=f"filters.Regex({self.pattern})", data_filter=True) - def filter(self, message: Message) -> Optional[Dict[str, List[Match]]]: + def filter(self, message: Message) -> Optional[Dict[str, List[Match[str]]]]: if message.text: match = self.pattern.search(message.text) if match: @@ -1685,7 +1705,7 @@ class SenderChat(_ChatUserBaseFilter): """ return super()._add_chat_ids(chat_id) - def get_chat_or_user(self, message: Message) -> Optional[TGChat]: + def _get_chat_or_user(self, message: Message) -> Optional[TGChat]: return message.sender_chat def remove_chat_ids(self, chat_id: SCT[int]) -> None: @@ -2141,7 +2161,7 @@ class Text(MessageFilter): __slots__ = ("strings",) def __init__(self, strings: Union[List[str], Tuple[str, ...]] = None): - self.strings = strings + self.strings: Optional[Sequence[str]] = strings super().__init__(name=f"filters.Text({strings})" if strings else "filters.TEXT") def filter(self, message: Message) -> bool: @@ -2277,7 +2297,7 @@ class User(_ChatUserBaseFilter): super().__init__(chat_id=user_id, username=username, allow_empty=allow_empty) self._chat_id_name = "user_id" - def get_chat_or_user(self, message: Message) -> Optional[TGUser]: + def _get_chat_or_user(self, message: Message) -> Optional[TGUser]: return message.from_user @property @@ -2413,7 +2433,7 @@ class ViaBot(_ChatUserBaseFilter): super().__init__(chat_id=bot_id, username=username, allow_empty=allow_empty) self._chat_id_name = "bot_id" - def get_chat_or_user(self, message: Message) -> Optional[TGUser]: + def _get_chat_or_user(self, message: Message) -> Optional[TGUser]: return message.via_bot @property diff --git a/telegram/request/_baserequest.py b/telegram/request/_baserequest.py index ba878d672..97445cbe9 100644 --- a/telegram/request/_baserequest.py +++ b/telegram/request/_baserequest.py @@ -20,12 +20,12 @@ import abc import asyncio import json -from contextlib import AbstractAsyncContextManager from http import HTTPStatus from types import TracebackType -from typing import ClassVar, List, Optional, Tuple, Type, TypeVar, Union +from typing import AsyncContextManager, ClassVar, List, Optional, Tuple, Type, TypeVar, Union from telegram._utils.defaultvalue import DEFAULT_NONE as _DEFAULT_NONE +from telegram._utils.defaultvalue import DefaultValue from telegram._utils.types import JSONDict, ODVInput from telegram._version import __version__ as ptb_ver from telegram.error import ( @@ -44,7 +44,7 @@ RT = TypeVar("RT", bound="BaseRequest") class BaseRequest( - AbstractAsyncContextManager, + AsyncContextManager["BaseRequest"], abc.ABC, ): """Abstract interface class that allows python-telegram-bot to make requests to the Bot API. @@ -84,7 +84,7 @@ class BaseRequest( USER_AGENT: ClassVar[str] = f"python-telegram-bot v{ptb_ver} (https://python-telegram-bot.org)" """:obj:`str`: A description that can be used as user agent for requests made to the Bot API. """ - DEFAULT_NONE: ClassVar = _DEFAULT_NONE + DEFAULT_NONE: ClassVar[DefaultValue[None]] = _DEFAULT_NONE """:class:`object`: A special object that indicates that an argument of a function was not explicitly passed. Used for the timeout parameters of :meth:`post` and :meth:`do_request`. diff --git a/telegram/request/_requestdata.py b/telegram/request/_requestdata.py index 2ebde8f30..a957b1e8f 100644 --- a/telegram/request/_requestdata.py +++ b/telegram/request/_requestdata.py @@ -44,11 +44,11 @@ class RequestData: __slots__ = ("_parameters", "contains_files") def __init__(self, parameters: List[RequestParameter] = None): - self._parameters = parameters or [] - self.contains_files = any(param.input_files for param in self._parameters) + self._parameters: List[RequestParameter] = parameters or [] + self.contains_files: bool = any(param.input_files for param in self._parameters) @property - def parameters(self) -> Dict[str, Union[str, int, List, Dict]]: + def parameters(self) -> Dict[str, Union[str, int, List[Any], Dict[Any, Any]]]: """Gives the parameters as mapping of parameter name to the parameter value, which can be a single object of type :obj:`int`, :obj:`float`, :obj:`str` or :obj:`bool` or any (possibly nested) composition of lists, tuples and dictionaries, where each entry, key