Improve Type Completeness (#4466)

This commit is contained in:
Bibo-Joshi 2024-09-14 08:16:39 +02:00 committed by GitHub
parent b3155b2e55
commit 6b5e46cc08
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 109 additions and 69 deletions

View file

@ -0,0 +1,33 @@
name: Check Type Completeness Monthly Run
on:
schedule:
# Run first friday of the month at 03:17 - odd time to spread load on GitHub Actions
- cron: '17 3 1-7 * 5'
jobs:
test-type-completeness:
name: test-type-completeness
runs-on: ubuntu-latest
steps:
- uses: Bibo-Joshi/pyright-type-completeness@1.0.0
id: pyright-type-completeness
with:
package-name: telegram
python-version: 3.12
pyright-version: ~=1.1.367
- name: Check Output
uses: jannekem/run-python-script-action@v1
env:
TYPE_COMPLETENESS: ${{ steps.pyright-type-completeness.outputs.base-completeness-score }}
with:
script: |
import os
completeness = float(os.getenv("TYPE_COMPLETENESS"))
if completeness >= 1:
exit(0)
text = f"Type Completeness Decreased to {completeness}. ❌"
error(text)
set_summary(text)
exit(1)

View file

@ -371,8 +371,8 @@ def main() -> None:
entry_points=[CommandHandler("start", start)],
states={
SHOWING: [CallbackQueryHandler(start, pattern="^" + str(END) + "$")],
SELECTING_ACTION: selection_handlers,
SELECTING_LEVEL: selection_handlers,
SELECTING_ACTION: selection_handlers, # type: ignore[dict-item]
SELECTING_LEVEL: selection_handlers, # type: ignore[dict-item]
DESCRIBING_SELF: [description_conv],
STOPPING: [CommandHandler("start", start)],
},

View file

@ -323,7 +323,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
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.handlers: Dict[int, List[BaseHandler[Any, CCT, Any]]] = {}
self.error_handlers: Dict[
HandlerCallback[object, CCT, None], Union[bool, DefaultValue[bool]]
] = {}
@ -1352,7 +1352,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
# (in __create_task_callback)
self._mark_for_persistence_update(update=update)
def add_handler(self, handler: BaseHandler[Any, CCT], group: int = DEFAULT_GROUP) -> None:
def add_handler(self, handler: BaseHandler[Any, CCT, Any], group: int = DEFAULT_GROUP) -> None:
"""Register a handler.
TL;DR: Order and priority counts. 0 or 1 handlers per group will be used. End handling of
@ -1420,8 +1420,8 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
def add_handlers(
self,
handlers: Union[
Union[List[BaseHandler[Any, CCT]], Tuple[BaseHandler[Any, CCT]]],
Dict[int, Union[List[BaseHandler[Any, CCT]], Tuple[BaseHandler[Any, CCT]]]],
Union[List[BaseHandler[Any, CCT, Any]], Tuple[BaseHandler[Any, CCT, Any]]],
Dict[int, Union[List[BaseHandler[Any, CCT, Any]], Tuple[BaseHandler[Any, CCT, Any]]]],
],
group: Union[int, DefaultValue[int]] = _DEFAULT_0,
) -> None:
@ -1469,7 +1469,9 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
"dictionary where the keys are groups and values are sequences of handlers."
)
def remove_handler(self, handler: BaseHandler[Any, CCT], group: int = DEFAULT_GROUP) -> None:
def remove_handler(
self, handler: BaseHandler[Any, CCT, Any], group: int = DEFAULT_GROUP
) -> None:
"""Remove a handler from the specified group.
Args:

View file

@ -29,6 +29,7 @@ from typing import (
NoReturn,
Optional,
Type,
TypeVar,
Union,
)
@ -49,6 +50,9 @@ _STORING_DATA_WIKI = (
"/wiki/Storing-bot%2C-user-and-chat-related-data"
)
# something like poor mans "typing.Self" for py<3.11
ST = TypeVar("ST", bound="CallbackContext[Any, Any, Any, Any]")
class CallbackContext(Generic[BT, UD, CD, BD]):
"""
@ -133,24 +137,24 @@ class CallbackContext(Generic[BT, UD, CD, BD]):
)
def __init__(
self: "CCT",
application: "Application[BT, CCT, UD, CD, BD, Any]",
self: ST,
application: "Application[BT, ST, UD, CD, BD, Any]",
chat_id: Optional[int] = None,
user_id: Optional[int] = None,
):
self._application: Application[BT, CCT, UD, CD, BD, Any] = application
self._application: Application[BT, ST, 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[str]]] = None
self.error: Optional[Exception] = None
self.job: Optional[Job[CCT]] = None
self.job: Optional[Job[Any]] = None
self.coroutine: Optional[
Union[Generator[Optional[Future[object]], None, Any], Awaitable[Any]]
] = None
@property
def application(self) -> "Application[BT, CCT, UD, CD, BD, Any]":
def application(self) -> "Application[BT, ST, UD, CD, BD, Any]":
""":class:`telegram.ext.Application`: The application associated with this context."""
return self._application
@ -398,7 +402,7 @@ class CallbackContext(Generic[BT, UD, CD, BD]):
return self._application.bot
@property
def job_queue(self) -> Optional["JobQueue[CCT]"]:
def job_queue(self) -> Optional["JobQueue[ST]"]:
"""
:class:`telegram.ext.JobQueue`: The :class:`JobQueue` used by the
:class:`telegram.ext.Application`.

View file

@ -32,14 +32,14 @@ RT = TypeVar("RT")
UT = TypeVar("UT")
class BaseHandler(Generic[UT, CCT], ABC):
class BaseHandler(Generic[UT, CCT, RT], ABC):
"""The base class for all update handlers. Create custom handlers by inheriting from it.
Warning:
When setting :paramref:`block` to :obj:`False`, you cannot rely on adding custom
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
This class is a :class:`~typing.Generic` class and accepts two type variables:
This class is a :class:`~typing.Generic` class and accepts three type variables:
1. The type of the updates that this handler will handle. Must coincide with the type of the
first argument of :paramref:`callback`. :meth:`check_update` must only accept
@ -54,6 +54,7 @@ class BaseHandler(Generic[UT, CCT], ABC):
For this type variable, one should usually provide a :class:`~typing.TypeVar` that is
also used for the mentioned method arguments. That way, a type checker can check whether
this handler fits the definition of the :class:`~Application`.
3. The return type of the :paramref:`callback` function accepted by this handler.
.. seealso:: :wiki:`Types of Handlers <Types-of-Handlers>`
@ -89,7 +90,7 @@ class BaseHandler(Generic[UT, CCT], ABC):
)
def __init__(
self,
self: "BaseHandler[UT, CCT, RT]",
callback: HandlerCallback[UT, CCT, RT],
block: DVType[bool] = DEFAULT_TRUE,
):

View file

@ -29,7 +29,7 @@ from telegram.ext._utils.types import CCT, HandlerCallback
RT = TypeVar("RT")
class BusinessConnectionHandler(BaseHandler[Update, CCT]):
class BusinessConnectionHandler(BaseHandler[Update, CCT, RT]):
"""Handler class to handle Telegram
:attr:`Business Connections <telegram.Update.business_connection>`.
@ -65,7 +65,7 @@ class BusinessConnectionHandler(BaseHandler[Update, CCT]):
)
def __init__(
self,
self: "BusinessConnectionHandler[CCT, RT]",
callback: HandlerCallback[Update, CCT, RT],
user_id: Optional[SCT[int]] = None,
username: Optional[SCT[str]] = None,

View file

@ -29,7 +29,7 @@ from telegram.ext._utils.types import CCT, HandlerCallback
RT = TypeVar("RT")
class BusinessMessagesDeletedHandler(BaseHandler[Update, CCT]):
class BusinessMessagesDeletedHandler(BaseHandler[Update, CCT, RT]):
"""Handler class to handle
:attr:`deleted Telegram Business messages <telegram.Update.deleted_business_messages>`.
@ -65,7 +65,7 @@ class BusinessMessagesDeletedHandler(BaseHandler[Update, CCT]):
)
def __init__(
self,
self: "BusinessMessagesDeletedHandler[CCT, RT]",
callback: HandlerCallback[Update, CCT, RT],
chat_id: Optional[SCT[int]] = None,
username: Optional[SCT[str]] = None,

View file

@ -33,7 +33,7 @@ if TYPE_CHECKING:
RT = TypeVar("RT")
class CallbackQueryHandler(BaseHandler[Update, CCT]):
class CallbackQueryHandler(BaseHandler[Update, CCT, RT]):
"""Handler class to handle Telegram
:attr:`callback queries <telegram.Update.callback_query>`. Optionally based on a regex.
@ -125,7 +125,7 @@ class CallbackQueryHandler(BaseHandler[Update, CCT]):
__slots__ = ("game_pattern", "pattern")
def __init__(
self,
self: "CallbackQueryHandler[CCT, RT]",
callback: HandlerCallback[Update, CCT, RT],
pattern: Optional[
Union[str, Pattern[str], type, Callable[[object], Optional[bool]]]

View file

@ -23,10 +23,10 @@ from typing import Final, Optional
from telegram import Update
from telegram.ext._handlers.basehandler import BaseHandler
from telegram.ext._utils._update_parsing import parse_chat_id, parse_username
from telegram.ext._utils.types import CCT, HandlerCallback
from telegram.ext._utils.types import CCT, RT, HandlerCallback
class ChatBoostHandler(BaseHandler[Update, CCT]):
class ChatBoostHandler(BaseHandler[Update, CCT, RT]):
"""
Handler class to handle Telegram updates that contain a chat boost.
@ -84,8 +84,8 @@ class ChatBoostHandler(BaseHandler[Update, CCT]):
and :attr:`telegram.Update.removed_chat_boost`."""
def __init__(
self,
callback: HandlerCallback[Update, CCT, None],
self: "ChatBoostHandler[CCT, RT]",
callback: HandlerCallback[Update, CCT, RT],
chat_boost_types: int = CHAT_BOOST,
chat_id: Optional[int] = None,
chat_username: Optional[str] = None,

View file

@ -28,7 +28,7 @@ from telegram.ext._utils._update_parsing import parse_chat_id, parse_username
from telegram.ext._utils.types import CCT, HandlerCallback
class ChatJoinRequestHandler(BaseHandler[Update, CCT]):
class ChatJoinRequestHandler(BaseHandler[Update, CCT, RT]):
"""Handler class to handle Telegram updates that contain
:attr:`telegram.Update.chat_join_request`.
@ -81,7 +81,7 @@ class ChatJoinRequestHandler(BaseHandler[Update, CCT]):
)
def __init__(
self,
self: "ChatJoinRequestHandler[CCT, RT]",
callback: HandlerCallback[Update, CCT, RT],
chat_id: Optional[SCT[int]] = None,
username: Optional[SCT[str]] = None,

View file

@ -29,7 +29,7 @@ from telegram.ext._utils.types import CCT, HandlerCallback
RT = TypeVar("RT")
class ChatMemberHandler(BaseHandler[Update, CCT]):
class ChatMemberHandler(BaseHandler[Update, CCT, RT]):
"""Handler class to handle Telegram updates that contain a chat member update.
Warning:
@ -87,7 +87,7 @@ class ChatMemberHandler(BaseHandler[Update, CCT]):
and :attr:`telegram.Update.chat_member`."""
def __init__(
self,
self: "ChatMemberHandler[CCT, RT]",
callback: HandlerCallback[Update, CCT, RT],
chat_member_types: int = MY_CHAT_MEMBER,
block: DVType[bool] = DEFAULT_TRUE,

View file

@ -32,7 +32,7 @@ if TYPE_CHECKING:
from telegram.ext import Application
class ChosenInlineResultHandler(BaseHandler[Update, CCT]):
class ChosenInlineResultHandler(BaseHandler[Update, CCT, RT]):
"""Handler class to handle Telegram updates that contain
:attr:`telegram.Update.chosen_inline_result`.
@ -76,7 +76,7 @@ class ChosenInlineResultHandler(BaseHandler[Update, CCT]):
__slots__ = ("pattern",)
def __init__(
self,
self: "ChosenInlineResultHandler[CCT, RT]",
callback: HandlerCallback[Update, CCT, RT],
block: DVType[bool] = DEFAULT_TRUE,
pattern: Optional[Union[str, Pattern[str]]] = None,

View file

@ -33,7 +33,7 @@ if TYPE_CHECKING:
RT = TypeVar("RT")
class CommandHandler(BaseHandler[Update, CCT]):
class CommandHandler(BaseHandler[Update, CCT, RT]):
"""Handler class to handle Telegram commands.
Commands are Telegram messages that start with ``/``, optionally followed by an ``@`` and the
@ -118,7 +118,7 @@ class CommandHandler(BaseHandler[Update, CCT]):
__slots__ = ("commands", "filters", "has_args")
def __init__(
self,
self: "CommandHandler[CCT, RT]",
command: SCT[str],
callback: HandlerCallback[Update, CCT, RT],
filters: Optional[filters_module.BaseFilter] = None,

View file

@ -55,7 +55,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[Update, CCT], object]
_CheckUpdateType = Tuple[object, ConversationKey, BaseHandler[Update, CCT, object], object]
_LOGGER = get_logger(__name__, class_name="ConversationHandler")
@ -119,7 +119,7 @@ class PendingState:
return res
class ConversationHandler(BaseHandler[Update, CCT]):
class ConversationHandler(BaseHandler[Update, CCT, object]):
"""
A handler to hold a conversation with a single or multiple users through Telegram updates by
managing three collections of other handlers.
@ -296,10 +296,10 @@ class ConversationHandler(BaseHandler[Update, CCT]):
# pylint: disable=super-init-not-called
def __init__(
self,
entry_points: List[BaseHandler[Update, CCT]],
states: Dict[object, List[BaseHandler[Update, CCT]]],
fallbacks: List[BaseHandler[Update, CCT]],
self: "ConversationHandler[CCT]",
entry_points: List[BaseHandler[Update, CCT, object]],
states: Dict[object, List[BaseHandler[Update, CCT, object]]],
fallbacks: List[BaseHandler[Update, CCT, object]],
allow_reentry: bool = False,
per_chat: bool = True,
per_user: bool = True,
@ -324,9 +324,9 @@ class ConversationHandler(BaseHandler[Update, CCT]):
# Store the actual setting in a protected variable instead
self._block: DVType[bool] = block
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._entry_points: List[BaseHandler[Update, CCT, object]] = entry_points
self._states: Dict[object, List[BaseHandler[Update, CCT, object]]] = states
self._fallbacks: List[BaseHandler[Update, CCT, object]] = fallbacks
self._allow_reentry: bool = allow_reentry
self._per_user: bool = per_user
@ -359,7 +359,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
stacklevel=2,
)
all_handlers: List[BaseHandler[Update, CCT]] = []
all_handlers: List[BaseHandler[Update, CCT, object]] = []
all_handlers.extend(entry_points)
all_handlers.extend(fallbacks)
@ -466,7 +466,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
)
@property
def entry_points(self) -> List[BaseHandler[Update, CCT]]:
def entry_points(self) -> List[BaseHandler[Update, CCT, object]]:
"""List[:class:`telegram.ext.BaseHandler`]: A list of :obj:`BaseHandler` objects that can
trigger the start of the conversation.
"""
@ -479,7 +479,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
)
@property
def states(self) -> Dict[object, List[BaseHandler[Update, CCT]]]:
def states(self) -> Dict[object, List[BaseHandler[Update, CCT, object]]]:
"""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.
@ -491,7 +491,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[Update, CCT]]:
def fallbacks(self) -> List[BaseHandler[Update, CCT, object]]:
"""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`.

View file

@ -32,7 +32,7 @@ if TYPE_CHECKING:
RT = TypeVar("RT")
class InlineQueryHandler(BaseHandler[Update, CCT]):
class InlineQueryHandler(BaseHandler[Update, CCT, RT]):
"""
BaseHandler class to handle Telegram updates that contain a
:attr:`telegram.Update.inline_query`.
@ -87,7 +87,7 @@ class InlineQueryHandler(BaseHandler[Update, CCT]):
__slots__ = ("chat_types", "pattern")
def __init__(
self,
self: "InlineQueryHandler[CCT, RT]",
callback: HandlerCallback[Update, CCT, RT],
pattern: Optional[Union[str, Pattern[str]]] = None,
block: DVType[bool] = DEFAULT_TRUE,

View file

@ -32,7 +32,7 @@ if TYPE_CHECKING:
RT = TypeVar("RT")
class MessageHandler(BaseHandler[Update, CCT]):
class MessageHandler(BaseHandler[Update, CCT, RT]):
"""Handler class to handle Telegram messages. They might contain text, media or status
updates.
@ -75,7 +75,7 @@ class MessageHandler(BaseHandler[Update, CCT]):
__slots__ = ("filters",)
def __init__(
self,
self: "MessageHandler[CCT, RT]",
filters: Optional[filters_module.BaseFilter],
callback: HandlerCallback[Update, CCT, RT],
block: DVType[bool] = DEFAULT_TRUE,

View file

@ -28,7 +28,7 @@ from telegram.ext._utils._update_parsing import parse_chat_id, parse_username
from telegram.ext._utils.types import CCT, HandlerCallback
class MessageReactionHandler(BaseHandler[Update, CCT]):
class MessageReactionHandler(BaseHandler[Update, CCT, RT]):
"""Handler class to handle Telegram updates that contain a message reaction.
Note:
@ -110,7 +110,7 @@ class MessageReactionHandler(BaseHandler[Update, CCT]):
and :attr:`telegram.Update.message_reaction_count`."""
def __init__(
self,
self: "MessageReactionHandler[CCT, RT]",
callback: HandlerCallback[Update, CCT, RT],
chat_id: Optional[SCT[int]] = None,
chat_username: Optional[SCT[str]] = None,

View file

@ -21,10 +21,10 @@
from telegram import Update
from telegram.ext._handlers.basehandler import BaseHandler
from telegram.ext._utils.types import CCT
from telegram.ext._utils.types import CCT, RT
class PollAnswerHandler(BaseHandler[Update, CCT]):
class PollAnswerHandler(BaseHandler[Update, CCT, RT]):
"""Handler class to handle Telegram updates that contain a
:attr:`poll answer <telegram.Update.poll_answer>`.

View file

@ -21,10 +21,10 @@
from telegram import Update
from telegram.ext._handlers.basehandler import BaseHandler
from telegram.ext._utils.types import CCT
from telegram.ext._utils.types import CCT, RT
class PollHandler(BaseHandler[Update, CCT]):
class PollHandler(BaseHandler[Update, CCT, RT]):
"""Handler class to handle Telegram updates that contain a
:attr:`poll <telegram.Update.poll>`.

View file

@ -31,7 +31,7 @@ from telegram.ext._utils.types import CCT, HandlerCallback
RT = TypeVar("RT")
class PreCheckoutQueryHandler(BaseHandler[Update, CCT]):
class PreCheckoutQueryHandler(BaseHandler[Update, CCT, RT]):
"""Handler class to handle Telegram :attr:`telegram.Update.pre_checkout_query`.
Warning:
@ -73,7 +73,7 @@ class PreCheckoutQueryHandler(BaseHandler[Update, CCT]):
__slots__ = ("pattern",)
def __init__(
self,
self: "PreCheckoutQueryHandler[CCT, RT]",
callback: HandlerCallback[Update, CCT, RT],
block: DVType[bool] = DEFAULT_TRUE,
pattern: Optional[Union[str, Pattern[str]]] = None,

View file

@ -33,7 +33,7 @@ if TYPE_CHECKING:
RT = TypeVar("RT")
class PrefixHandler(BaseHandler[Update, CCT]):
class PrefixHandler(BaseHandler[Update, CCT, RT]):
"""Handler class to handle custom prefix commands.
This is an intermediate handler between :class:`MessageHandler` and :class:`CommandHandler`.
@ -123,7 +123,7 @@ class PrefixHandler(BaseHandler[Update, CCT]):
__slots__ = ("commands", "filters")
def __init__(
self,
self: "PrefixHandler[CCT, RT]",
prefix: SCT[str],
command: SCT[str],
callback: HandlerCallback[Update, CCT, RT],

View file

@ -21,10 +21,10 @@
from telegram import Update
from telegram.ext._handlers.basehandler import BaseHandler
from telegram.ext._utils.types import CCT
from telegram.ext._utils.types import CCT, RT
class ShippingQueryHandler(BaseHandler[Update, CCT]):
class ShippingQueryHandler(BaseHandler[Update, CCT, RT]):
"""Handler class to handle Telegram :attr:`telegram.Update.shipping_query`.
Warning:

View file

@ -29,7 +29,7 @@ if TYPE_CHECKING:
from telegram.ext import Application
class StringCommandHandler(BaseHandler[str, CCT]):
class StringCommandHandler(BaseHandler[str, CCT, RT]):
"""Handler class to handle string commands. Commands are string updates that start with
``/``. The handler will add a :obj:`list` to the
:class:`CallbackContext` named :attr:`CallbackContext.args`. It will contain a list of strings,
@ -71,7 +71,7 @@ class StringCommandHandler(BaseHandler[str, CCT]):
__slots__ = ("command",)
def __init__(
self,
self: "StringCommandHandler[CCT, RT]",
command: str,
callback: HandlerCallback[str, CCT, RT],
block: DVType[bool] = DEFAULT_TRUE,

View file

@ -32,7 +32,7 @@ if TYPE_CHECKING:
RT = TypeVar("RT")
class StringRegexHandler(BaseHandler[str, CCT]):
class StringRegexHandler(BaseHandler[str, CCT, RT]):
"""Handler class to handle string updates based on a regex which checks the update content.
Read the documentation of the :mod:`re` module for more information. The :func:`re.match`
@ -74,7 +74,7 @@ class StringRegexHandler(BaseHandler[str, CCT]):
__slots__ = ("pattern",)
def __init__(
self,
self: "StringRegexHandler[CCT, RT]",
pattern: Union[str, Pattern[str]],
callback: HandlerCallback[str, CCT, RT],
block: DVType[bool] = DEFAULT_TRUE,

View file

@ -29,7 +29,7 @@ RT = TypeVar("RT")
UT = TypeVar("UT")
class TypeHandler(BaseHandler[UT, CCT]):
class TypeHandler(BaseHandler[UT, CCT, RT]):
"""Handler class to handle updates of custom types.
Warning:
@ -70,7 +70,7 @@ class TypeHandler(BaseHandler[UT, CCT]):
__slots__ = ("strict", "type")
def __init__(
self,
self: "TypeHandler[UT, CCT, RT]",
type: Type[UT], # pylint: disable=redefined-builtin
callback: HandlerCallback[UT, CCT, RT],
strict: bool = False,