Skip to content

Commit

Permalink
Explicit Type Annotations (#3508)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bibo-Joshi committed Feb 2, 2023
1 parent 1701950 commit 23d0bd8
Show file tree
Hide file tree
Showing 137 changed files with 1,355 additions and 1,170 deletions.
61 changes: 61 additions & 0 deletions .github/workflows/type_completeness.yml
Original file line number Diff line number Diff line change
@@ -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)
5 changes: 5 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 == "<enum 'StringEnum'>":
bases[idx] = ":class:`enum.Enum`"
Expand Down
4 changes: 2 additions & 2 deletions docs/source/telegram.ext.filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 4 additions & 4 deletions telegram/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@
#: :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.
Expand All @@ -352,13 +352,13 @@
#: ``__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__
2 changes: 2 additions & 0 deletions telegram/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand All @@ -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()


Expand Down
16 changes: 8 additions & 8 deletions telegram/_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
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,
Expand Down Expand Up @@ -114,7 +114,7 @@
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
Expand Down Expand Up @@ -229,13 +229,13 @@ def __init__(
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

Expand Down Expand Up @@ -312,7 +312,7 @@ def __reduce__(self) -> NoReturn:
"""
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.
Expand Down
4 changes: 2 additions & 2 deletions telegram/_botcommand.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
10 changes: 5 additions & 5 deletions telegram/_botcommandscope.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
14 changes: 7 additions & 7 deletions telegram/_callbackquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,14 @@ def __init__(
):
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,)

Expand Down
58 changes: 30 additions & 28 deletions telegram/_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,37 +352,39 @@ def __init__(
):
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,)

Expand Down
25 changes: 13 additions & 12 deletions telegram/_chatadministratorrights.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -138,19 +139,19 @@ def __init__(
) -> 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,
Expand Down
18 changes: 9 additions & 9 deletions telegram/_chatinvitelink.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,17 @@ def __init__(
):
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 = (
Expand Down
Loading

0 comments on commit 23d0bd8

Please sign in to comment.