Drop Support for Python 3.7 (#3728, #3742, #3749, #3740, #3754, #3753, #3764, #3762, #3759)

Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
Co-authored-by: Luca Bellanti <luca.bellanti@gmail.com>
Co-authored-by: thefunkycat <104379699+thefunkycat@users.noreply.github.com>
Co-authored-by: Aditya Yadav <69784758+clot27@users.noreply.github.com>
Co-authored-by: Dmitry Kolomatskiy <58207913+lemontree210@users.noreply.github.com>
This commit is contained in:
Bibo-Joshi 2023-06-29 18:17:47 +02:00 committed by GitHub
parent 58b89cf0e9
commit fb86bb3417
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
78 changed files with 421 additions and 322 deletions

View file

@ -16,7 +16,7 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
python-version: ['3.8', '3.9', '3.10', '3.11']
os: [ubuntu-latest, windows-latest, macos-latest]
fail-fast: False
steps:
@ -98,7 +98,7 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
python-version: [3.7]
python-version: [3.11]
os: [ubuntu-latest]
fail-fast: False
steps:

View file

@ -67,7 +67,7 @@ repos:
- id: pyupgrade
files: ^(telegram|examples|tests|docs)/.*\.py$
args:
- --py37-plus
- --py38-plus
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:

View file

@ -24,6 +24,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `Abshar <https://github.com/abxhr>`_
- `Alateas <https://github.com/alateas>`_
- `Ales Dokshanin <https://github.com/alesdokshanin>`_
- `Alizia <https://github.com/thefunkycat>`_
- `Ambro17 <https://github.com/Ambro17>`_
- `Andrej Zhilenkov <https://github.com/Andrej730>`_
- `Anton Tagunov <https://github.com/anton-tagunov>`_

View file

@ -77,7 +77,7 @@ Introduction
This library provides a pure Python, asynchronous interface for the
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
It's compatible with Python versions **3.7+**.
It's compatible with Python versions **3.8+**.
In addition to the pure API implementation, this library features a number of high-level classes to
make the development of bots easy and straightforward. These classes are contained in the

View file

@ -77,7 +77,7 @@ Introduction
This library provides a pure Python, asynchronous interface for the
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
It's compatible with Python versions **3.7+**.
It's compatible with Python versions **3.8+**.
``python-telegram-bot-raw`` is part of the `python-telegram-bot <https://python-telegram-bot.org>`_ ecosystem and provides the pure API functionality extracted from PTB. It therefore does not have independent release schedules, changelogs or documentation.

View file

@ -174,8 +174,7 @@ class AdmonitionInserter:
break
for line in lines_with_attrs:
line_match = attr_docstr_pattern.match(line)
if not line_match:
if not (line_match := attr_docstr_pattern.match(line)):
continue
target_attr = line_match.group("attr_name")
@ -529,7 +528,11 @@ class AdmonitionInserter:
# For custom generics like telegram.ext._application.Application[~BT, ~CCT, ~UD...].
# This must come before the check for isinstance(type) because GenericAlias can also be
# recognized as type if it belongs to <class 'types.GenericAlias'>.
elif str(type(arg)) in ("<class 'typing._GenericAlias'>", "<class 'types.GenericAlias'>"):
elif str(type(arg)) in (
"<class 'typing._GenericAlias'>",
"<class 'types.GenericAlias'>",
"<class 'typing._LiteralGenericAlias'>",
):
if "telegram" in str(arg):
# get_origin() of telegram.ext._application.Application[~BT, ~CCT, ~UD...]
# will produce <class 'telegram.ext._application.Application'>

View file

@ -189,8 +189,10 @@ def autodoc_process_bases(app, name, obj, option, bases: list):
bases[idx] = f":class:`{base}`"
# Now convert `telegram._message.Message` to `telegram.Message` etc
match = re.search(pattern=r"(telegram(\.ext|))\.[_\w\.]+", string=base)
if not match or "_utils" in base:
if (
not (match := re.search(pattern=r"(telegram(\.ext|))\.[_\w\.]+", string=base))
or "_utils" in base
):
continue
parts = match.group(0).split(".")

View file

@ -1,6 +1,6 @@
[tool.black]
line-length = 99
target-version = ['py37', 'py38', 'py39', 'py310', 'py311']
target-version = ['py38', 'py39', 'py310', 'py311']
[tool.isort] # black config
profile = "black"
@ -8,7 +8,7 @@ line_length = 99
[tool.ruff]
line-length = 99
target-version = "py37"
target-version = "py38"
show-fixes = true
ignore = ["PLR2004", "PLR0911", "PLR0912", "PLR0913", "PLR0915"]
select = ["E", "F", "I", "PL", "UP", "RUF", "PTH", "C4", "B", "PIE", "SIM", "RET", "RSE",

View file

@ -105,13 +105,12 @@ def get_setup_kwargs(raw=False):
"Topic :: Internet",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
],
python_requires=">=3.7",
python_requires=">=3.8",
)
return kwargs

View file

@ -96,7 +96,14 @@ from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.files import is_local_file, parse_file_input
from telegram._utils.logging import get_logger
from telegram._utils.types import DVInput, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram._utils.types import (
CorrectOptionID,
DVInput,
FileInput,
JSONDict,
ODVInput,
ReplyMarkup,
)
from telegram._utils.warnings import warn
from telegram._utils.warnings_transition import warn_about_thumb_return_thumbnail
from telegram._webhookinfo import WebhookInfo
@ -6602,7 +6609,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
is_anonymous: Optional[bool] = None,
type: Optional[str] = None, # pylint: disable=redefined-builtin
allows_multiple_answers: Optional[bool] = None,
correct_option_id: Optional[int] = None,
correct_option_id: Optional[CorrectOptionID] = None,
is_closed: Optional[bool] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,

View file

@ -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 Bot Command."""
from typing import ClassVar, Optional
from typing import Final, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
@ -61,22 +61,22 @@ class BotCommand(TelegramObject):
self._freeze()
MIN_COMMAND: ClassVar[int] = constants.BotCommandLimit.MIN_COMMAND
MIN_COMMAND: Final[int] = constants.BotCommandLimit.MIN_COMMAND
""":const:`telegram.constants.BotCommandLimit.MIN_COMMAND`
.. versionadded:: 20.0
"""
MAX_COMMAND: ClassVar[int] = constants.BotCommandLimit.MAX_COMMAND
MAX_COMMAND: Final[int] = constants.BotCommandLimit.MAX_COMMAND
""":const:`telegram.constants.BotCommandLimit.MAX_COMMAND`
.. versionadded:: 20.0
"""
MIN_DESCRIPTION: ClassVar[int] = constants.BotCommandLimit.MIN_DESCRIPTION
MIN_DESCRIPTION: Final[int] = constants.BotCommandLimit.MIN_DESCRIPTION
""":const:`telegram.constants.BotCommandLimit.MIN_DESCRIPTION`
.. versionadded:: 20.0
"""
MAX_DESCRIPTION: ClassVar[int] = constants.BotCommandLimit.MAX_DESCRIPTION
MAX_DESCRIPTION: Final[int] = constants.BotCommandLimit.MAX_DESCRIPTION
""":const:`telegram.constants.BotCommandLimit.MAX_DESCRIPTION`
.. versionadded:: 20.0

View file

@ -18,7 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
# pylint: disable=redefined-builtin
"""This module contains objects representing Telegram bot command scopes."""
from typing import TYPE_CHECKING, ClassVar, Dict, Optional, Type, Union
from typing import TYPE_CHECKING, Dict, Final, Optional, Type, Union
from telegram import constants
from telegram._telegramobject import TelegramObject
@ -60,19 +60,19 @@ class BotCommandScope(TelegramObject):
__slots__ = ("type",)
DEFAULT: ClassVar[str] = constants.BotCommandScopeType.DEFAULT
DEFAULT: Final[str] = constants.BotCommandScopeType.DEFAULT
""":const:`telegram.constants.BotCommandScopeType.DEFAULT`"""
ALL_PRIVATE_CHATS: ClassVar[str] = constants.BotCommandScopeType.ALL_PRIVATE_CHATS
ALL_PRIVATE_CHATS: Final[str] = constants.BotCommandScopeType.ALL_PRIVATE_CHATS
""":const:`telegram.constants.BotCommandScopeType.ALL_PRIVATE_CHATS`"""
ALL_GROUP_CHATS: ClassVar[str] = constants.BotCommandScopeType.ALL_GROUP_CHATS
ALL_GROUP_CHATS: Final[str] = constants.BotCommandScopeType.ALL_GROUP_CHATS
""":const:`telegram.constants.BotCommandScopeType.ALL_GROUP_CHATS`"""
ALL_CHAT_ADMINISTRATORS: ClassVar[str] = constants.BotCommandScopeType.ALL_CHAT_ADMINISTRATORS
ALL_CHAT_ADMINISTRATORS: Final[str] = constants.BotCommandScopeType.ALL_CHAT_ADMINISTRATORS
""":const:`telegram.constants.BotCommandScopeType.ALL_CHAT_ADMINISTRATORS`"""
CHAT: ClassVar[str] = constants.BotCommandScopeType.CHAT
CHAT: Final[str] = constants.BotCommandScopeType.CHAT
""":const:`telegram.constants.BotCommandScopeType.CHAT`"""
CHAT_ADMINISTRATORS: ClassVar[str] = constants.BotCommandScopeType.CHAT_ADMINISTRATORS
CHAT_ADMINISTRATORS: Final[str] = constants.BotCommandScopeType.CHAT_ADMINISTRATORS
""":const:`telegram.constants.BotCommandScopeType.CHAT_ADMINISTRATORS`"""
CHAT_MEMBER: ClassVar[str] = constants.BotCommandScopeType.CHAT_MEMBER
CHAT_MEMBER: Final[str] = constants.BotCommandScopeType.CHAT_MEMBER
""":const:`telegram.constants.BotCommandScopeType.CHAT_MEMBER`"""
def __init__(self, type: str, *, api_kwargs: Optional[JSONDict] = None):

View file

@ -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 represent a Telegram bots name."""
from typing import ClassVar, Optional
from typing import Final, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
@ -50,5 +50,5 @@ class BotName(TelegramObject):
self._freeze()
MAX_LENGTH: ClassVar[int] = constants.BotNameLimit.MAX_NAME_LENGTH
MAX_LENGTH: Final[int] = constants.BotNameLimit.MAX_NAME_LENGTH
""":const:`telegram.constants.BotNameLimit.MAX_NAME_LENGTH`"""

View file

@ -18,7 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
# pylint: disable=redefined-builtin
"""This module contains an object that represents a Telegram CallbackQuery"""
from typing import TYPE_CHECKING, ClassVar, Optional, Sequence, Tuple, Union
from typing import TYPE_CHECKING, Final, Optional, Sequence, Tuple, Union
from telegram import constants
from telegram._files.location import Location
@ -771,7 +771,7 @@ class CallbackQuery(TelegramObject):
message_thread_id=message_thread_id,
)
MAX_ANSWER_TEXT_LENGTH: ClassVar[
MAX_ANSWER_TEXT_LENGTH: Final[
int
] = constants.CallbackQueryLimit.ANSWER_CALLBACK_QUERY_TEXT_LENGTH
"""

View file

@ -20,7 +20,7 @@
"""This module contains an object that represents a Telegram Chat."""
from datetime import datetime
from html import escape
from typing import TYPE_CHECKING, ClassVar, Optional, Sequence, Tuple, Union
from typing import TYPE_CHECKING, Final, Optional, Sequence, Tuple, Union
from telegram import constants
from telegram._chatlocation import ChatLocation
@ -32,7 +32,14 @@ from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import DVInput, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram._utils.types import (
CorrectOptionID,
DVInput,
FileInput,
JSONDict,
ODVInput,
ReplyMarkup,
)
from telegram.helpers import escape_markdown
from telegram.helpers import mention_html as helpers_mention_html
from telegram.helpers import mention_markdown as helpers_mention_markdown
@ -303,18 +310,18 @@ class Chat(TelegramObject):
"has_aggressive_anti_spam_enabled",
)
SENDER: ClassVar[str] = constants.ChatType.SENDER
SENDER: Final[str] = constants.ChatType.SENDER
""":const:`telegram.constants.ChatType.SENDER`
.. versionadded:: 13.5
"""
PRIVATE: ClassVar[str] = constants.ChatType.PRIVATE
PRIVATE: Final[str] = constants.ChatType.PRIVATE
""":const:`telegram.constants.ChatType.PRIVATE`"""
GROUP: ClassVar[str] = constants.ChatType.GROUP
GROUP: Final[str] = constants.ChatType.GROUP
""":const:`telegram.constants.ChatType.GROUP`"""
SUPERGROUP: ClassVar[str] = constants.ChatType.SUPERGROUP
SUPERGROUP: Final[str] = constants.ChatType.SUPERGROUP
""":const:`telegram.constants.ChatType.SUPERGROUP`"""
CHANNEL: ClassVar[str] = constants.ChatType.CHANNEL
CHANNEL: Final[str] = constants.ChatType.CHANNEL
""":const:`telegram.constants.ChatType.CHANNEL`"""
def __init__(
@ -2201,7 +2208,7 @@ class Chat(TelegramObject):
is_anonymous: Optional[bool] = None,
type: Optional[str] = None,
allows_multiple_answers: Optional[bool] = None,
correct_option_id: Optional[int] = None,
correct_option_id: Optional[CorrectOptionID] = None,
is_closed: Optional[bool] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,

View file

@ -18,7 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a location to which a chat is connected."""
from typing import TYPE_CHECKING, ClassVar, Optional
from typing import TYPE_CHECKING, Final, Optional
from telegram import constants
from telegram._files.location import Location
@ -79,12 +79,12 @@ class ChatLocation(TelegramObject):
return super().de_json(data=data, bot=bot)
MIN_ADDRESS: ClassVar[int] = constants.LocationLimit.MIN_CHAT_LOCATION_ADDRESS
MIN_ADDRESS: Final[int] = constants.LocationLimit.MIN_CHAT_LOCATION_ADDRESS
""":const:`telegram.constants.LocationLimit.MIN_CHAT_LOCATION_ADDRESS`
.. versionadded:: 20.0
"""
MAX_ADDRESS: ClassVar[int] = constants.LocationLimit.MAX_CHAT_LOCATION_ADDRESS
MAX_ADDRESS: Final[int] = constants.LocationLimit.MAX_CHAT_LOCATION_ADDRESS
""":const:`telegram.constants.LocationLimit.MAX_CHAT_LOCATION_ADDRESS`
.. versionadded:: 20.0

View file

@ -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 ChatMember."""
import datetime
from typing import TYPE_CHECKING, ClassVar, Dict, Optional, Type
from typing import TYPE_CHECKING, Dict, Final, Optional, Type
from telegram import constants
from telegram._telegramobject import TelegramObject
@ -74,17 +74,17 @@ class ChatMember(TelegramObject):
__slots__ = ("user", "status")
ADMINISTRATOR: ClassVar[str] = constants.ChatMemberStatus.ADMINISTRATOR
ADMINISTRATOR: Final[str] = constants.ChatMemberStatus.ADMINISTRATOR
""":const:`telegram.constants.ChatMemberStatus.ADMINISTRATOR`"""
OWNER: ClassVar[str] = constants.ChatMemberStatus.OWNER
OWNER: Final[str] = constants.ChatMemberStatus.OWNER
""":const:`telegram.constants.ChatMemberStatus.OWNER`"""
BANNED: ClassVar[str] = constants.ChatMemberStatus.BANNED
BANNED: Final[str] = constants.ChatMemberStatus.BANNED
""":const:`telegram.constants.ChatMemberStatus.BANNED`"""
LEFT: ClassVar[str] = constants.ChatMemberStatus.LEFT
LEFT: Final[str] = constants.ChatMemberStatus.LEFT
""":const:`telegram.constants.ChatMemberStatus.LEFT`"""
MEMBER: ClassVar[str] = constants.ChatMemberStatus.MEMBER
MEMBER: Final[str] = constants.ChatMemberStatus.MEMBER
""":const:`telegram.constants.ChatMemberStatus.MEMBER`"""
RESTRICTED: ClassVar[str] = constants.ChatMemberStatus.RESTRICTED
RESTRICTED: Final[str] = constants.ChatMemberStatus.RESTRICTED
""":const:`telegram.constants.ChatMemberStatus.RESTRICTED`"""
def __init__(

View file

@ -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 Dice."""
from typing import ClassVar, List, Optional
from typing import Final, List, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
@ -98,62 +98,62 @@ class Dice(TelegramObject):
self._freeze()
DICE: ClassVar[str] = constants.DiceEmoji.DICE # skipcq: PTC-W0052
DICE: Final[str] = constants.DiceEmoji.DICE # skipcq: PTC-W0052
""":const:`telegram.constants.DiceEmoji.DICE`"""
DARTS: ClassVar[str] = constants.DiceEmoji.DARTS
DARTS: Final[str] = constants.DiceEmoji.DARTS
""":const:`telegram.constants.DiceEmoji.DARTS`"""
BASKETBALL: ClassVar[str] = constants.DiceEmoji.BASKETBALL
BASKETBALL: Final[str] = constants.DiceEmoji.BASKETBALL
""":const:`telegram.constants.DiceEmoji.BASKETBALL`"""
FOOTBALL: ClassVar[str] = constants.DiceEmoji.FOOTBALL
FOOTBALL: Final[str] = constants.DiceEmoji.FOOTBALL
""":const:`telegram.constants.DiceEmoji.FOOTBALL`"""
SLOT_MACHINE: ClassVar[str] = constants.DiceEmoji.SLOT_MACHINE
SLOT_MACHINE: Final[str] = constants.DiceEmoji.SLOT_MACHINE
""":const:`telegram.constants.DiceEmoji.SLOT_MACHINE`"""
BOWLING: ClassVar[str] = constants.DiceEmoji.BOWLING
BOWLING: Final[str] = constants.DiceEmoji.BOWLING
"""
:const:`telegram.constants.DiceEmoji.BOWLING`
.. versionadded:: 13.4
"""
ALL_EMOJI: ClassVar[List[str]] = list(constants.DiceEmoji)
ALL_EMOJI: Final[List[str]] = list(constants.DiceEmoji)
"""List[:obj:`str`]: A list of all available dice emoji."""
MIN_VALUE: ClassVar[int] = constants.DiceLimit.MIN_VALUE
MIN_VALUE: Final[int] = constants.DiceLimit.MIN_VALUE
""":const:`telegram.constants.DiceLimit.MIN_VALUE`
.. versionadded:: 20.0
"""
MAX_VALUE_BOWLING: ClassVar[int] = constants.DiceLimit.MAX_VALUE_BOWLING
MAX_VALUE_BOWLING: Final[int] = constants.DiceLimit.MAX_VALUE_BOWLING
""":const:`telegram.constants.DiceLimit.MAX_VALUE_BOWLING`
.. versionadded:: 20.0
"""
MAX_VALUE_DARTS: ClassVar[int] = constants.DiceLimit.MAX_VALUE_DARTS
MAX_VALUE_DARTS: Final[int] = constants.DiceLimit.MAX_VALUE_DARTS
""":const:`telegram.constants.DiceLimit.MAX_VALUE_DARTS`
.. versionadded:: 20.0
"""
MAX_VALUE_DICE: ClassVar[int] = constants.DiceLimit.MAX_VALUE_DICE
MAX_VALUE_DICE: Final[int] = constants.DiceLimit.MAX_VALUE_DICE
""":const:`telegram.constants.DiceLimit.MAX_VALUE_DICE`
.. versionadded:: 20.0
"""
MAX_VALUE_BASKETBALL: ClassVar[int] = constants.DiceLimit.MAX_VALUE_BASKETBALL
MAX_VALUE_BASKETBALL: Final[int] = constants.DiceLimit.MAX_VALUE_BASKETBALL
""":const:`telegram.constants.DiceLimit.MAX_VALUE_BASKETBALL`
.. versionadded:: 20.0
"""
MAX_VALUE_FOOTBALL: ClassVar[int] = constants.DiceLimit.MAX_VALUE_FOOTBALL
MAX_VALUE_FOOTBALL: Final[int] = constants.DiceLimit.MAX_VALUE_FOOTBALL
""":const:`telegram.constants.DiceLimit.MAX_VALUE_FOOTBALL`
.. versionadded:: 20.0
"""
MAX_VALUE_SLOT_MACHINE: ClassVar[int] = constants.DiceLimit.MAX_VALUE_SLOT_MACHINE
MAX_VALUE_SLOT_MACHINE: Final[int] = constants.DiceLimit.MAX_VALUE_SLOT_MACHINE
""":const:`telegram.constants.DiceLimit.MAX_VALUE_SLOT_MACHINE`
.. versionadded:: 20.0

View file

@ -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 ChatPhoto."""
from typing import TYPE_CHECKING, ClassVar, Optional
from typing import TYPE_CHECKING, Final, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
@ -164,12 +164,12 @@ class ChatPhoto(TelegramObject):
api_kwargs=api_kwargs,
)
SIZE_SMALL: ClassVar[int] = constants.ChatPhotoSize.SMALL
SIZE_SMALL: Final[int] = constants.ChatPhotoSize.SMALL
""":const:`telegram.constants.ChatPhotoSize.SMALL`
.. versionadded:: 20.0
"""
SIZE_BIG: ClassVar[int] = constants.ChatPhotoSize.BIG
SIZE_BIG: Final[int] = constants.ChatPhotoSize.BIG
""":const:`telegram.constants.ChatPhotoSize.BIG`
.. versionadded:: 20.0

View file

@ -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, Optional
from typing import Final, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
@ -96,17 +96,17 @@ class Location(TelegramObject):
self._freeze()
HORIZONTAL_ACCURACY: ClassVar[int] = constants.LocationLimit.HORIZONTAL_ACCURACY
HORIZONTAL_ACCURACY: Final[int] = constants.LocationLimit.HORIZONTAL_ACCURACY
""":const:`telegram.constants.LocationLimit.HORIZONTAL_ACCURACY`
.. versionadded:: 20.0
"""
MIN_HEADING: ClassVar[int] = constants.LocationLimit.MIN_HEADING
MIN_HEADING: Final[int] = constants.LocationLimit.MIN_HEADING
""":const:`telegram.constants.LocationLimit.MIN_HEADING`
.. versionadded:: 20.0
"""
MAX_HEADING: ClassVar[int] = constants.LocationLimit.MAX_HEADING
MAX_HEADING: Final[int] = constants.LocationLimit.MAX_HEADING
""":const:`telegram.constants.LocationLimit.MAX_HEADING`
.. versionadded:: 20.0

View file

@ -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, Tuple
from typing import TYPE_CHECKING, Final, Optional, Sequence, Tuple
from telegram import constants
from telegram._files._basethumbedmedium import _BaseThumbedMedium
@ -192,11 +192,11 @@ class Sticker(_BaseThumbedMedium):
self.custom_emoji_id: Optional[str] = custom_emoji_id
self.needs_repainting: Optional[bool] = needs_repainting
REGULAR: ClassVar[str] = constants.StickerType.REGULAR
REGULAR: Final[str] = constants.StickerType.REGULAR
""":const:`telegram.constants.StickerType.REGULAR`"""
MASK: ClassVar[str] = constants.StickerType.MASK
MASK: Final[str] = constants.StickerType.MASK
""":const:`telegram.constants.StickerType.MASK`"""
CUSTOM_EMOJI: ClassVar[str] = constants.StickerType.CUSTOM_EMOJI
CUSTOM_EMOJI: Final[str] = constants.StickerType.CUSTOM_EMOJI
""":const:`telegram.constants.StickerType.CUSTOM_EMOJI`"""
@classmethod
@ -390,13 +390,13 @@ class MaskPosition(TelegramObject):
__slots__ = ("point", "scale", "x_shift", "y_shift")
FOREHEAD: ClassVar[str] = constants.MaskPosition.FOREHEAD
FOREHEAD: Final[str] = constants.MaskPosition.FOREHEAD
""":const:`telegram.constants.MaskPosition.FOREHEAD`"""
EYES: ClassVar[str] = constants.MaskPosition.EYES
EYES: Final[str] = constants.MaskPosition.EYES
""":const:`telegram.constants.MaskPosition.EYES`"""
MOUTH: ClassVar[str] = constants.MaskPosition.MOUTH
MOUTH: Final[str] = constants.MaskPosition.MOUTH
""":const:`telegram.constants.MaskPosition.MOUTH`"""
CHIN: ClassVar[str] = constants.MaskPosition.CHIN
CHIN: Final[str] = constants.MaskPosition.CHIN
""":const:`telegram.constants.MaskPosition.CHIN`"""
def __init__(

View file

@ -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, Optional
from typing import Final, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
@ -93,12 +93,12 @@ class ForceReply(TelegramObject):
self._freeze()
MIN_INPUT_FIELD_PLACEHOLDER: ClassVar[int] = constants.ReplyLimit.MIN_INPUT_FIELD_PLACEHOLDER
MIN_INPUT_FIELD_PLACEHOLDER: Final[int] = constants.ReplyLimit.MIN_INPUT_FIELD_PLACEHOLDER
""":const:`telegram.constants.ReplyLimit.MIN_INPUT_FIELD_PLACEHOLDER`
.. versionadded:: 20.0
"""
MAX_INPUT_FIELD_PLACEHOLDER: ClassVar[int] = constants.ReplyLimit.MAX_INPUT_FIELD_PLACEHOLDER
MAX_INPUT_FIELD_PLACEHOLDER: Final[int] = constants.ReplyLimit.MAX_INPUT_FIELD_PLACEHOLDER
""":const:`telegram.constants.ReplyLimit.MAX_INPUT_FIELD_PLACEHOLDER`
.. versionadded:: 20.0

View file

@ -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 InlineKeyboardButton."""
from typing import TYPE_CHECKING, ClassVar, Optional, Union
from typing import TYPE_CHECKING, Final, Optional, Union
from telegram import constants
from telegram._games.callbackgame import CallbackGame
@ -297,12 +297,12 @@ class InlineKeyboardButton(TelegramObject):
self.callback_data = callback_data
self._set_id_attrs()
MIN_CALLBACK_DATA: ClassVar[int] = constants.InlineKeyboardButtonLimit.MIN_CALLBACK_DATA
MIN_CALLBACK_DATA: Final[int] = constants.InlineKeyboardButtonLimit.MIN_CALLBACK_DATA
""":const:`telegram.constants.InlineKeyboardButtonLimit.MIN_CALLBACK_DATA`
.. versionadded:: 20.0
"""
MAX_CALLBACK_DATA: ClassVar[int] = constants.InlineKeyboardButtonLimit.MAX_CALLBACK_DATA
MAX_CALLBACK_DATA: Final[int] = constants.InlineKeyboardButtonLimit.MAX_CALLBACK_DATA
""":const:`telegram.constants.InlineKeyboardButtonLimit.MAX_CALLBACK_DATA`
.. versionadded:: 20.0

View file

@ -19,7 +19,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram InlineQuery."""
from typing import TYPE_CHECKING, Callable, ClassVar, Optional, Sequence, Union
from typing import TYPE_CHECKING, Callable, Final, Optional, Sequence, Union
from telegram import constants
from telegram._files.location import Location
@ -202,27 +202,27 @@ class InlineQuery(TelegramObject):
api_kwargs=api_kwargs,
)
MAX_RESULTS: ClassVar[int] = constants.InlineQueryLimit.RESULTS
MAX_RESULTS: Final[int] = constants.InlineQueryLimit.RESULTS
""":const:`telegram.constants.InlineQueryLimit.RESULTS`
.. versionadded:: 13.2
"""
MIN_SWITCH_PM_TEXT_LENGTH: ClassVar[int] = constants.InlineQueryLimit.MIN_SWITCH_PM_TEXT_LENGTH
MIN_SWITCH_PM_TEXT_LENGTH: Final[int] = constants.InlineQueryLimit.MIN_SWITCH_PM_TEXT_LENGTH
""":const:`telegram.constants.InlineQueryLimit.MIN_SWITCH_PM_TEXT_LENGTH`
.. versionadded:: 20.0
"""
MAX_SWITCH_PM_TEXT_LENGTH: ClassVar[int] = constants.InlineQueryLimit.MAX_SWITCH_PM_TEXT_LENGTH
MAX_SWITCH_PM_TEXT_LENGTH: Final[int] = constants.InlineQueryLimit.MAX_SWITCH_PM_TEXT_LENGTH
""":const:`telegram.constants.InlineQueryLimit.MAX_SWITCH_PM_TEXT_LENGTH`
.. versionadded:: 20.0
"""
MAX_OFFSET_LENGTH: ClassVar[int] = constants.InlineQueryLimit.MAX_OFFSET_LENGTH
MAX_OFFSET_LENGTH: Final[int] = constants.InlineQueryLimit.MAX_OFFSET_LENGTH
""":const:`telegram.constants.InlineQueryLimit.MAX_OFFSET_LENGTH`
.. versionadded:: 20.0
"""
MAX_QUERY_LENGTH: ClassVar[int] = constants.InlineQueryLimit.MAX_QUERY_LENGTH
MAX_QUERY_LENGTH: Final[int] = constants.InlineQueryLimit.MAX_QUERY_LENGTH
""":const:`telegram.constants.InlineQueryLimit.MAX_QUERY_LENGTH`
.. versionadded:: 20.0

View file

@ -19,7 +19,7 @@
# pylint: disable=redefined-builtin
"""This module contains the classes that represent Telegram InlineQueryResult."""
from typing import ClassVar, Optional
from typing import Final, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
@ -66,12 +66,12 @@ class InlineQueryResult(TelegramObject):
self._freeze()
MIN_ID_LENGTH: ClassVar[int] = constants.InlineQueryResultLimit.MIN_ID_LENGTH
MIN_ID_LENGTH: Final[int] = constants.InlineQueryResultLimit.MIN_ID_LENGTH
""":const:`telegram.constants.InlineQueryResultLimit.MIN_ID_LENGTH`
.. versionadded:: 20.0
"""
MAX_ID_LENGTH: ClassVar[int] = constants.InlineQueryResultLimit.MAX_ID_LENGTH
MAX_ID_LENGTH: Final[int] = constants.InlineQueryResultLimit.MAX_ID_LENGTH
""":const:`telegram.constants.InlineQueryResultLimit.MAX_ID_LENGTH`
.. versionadded:: 20.0

View file

@ -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, Optional
from typing import TYPE_CHECKING, Final, Optional
from telegram import constants
from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
@ -244,37 +244,37 @@ class InlineQueryResultLocation(InlineQueryResult):
)
return self.thumbnail_height
HORIZONTAL_ACCURACY: ClassVar[int] = constants.LocationLimit.HORIZONTAL_ACCURACY
HORIZONTAL_ACCURACY: Final[int] = constants.LocationLimit.HORIZONTAL_ACCURACY
""":const:`telegram.constants.LocationLimit.HORIZONTAL_ACCURACY`
.. versionadded:: 20.0
"""
MIN_HEADING: ClassVar[int] = constants.LocationLimit.MIN_HEADING
MIN_HEADING: Final[int] = constants.LocationLimit.MIN_HEADING
""":const:`telegram.constants.LocationLimit.MIN_HEADING`
.. versionadded:: 20.0
"""
MAX_HEADING: ClassVar[int] = constants.LocationLimit.MAX_HEADING
MAX_HEADING: Final[int] = constants.LocationLimit.MAX_HEADING
""":const:`telegram.constants.LocationLimit.MAX_HEADING`
.. versionadded:: 20.0
"""
MIN_LIVE_PERIOD: ClassVar[int] = constants.LocationLimit.MIN_LIVE_PERIOD
MIN_LIVE_PERIOD: Final[int] = constants.LocationLimit.MIN_LIVE_PERIOD
""":const:`telegram.constants.LocationLimit.MIN_LIVE_PERIOD`
.. versionadded:: 20.0
"""
MAX_LIVE_PERIOD: ClassVar[int] = constants.LocationLimit.MAX_LIVE_PERIOD
MAX_LIVE_PERIOD: Final[int] = constants.LocationLimit.MAX_LIVE_PERIOD
""":const:`telegram.constants.LocationLimit.MAX_LIVE_PERIOD`
.. versionadded:: 20.0
"""
MIN_PROXIMITY_ALERT_RADIUS: ClassVar[int] = constants.LocationLimit.MIN_PROXIMITY_ALERT_RADIUS
MIN_PROXIMITY_ALERT_RADIUS: Final[int] = constants.LocationLimit.MIN_PROXIMITY_ALERT_RADIUS
""":const:`telegram.constants.LocationLimit.MIN_PROXIMITY_ALERT_RADIUS`
.. versionadded:: 20.0
"""
MAX_PROXIMITY_ALERT_RADIUS: ClassVar[int] = constants.LocationLimit.MAX_PROXIMITY_ALERT_RADIUS
MAX_PROXIMITY_ALERT_RADIUS: Final[int] = constants.LocationLimit.MAX_PROXIMITY_ALERT_RADIUS
""":const:`telegram.constants.LocationLimit.MAX_PROXIMITY_ALERT_RADIUS`
.. versionadded:: 20.0

View file

@ -19,7 +19,7 @@
# pylint: disable=redefined-builtin
"""This module contains the class that represent a Telegram InlineQueryResultsButton."""
from typing import TYPE_CHECKING, ClassVar, Optional
from typing import TYPE_CHECKING, Final, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
@ -107,11 +107,11 @@ class InlineQueryResultsButton(TelegramObject):
return super().de_json(data=data, bot=bot)
MIN_START_PARAMETER_LENGTH: ClassVar[
MIN_START_PARAMETER_LENGTH: Final[
int
] = constants.InlineQueryResultsButtonLimit.MIN_START_PARAMETER_LENGTH
""":const:`telegram.constants.InlineQueryResultsButtonLimit.MIN_START_PARAMETER_LENGTH`"""
MAX_START_PARAMETER_LENGTH: ClassVar[
MAX_START_PARAMETER_LENGTH: Final[
int
] = constants.InlineQueryResultsButtonLimit.MAX_START_PARAMETER_LENGTH
""":const:`telegram.constants.InlineQueryResultsButtonLimit.MAX_START_PARAMETER_LENGTH`"""

View file

@ -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, Optional
from typing import Final, Optional
from telegram import constants
from telegram._inline.inputmessagecontent import InputMessageContent
@ -106,37 +106,37 @@ class InputLocationMessageContent(InputMessageContent):
self._id_attrs = (self.latitude, self.longitude)
HORIZONTAL_ACCURACY: ClassVar[int] = constants.LocationLimit.HORIZONTAL_ACCURACY
HORIZONTAL_ACCURACY: Final[int] = constants.LocationLimit.HORIZONTAL_ACCURACY
""":const:`telegram.constants.LocationLimit.HORIZONTAL_ACCURACY`
.. versionadded:: 20.0
"""
MIN_HEADING: ClassVar[int] = constants.LocationLimit.MIN_HEADING
MIN_HEADING: Final[int] = constants.LocationLimit.MIN_HEADING
""":const:`telegram.constants.LocationLimit.MIN_HEADING`
.. versionadded:: 20.0
"""
MAX_HEADING: ClassVar[int] = constants.LocationLimit.MAX_HEADING
MAX_HEADING: Final[int] = constants.LocationLimit.MAX_HEADING
""":const:`telegram.constants.LocationLimit.MAX_HEADING`
.. versionadded:: 20.0
"""
MIN_LIVE_PERIOD: ClassVar[int] = constants.LocationLimit.MIN_LIVE_PERIOD
MIN_LIVE_PERIOD: Final[int] = constants.LocationLimit.MIN_LIVE_PERIOD
""":const:`telegram.constants.LocationLimit.MIN_LIVE_PERIOD`
.. versionadded:: 20.0
"""
MAX_LIVE_PERIOD: ClassVar[int] = constants.LocationLimit.MAX_LIVE_PERIOD
MAX_LIVE_PERIOD: Final[int] = constants.LocationLimit.MAX_LIVE_PERIOD
""":const:`telegram.constants.LocationLimit.MAX_LIVE_PERIOD`
.. versionadded:: 20.0
"""
MIN_PROXIMITY_ALERT_RADIUS: ClassVar[int] = constants.LocationLimit.MIN_PROXIMITY_ALERT_RADIUS
MIN_PROXIMITY_ALERT_RADIUS: Final[int] = constants.LocationLimit.MIN_PROXIMITY_ALERT_RADIUS
""":const:`telegram.constants.LocationLimit.MIN_PROXIMITY_ALERT_RADIUS`
.. versionadded:: 20.0
"""
MAX_PROXIMITY_ALERT_RADIUS: ClassVar[int] = constants.LocationLimit.MAX_PROXIMITY_ALERT_RADIUS
MAX_PROXIMITY_ALERT_RADIUS: Final[int] = constants.LocationLimit.MAX_PROXIMITY_ALERT_RADIUS
""":const:`telegram.constants.LocationLimit.MAX_PROXIMITY_ALERT_RADIUS`
.. versionadded:: 20.0

View file

@ -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 related to Telegram menu buttons."""
from typing import TYPE_CHECKING, ClassVar, Dict, Optional, Type
from typing import TYPE_CHECKING, Dict, Final, Optional, Type
from telegram import constants
from telegram._telegramobject import TelegramObject
@ -95,11 +95,11 @@ class MenuButton(TelegramObject):
return _class_mapping[data.pop("type")].de_json(data, bot=bot)
return super().de_json(data=data, bot=bot)
COMMANDS: ClassVar[str] = constants.MenuButtonType.COMMANDS
COMMANDS: Final[str] = constants.MenuButtonType.COMMANDS
""":const:`telegram.constants.MenuButtonType.COMMANDS`"""
WEB_APP: ClassVar[str] = constants.MenuButtonType.WEB_APP
WEB_APP: Final[str] = constants.MenuButtonType.WEB_APP
""":const:`telegram.constants.MenuButtonType.WEB_APP`"""
DEFAULT: ClassVar[str] = constants.MenuButtonType.DEFAULT
DEFAULT: Final[str] = constants.MenuButtonType.DEFAULT
""":const:`telegram.constants.MenuButtonType.DEFAULT`"""

View file

@ -58,7 +58,15 @@ from telegram._user import User
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.types import DVInput, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram._utils.types import (
CorrectOptionID,
DVInput,
FileInput,
JSONDict,
MarkdownVersion,
ODVInput,
ReplyMarkup,
)
from telegram._utils.warnings import warn
from telegram._videochat import (
VideoChatEnded,
@ -2023,7 +2031,7 @@ class Message(TelegramObject):
is_anonymous: Optional[bool] = None,
type: Optional[str] = None, # pylint: disable=redefined-builtin
allows_multiple_answers: Optional[bool] = None,
correct_option_id: Optional[int] = None,
correct_option_id: Optional[CorrectOptionID] = None,
is_closed: Optional[bool] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -3442,10 +3450,10 @@ class Message(TelegramObject):
message_text: Optional[str],
entities: Dict[MessageEntity, str],
urled: bool = False,
version: int = 1,
version: MarkdownVersion = 1,
offset: int = 0,
) -> Optional[str]:
version = int(version)
version = int(version) # type: ignore
if message_text is None:
return None

View file

@ -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 MessageEntity."""
from typing import TYPE_CHECKING, ClassVar, List, Optional
from typing import TYPE_CHECKING, Final, List, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
@ -130,45 +130,45 @@ class MessageEntity(TelegramObject):
return super().de_json(data=data, bot=bot)
MENTION: ClassVar[str] = constants.MessageEntityType.MENTION
MENTION: Final[str] = constants.MessageEntityType.MENTION
""":const:`telegram.constants.MessageEntityType.MENTION`"""
HASHTAG: ClassVar[str] = constants.MessageEntityType.HASHTAG
HASHTAG: Final[str] = constants.MessageEntityType.HASHTAG
""":const:`telegram.constants.MessageEntityType.HASHTAG`"""
CASHTAG: ClassVar[str] = constants.MessageEntityType.CASHTAG
CASHTAG: Final[str] = constants.MessageEntityType.CASHTAG
""":const:`telegram.constants.MessageEntityType.CASHTAG`"""
PHONE_NUMBER: ClassVar[str] = constants.MessageEntityType.PHONE_NUMBER
PHONE_NUMBER: Final[str] = constants.MessageEntityType.PHONE_NUMBER
""":const:`telegram.constants.MessageEntityType.PHONE_NUMBER`"""
BOT_COMMAND: ClassVar[str] = constants.MessageEntityType.BOT_COMMAND
BOT_COMMAND: Final[str] = constants.MessageEntityType.BOT_COMMAND
""":const:`telegram.constants.MessageEntityType.BOT_COMMAND`"""
URL: ClassVar[str] = constants.MessageEntityType.URL
URL: Final[str] = constants.MessageEntityType.URL
""":const:`telegram.constants.MessageEntityType.URL`"""
EMAIL: ClassVar[str] = constants.MessageEntityType.EMAIL
EMAIL: Final[str] = constants.MessageEntityType.EMAIL
""":const:`telegram.constants.MessageEntityType.EMAIL`"""
BOLD: ClassVar[str] = constants.MessageEntityType.BOLD
BOLD: Final[str] = constants.MessageEntityType.BOLD
""":const:`telegram.constants.MessageEntityType.BOLD`"""
ITALIC: ClassVar[str] = constants.MessageEntityType.ITALIC
ITALIC: Final[str] = constants.MessageEntityType.ITALIC
""":const:`telegram.constants.MessageEntityType.ITALIC`"""
CODE: ClassVar[str] = constants.MessageEntityType.CODE
CODE: Final[str] = constants.MessageEntityType.CODE
""":const:`telegram.constants.MessageEntityType.CODE`"""
PRE: ClassVar[str] = constants.MessageEntityType.PRE
PRE: Final[str] = constants.MessageEntityType.PRE
""":const:`telegram.constants.MessageEntityType.PRE`"""
TEXT_LINK: ClassVar[str] = constants.MessageEntityType.TEXT_LINK
TEXT_LINK: Final[str] = constants.MessageEntityType.TEXT_LINK
""":const:`telegram.constants.MessageEntityType.TEXT_LINK`"""
TEXT_MENTION: ClassVar[str] = constants.MessageEntityType.TEXT_MENTION
TEXT_MENTION: Final[str] = constants.MessageEntityType.TEXT_MENTION
""":const:`telegram.constants.MessageEntityType.TEXT_MENTION`"""
UNDERLINE: ClassVar[str] = constants.MessageEntityType.UNDERLINE
UNDERLINE: Final[str] = constants.MessageEntityType.UNDERLINE
""":const:`telegram.constants.MessageEntityType.UNDERLINE`"""
STRIKETHROUGH: ClassVar[str] = constants.MessageEntityType.STRIKETHROUGH
STRIKETHROUGH: Final[str] = constants.MessageEntityType.STRIKETHROUGH
""":const:`telegram.constants.MessageEntityType.STRIKETHROUGH`"""
SPOILER: ClassVar[str] = constants.MessageEntityType.SPOILER
SPOILER: Final[str] = constants.MessageEntityType.SPOILER
""":const:`telegram.constants.MessageEntityType.SPOILER`
.. versionadded:: 13.10
"""
CUSTOM_EMOJI: ClassVar[str] = constants.MessageEntityType.CUSTOM_EMOJI
CUSTOM_EMOJI: Final[str] = constants.MessageEntityType.CUSTOM_EMOJI
""":const:`telegram.constants.MessageEntityType.CUSTOM_EMOJI`
.. versionadded:: 20.0
"""
ALL_TYPES: ClassVar[List[str]] = list(constants.MessageEntityType)
ALL_TYPES: Final[List[str]] = list(constants.MessageEntityType)
"""List[:obj:`str`]: A list of all available message entity types."""

View file

@ -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 Invoice."""
from typing import ClassVar, Optional
from typing import Final, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
@ -95,37 +95,37 @@ class Invoice(TelegramObject):
self._freeze()
MIN_TITLE_LENGTH: ClassVar[int] = constants.InvoiceLimit.MIN_TITLE_LENGTH
MIN_TITLE_LENGTH: Final[int] = constants.InvoiceLimit.MIN_TITLE_LENGTH
""":const:`telegram.constants.InvoiceLimit.MIN_TITLE_LENGTH`
.. versionadded:: 20.0
"""
MAX_TITLE_LENGTH: ClassVar[int] = constants.InvoiceLimit.MAX_TITLE_LENGTH
MAX_TITLE_LENGTH: Final[int] = constants.InvoiceLimit.MAX_TITLE_LENGTH
""":const:`telegram.constants.InvoiceLimit.MAX_TITLE_LENGTH`
.. versionadded:: 20.0
"""
MIN_DESCRIPTION_LENGTH: ClassVar[int] = constants.InvoiceLimit.MIN_DESCRIPTION_LENGTH
MIN_DESCRIPTION_LENGTH: Final[int] = constants.InvoiceLimit.MIN_DESCRIPTION_LENGTH
""":const:`telegram.constants.InvoiceLimit.MIN_DESCRIPTION_LENGTH`
.. versionadded:: 20.0
"""
MAX_DESCRIPTION_LENGTH: ClassVar[int] = constants.InvoiceLimit.MAX_DESCRIPTION_LENGTH
MAX_DESCRIPTION_LENGTH: Final[int] = constants.InvoiceLimit.MAX_DESCRIPTION_LENGTH
""":const:`telegram.constants.InvoiceLimit.MAX_DESCRIPTION_LENGTH`
.. versionadded:: 20.0
"""
MIN_PAYLOAD_LENGTH: ClassVar[int] = constants.InvoiceLimit.MIN_PAYLOAD_LENGTH
MIN_PAYLOAD_LENGTH: Final[int] = constants.InvoiceLimit.MIN_PAYLOAD_LENGTH
""":const:`telegram.constants.InvoiceLimit.MIN_PAYLOAD_LENGTH`
.. versionadded:: 20.0
"""
MAX_PAYLOAD_LENGTH: ClassVar[int] = constants.InvoiceLimit.MAX_PAYLOAD_LENGTH
MAX_PAYLOAD_LENGTH: Final[int] = constants.InvoiceLimit.MAX_PAYLOAD_LENGTH
""":const:`telegram.constants.InvoiceLimit.MAX_PAYLOAD_LENGTH`
.. versionadded:: 20.0
"""
MAX_TIP_AMOUNTS: ClassVar[int] = constants.InvoiceLimit.MAX_TIP_AMOUNTS
MAX_TIP_AMOUNTS: Final[int] = constants.InvoiceLimit.MAX_TIP_AMOUNTS
""":const:`telegram.constants.InvoiceLimit.MAX_TIP_AMOUNTS`
.. versionadded:: 20.0

View file

@ -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 Poll."""
import datetime
from typing import TYPE_CHECKING, ClassVar, Dict, List, Optional, Sequence, Tuple
from typing import TYPE_CHECKING, Dict, Final, List, Optional, Sequence, Tuple
from telegram import constants
from telegram._messageentity import MessageEntity
@ -65,12 +65,12 @@ class PollOption(TelegramObject):
self._freeze()
MIN_LENGTH: ClassVar[int] = constants.PollLimit.MIN_OPTION_LENGTH
MIN_LENGTH: Final[int] = constants.PollLimit.MIN_OPTION_LENGTH
""":const:`telegram.constants.PollLimit.MIN_OPTION_LENGTH`
.. versionadded:: 20.0
"""
MAX_LENGTH: ClassVar[int] = constants.PollLimit.MAX_OPTION_LENGTH
MAX_LENGTH: Final[int] = constants.PollLimit.MAX_OPTION_LENGTH
""":const:`telegram.constants.PollLimit.MAX_OPTION_LENGTH`
.. versionadded:: 20.0
@ -350,56 +350,56 @@ class Poll(TelegramObject):
if entity.type in types
}
REGULAR: ClassVar[str] = constants.PollType.REGULAR
REGULAR: Final[str] = constants.PollType.REGULAR
""":const:`telegram.constants.PollType.REGULAR`"""
QUIZ: ClassVar[str] = constants.PollType.QUIZ
QUIZ: Final[str] = constants.PollType.QUIZ
""":const:`telegram.constants.PollType.QUIZ`"""
MAX_EXPLANATION_LENGTH: ClassVar[int] = constants.PollLimit.MAX_EXPLANATION_LENGTH
MAX_EXPLANATION_LENGTH: Final[int] = constants.PollLimit.MAX_EXPLANATION_LENGTH
""":const:`telegram.constants.PollLimit.MAX_EXPLANATION_LENGTH`
.. versionadded:: 20.0
"""
MAX_EXPLANATION_LINE_FEEDS: ClassVar[int] = constants.PollLimit.MAX_EXPLANATION_LINE_FEEDS
MAX_EXPLANATION_LINE_FEEDS: Final[int] = constants.PollLimit.MAX_EXPLANATION_LINE_FEEDS
""":const:`telegram.constants.PollLimit.MAX_EXPLANATION_LINE_FEEDS`
.. versionadded:: 20.0
"""
MIN_OPEN_PERIOD: ClassVar[int] = constants.PollLimit.MIN_OPEN_PERIOD
MIN_OPEN_PERIOD: Final[int] = constants.PollLimit.MIN_OPEN_PERIOD
""":const:`telegram.constants.PollLimit.MIN_OPEN_PERIOD`
.. versionadded:: 20.0
"""
MAX_OPEN_PERIOD: ClassVar[int] = constants.PollLimit.MAX_OPEN_PERIOD
MAX_OPEN_PERIOD: Final[int] = constants.PollLimit.MAX_OPEN_PERIOD
""":const:`telegram.constants.PollLimit.MAX_OPEN_PERIOD`
.. versionadded:: 20.0
"""
MIN_QUESTION_LENGTH: ClassVar[int] = constants.PollLimit.MIN_QUESTION_LENGTH
MIN_QUESTION_LENGTH: Final[int] = constants.PollLimit.MIN_QUESTION_LENGTH
""":const:`telegram.constants.PollLimit.MIN_QUESTION_LENGTH`
.. versionadded:: 20.0
"""
MAX_QUESTION_LENGTH: ClassVar[int] = constants.PollLimit.MAX_QUESTION_LENGTH
MAX_QUESTION_LENGTH: Final[int] = constants.PollLimit.MAX_QUESTION_LENGTH
""":const:`telegram.constants.PollLimit.MAX_QUESTION_LENGTH`
.. versionadded:: 20.0
"""
MIN_OPTION_LENGTH: ClassVar[int] = constants.PollLimit.MIN_OPTION_LENGTH
MIN_OPTION_LENGTH: Final[int] = constants.PollLimit.MIN_OPTION_LENGTH
""":const:`telegram.constants.PollLimit.MIN_OPTION_LENGTH`
.. versionadded:: 20.0
"""
MAX_OPTION_LENGTH: ClassVar[int] = constants.PollLimit.MAX_OPTION_LENGTH
MAX_OPTION_LENGTH: Final[int] = constants.PollLimit.MAX_OPTION_LENGTH
""":const:`telegram.constants.PollLimit.MAX_OPTION_LENGTH`
.. versionadded:: 20.0
"""
MIN_OPTION_NUMBER: ClassVar[int] = constants.PollLimit.MIN_OPTION_NUMBER
MIN_OPTION_NUMBER: Final[int] = constants.PollLimit.MIN_OPTION_NUMBER
""":const:`telegram.constants.PollLimit.MIN_OPTION_NUMBER`
.. versionadded:: 20.0
"""
MAX_OPTION_NUMBER: ClassVar[int] = constants.PollLimit.MAX_OPTION_NUMBER
MAX_OPTION_NUMBER: Final[int] = constants.PollLimit.MAX_OPTION_NUMBER
""":const:`telegram.constants.PollLimit.MAX_OPTION_NUMBER`
.. versionadded:: 20.0

View file

@ -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, Optional, Sequence, Tuple, Union
from typing import Final, Optional, Sequence, Tuple, Union
from telegram import constants
from telegram._keyboardbutton import KeyboardButton
@ -346,12 +346,12 @@ class ReplyKeyboardMarkup(TelegramObject):
**kwargs, # type: ignore[arg-type]
)
MIN_INPUT_FIELD_PLACEHOLDER: ClassVar[int] = constants.ReplyLimit.MIN_INPUT_FIELD_PLACEHOLDER
MIN_INPUT_FIELD_PLACEHOLDER: Final[int] = constants.ReplyLimit.MIN_INPUT_FIELD_PLACEHOLDER
""":const:`telegram.constants.ReplyLimit.MIN_INPUT_FIELD_PLACEHOLDER`
.. versionadded:: 20.0
"""
MAX_INPUT_FIELD_PLACEHOLDER: ClassVar[int] = constants.ReplyLimit.MAX_INPUT_FIELD_PLACEHOLDER
MAX_INPUT_FIELD_PLACEHOLDER: Final[int] = constants.ReplyLimit.MAX_INPUT_FIELD_PLACEHOLDER
""":const:`telegram.constants.ReplyLimit.MAX_INPUT_FIELD_PLACEHOLDER`
.. versionadded:: 20.0

View file

@ -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 Update."""
from typing import TYPE_CHECKING, ClassVar, List, Optional
from typing import TYPE_CHECKING, Final, List, Optional
from telegram import constants
from telegram._callbackquery import CallbackQuery
@ -171,63 +171,63 @@ class Update(TelegramObject):
"chat_join_request",
)
MESSAGE: ClassVar[str] = constants.UpdateType.MESSAGE
MESSAGE: Final[str] = constants.UpdateType.MESSAGE
""":const:`telegram.constants.UpdateType.MESSAGE`
.. versionadded:: 13.5"""
EDITED_MESSAGE: ClassVar[str] = constants.UpdateType.EDITED_MESSAGE
EDITED_MESSAGE: Final[str] = constants.UpdateType.EDITED_MESSAGE
""":const:`telegram.constants.UpdateType.EDITED_MESSAGE`
.. versionadded:: 13.5"""
CHANNEL_POST: ClassVar[str] = constants.UpdateType.CHANNEL_POST
CHANNEL_POST: Final[str] = constants.UpdateType.CHANNEL_POST
""":const:`telegram.constants.UpdateType.CHANNEL_POST`
.. versionadded:: 13.5"""
EDITED_CHANNEL_POST: ClassVar[str] = constants.UpdateType.EDITED_CHANNEL_POST
EDITED_CHANNEL_POST: Final[str] = constants.UpdateType.EDITED_CHANNEL_POST
""":const:`telegram.constants.UpdateType.EDITED_CHANNEL_POST`
.. versionadded:: 13.5"""
INLINE_QUERY: ClassVar[str] = constants.UpdateType.INLINE_QUERY
INLINE_QUERY: Final[str] = constants.UpdateType.INLINE_QUERY
""":const:`telegram.constants.UpdateType.INLINE_QUERY`
.. versionadded:: 13.5"""
CHOSEN_INLINE_RESULT: ClassVar[str] = constants.UpdateType.CHOSEN_INLINE_RESULT
CHOSEN_INLINE_RESULT: Final[str] = constants.UpdateType.CHOSEN_INLINE_RESULT
""":const:`telegram.constants.UpdateType.CHOSEN_INLINE_RESULT`
.. versionadded:: 13.5"""
CALLBACK_QUERY: ClassVar[str] = constants.UpdateType.CALLBACK_QUERY
CALLBACK_QUERY: Final[str] = constants.UpdateType.CALLBACK_QUERY
""":const:`telegram.constants.UpdateType.CALLBACK_QUERY`
.. versionadded:: 13.5"""
SHIPPING_QUERY: ClassVar[str] = constants.UpdateType.SHIPPING_QUERY
SHIPPING_QUERY: Final[str] = constants.UpdateType.SHIPPING_QUERY
""":const:`telegram.constants.UpdateType.SHIPPING_QUERY`
.. versionadded:: 13.5"""
PRE_CHECKOUT_QUERY: ClassVar[str] = constants.UpdateType.PRE_CHECKOUT_QUERY
PRE_CHECKOUT_QUERY: Final[str] = constants.UpdateType.PRE_CHECKOUT_QUERY
""":const:`telegram.constants.UpdateType.PRE_CHECKOUT_QUERY`
.. versionadded:: 13.5"""
POLL: ClassVar[str] = constants.UpdateType.POLL
POLL: Final[str] = constants.UpdateType.POLL
""":const:`telegram.constants.UpdateType.POLL`
.. versionadded:: 13.5"""
POLL_ANSWER: ClassVar[str] = constants.UpdateType.POLL_ANSWER
POLL_ANSWER: Final[str] = constants.UpdateType.POLL_ANSWER
""":const:`telegram.constants.UpdateType.POLL_ANSWER`
.. versionadded:: 13.5"""
MY_CHAT_MEMBER: ClassVar[str] = constants.UpdateType.MY_CHAT_MEMBER
MY_CHAT_MEMBER: Final[str] = constants.UpdateType.MY_CHAT_MEMBER
""":const:`telegram.constants.UpdateType.MY_CHAT_MEMBER`
.. versionadded:: 13.5"""
CHAT_MEMBER: ClassVar[str] = constants.UpdateType.CHAT_MEMBER
CHAT_MEMBER: Final[str] = constants.UpdateType.CHAT_MEMBER
""":const:`telegram.constants.UpdateType.CHAT_MEMBER`
.. versionadded:: 13.5"""
CHAT_JOIN_REQUEST = constants.UpdateType.CHAT_JOIN_REQUEST
CHAT_JOIN_REQUEST: Final[str] = constants.UpdateType.CHAT_JOIN_REQUEST
""":const:`telegram.constants.UpdateType.CHAT_JOIN_REQUEST`
.. versionadded:: 13.8"""
ALL_TYPES: ClassVar[List[str]] = list(constants.UpdateType)
ALL_TYPES: Final[List[str]] = list(constants.UpdateType)
"""List[:obj:`str`]: A list of all available update types.
.. versionadded:: 13.5"""

View file

@ -25,7 +25,14 @@ from telegram._inline.inlinekeyboardbutton import InlineKeyboardButton
from telegram._menubutton import MenuButton
from telegram._telegramobject import TelegramObject
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import DVInput, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram._utils.types import (
CorrectOptionID,
DVInput,
FileInput,
JSONDict,
ODVInput,
ReplyMarkup,
)
from telegram.helpers import mention_html as helpers_mention_html
from telegram.helpers import mention_markdown as helpers_mention_markdown
@ -1349,7 +1356,7 @@ class User(TelegramObject):
is_anonymous: Optional[bool] = None,
type: Optional[str] = None,
allows_multiple_answers: Optional[bool] = None,
correct_option_id: Optional[int] = None,
correct_option_id: Optional[CorrectOptionID] = None,
is_closed: Optional[bool] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,

View file

@ -24,7 +24,18 @@ Warning:
the changelog.
"""
from pathlib import Path
from typing import IO, TYPE_CHECKING, Any, Collection, Dict, Optional, Tuple, TypeVar, Union
from typing import (
IO,
TYPE_CHECKING,
Any,
Collection,
Dict,
Literal,
Optional,
Tuple,
TypeVar,
Union,
)
if TYPE_CHECKING:
from telegram import (
@ -75,3 +86,12 @@ FieldTuple = Tuple[str, bytes, str]
"""Alias for return type of `InputFile.field_tuple`."""
UploadFileDict = Dict[str, FieldTuple]
"""Dictionary containing file data to be uploaded to the API."""
HTTPVersion = Literal["1.1", "2.0"]
"""Allowed HTTP versions.
.. versionadded:: NEXT.VERSION"""
CorrectOptionID = Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
MarkdownVersion = Literal[1, 2]

View file

@ -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/].
# pylint: disable=missing-module-docstring
from typing import NamedTuple
from typing import Final, NamedTuple
__all__ = ("__version__", "__version_info__", "__bot_api_version__", "__bot_api_version_info__")
@ -50,8 +50,10 @@ class Version(NamedTuple):
return version
__version_info__ = Version(major=20, minor=3, micro=0, releaselevel="final", serial=0)
__version__ = str(__version_info__)
__version_info__: Final[Version] = Version(
major=20, minor=3, micro=0, releaselevel="final", serial=0
)
__version__: Final[str] = str(__version_info__)
# # SETUP.PY MARKER
# Lines above this line will be `exec`-cuted in setup.py. Make sure that this only contains
@ -59,5 +61,7 @@ __version__ = str(__version_info__)
from telegram import constants # noqa: E402 # pylint: disable=wrong-import-position
__bot_api_version__ = constants.BOT_API_VERSION
__bot_api_version_info__ = constants.BOT_API_VERSION_INFO
__bot_api_version__: Final[str] = constants.BOT_API_VERSION
__bot_api_version_info__: Final[
constants._BotAPIVersion
] = constants.BOT_API_VERSION_INFO # pylint: disable=protected-access

View file

@ -86,7 +86,7 @@ __all__ = [
]
import sys
from typing import List, NamedTuple
from typing import Final, List, NamedTuple
from telegram._utils.enum import IntEnum, StringEnum
@ -116,19 +116,19 @@ class _BotAPIVersion(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=7)
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=6, minor=7)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
#:
#: .. versionadded:: 13.4
BOT_API_VERSION = str(BOT_API_VERSION_INFO)
BOT_API_VERSION: Final[str] = str(BOT_API_VERSION_INFO)
# constants above this line are tested
#: List[:obj:`int`]: Ports supported by
#: :paramref:`telegram.Bot.set_webhook.url`.
SUPPORTED_WEBHOOK_PORTS: List[int] = [443, 80, 88, 8443]
SUPPORTED_WEBHOOK_PORTS: Final[List[int]] = [443, 80, 88, 8443]
class BotCommandLimit(IntEnum):

View file

@ -568,9 +568,8 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
try:
if self.persistence:
self.__update_persistence_task = asyncio.create_task(
self._persistence_updater()
# TODO: Add this once we drop py3.7
# name=f'Application:{self.bot.id}:persistence_updater'
self._persistence_updater(),
name=f"Application:{self.bot.id}:persistence_updater",
)
_LOGGER.debug("Loop for updating persistence started")
@ -579,9 +578,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
_LOGGER.debug("JobQueue started")
self.__update_fetcher_task = asyncio.create_task(
self._update_fetcher(),
# TODO: Add this once we drop py3.7
# name=f'Application:{self.bot.id}:update_fetcher'
self._update_fetcher(), name=f"Application:{self.bot.id}:update_fetcher"
)
_LOGGER.info("Application started")
@ -955,6 +952,8 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
self,
coroutine: Union[Generator[Optional["asyncio.Future[object]"], None, RT], Awaitable[RT]],
update: Optional[object] = None,
*,
name: Optional[str] = None,
) -> "asyncio.Task[RT]":
"""Thin wrapper around :func:`asyncio.create_task` that handles exceptions raised by
the :paramref:`coroutine` with :meth:`process_error`.
@ -977,16 +976,22 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
:attr:`chat_data` and :attr:`user_data` entries will be updated in the next run of
:meth:`update_persistence` after the :paramref:`coroutine` is finished.
Keyword Args:
name (:obj:`str`, optional): The name of the task.
.. versionadded:: NEXT.VERSION
Returns:
:class:`asyncio.Task`: The created task.
"""
return self.__create_task(coroutine=coroutine, update=update)
return self.__create_task(coroutine=coroutine, update=update, name=name)
def __create_task(
self,
coroutine: Union[Generator[Optional["asyncio.Future[object]"], None, RT], Awaitable[RT]],
update: Optional[object] = None,
is_error_handler: bool = False,
name: Optional[str] = None,
) -> "asyncio.Task[RT]":
# Unfortunately, we can't know if `coroutine` runs one of the error handler functions
# but by passing `is_error_handler=True` from `process_error`, we can make sure that we
@ -995,7 +1000,8 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
task: "asyncio.Task[RT]" = asyncio.create_task(
self.__create_task_callback(
coroutine=coroutine, update=update, is_error_handler=is_error_handler
)
),
name=name,
)
if self.running:
@ -1027,10 +1033,6 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
if isinstance(coroutine, Generator):
return await asyncio.create_task(coroutine)
return await coroutine
except asyncio.CancelledError as cancel:
# TODO: in py3.8+, CancelledError is a subclass of BaseException, so we can drop this
# clause when we drop py3.7
raise cancel
except Exception as exception:
if isinstance(exception, ApplicationHandlerStop):
warn(
@ -1080,6 +1082,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
self.create_task(
self.__process_update_wrapper(update),
update=update,
name=f"Application:{self.bot.id}:process_concurrent_update",
)
else:
await self.__process_update_wrapper(update)
@ -1136,7 +1139,12 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
and self.bot.defaults
and not self.bot.defaults.block
):
self.create_task(coroutine, update=update)
self.create_task(
coroutine,
update=update,
name=f"Application:{self.bot.id}:process_update_non_blocking"
f":{handler}",
)
else:
any_blocking = True
await coroutine
@ -1207,7 +1215,10 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
f"can not be persistent if application has no persistence"
)
if self._initialized:
self.create_task(self._add_ch_to_persistence(handler))
self.create_task(
self._add_ch_to_persistence(handler),
name=f"Application:{self.bot.id}:add_handler:conversation_handler_after_init",
)
warn(
"A persistent `ConversationHandler` was passed to `add_handler`, "
"after `Application.initialize` was called. This is discouraged."
@ -1687,7 +1698,10 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
and not self.bot.defaults.block
):
self.__create_task(
callback(update, context), update=update, is_error_handler=True
callback(update, context),
update=update,
is_error_handler=True,
name=f"Application:{self.bot.id}:process_error:non_blocking",
)
else:
try:

View file

@ -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, DVType, FilePathInput, ODVInput
from telegram._utils.types import DVInput, DVType, FilePathInput, HTTPVersion, ODVInput
from telegram.ext._application import Application
from telegram.ext._baseupdateprocessor import BaseUpdateProcessor, SimpleUpdateProcessor
from telegram.ext._contexttypes import ContextTypes
@ -240,7 +240,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
return HTTPXRequest(
connection_pool_size=connection_pool_size,
proxy_url=proxy_url,
http_version=http_version,
http_version=http_version, # type: ignore[arg-type]
**effective_timeouts,
)
@ -564,7 +564,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
self._pool_timeout = pool_timeout
return self
def http_version(self: BuilderType, http_version: str) -> BuilderType:
def http_version(self: BuilderType, http_version: HTTPVersion) -> BuilderType:
"""Sets the HTTP protocol version which is used for the
:paramref:`~telegram.request.HTTPXRequest.http_version` parameter of
:attr:`telegram.Bot.request`. By default, HTTP/1.1 is used.
@ -723,7 +723,9 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
self._get_updates_pool_timeout = get_updates_pool_timeout
return self
def get_updates_http_version(self: BuilderType, get_updates_http_version: str) -> BuilderType:
def get_updates_http_version(
self: BuilderType, get_updates_http_version: HTTPVersion
) -> BuilderType:
"""Sets the HTTP protocol version which is used for the
:paramref:`~telegram.request.HTTPXRequest.http_version` parameter which is used in the
:meth:`telegram.Bot.get_updates` request. By default, HTTP/1.1 is used.

View file

@ -20,7 +20,7 @@
from abc import ABC, abstractmethod
from asyncio import BoundedSemaphore
from types import TracebackType
from typing import Any, Awaitable, Optional, Type
from typing import Any, Awaitable, Optional, Type, final
class BaseUpdateProcessor(ABC):
@ -88,6 +88,7 @@ class BaseUpdateProcessor(ABC):
:meth:`initialize`
"""
@final
async def process_update(
self,
update: object,

View file

@ -150,8 +150,7 @@ class CallbackQueryHandler(BaseHandler[Update, CCT]):
return self.pattern(callback_data)
if not isinstance(callback_data, str):
return False
match = re.match(self.pattern, callback_data)
if match:
if match := re.match(self.pattern, callback_data):
return match
else:
return True

View file

@ -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 ChatMemberHandler class."""
from typing import ClassVar, Optional, TypeVar
from typing import Final, Optional, TypeVar
from telegram import Update
from telegram._utils.defaultvalue import DEFAULT_TRUE
@ -71,11 +71,11 @@ class ChatMemberHandler(BaseHandler[Update, CCT]):
"""
__slots__ = ("chat_member_types",)
MY_CHAT_MEMBER: ClassVar[int] = -1
MY_CHAT_MEMBER: Final[int] = -1
""":obj:`int`: Used as a constant to handle only :attr:`telegram.Update.my_chat_member`."""
CHAT_MEMBER: ClassVar[int] = 0
CHAT_MEMBER: Final[int] = 0
""":obj:`int`: Used as a constant to handle only :attr:`telegram.Update.chat_member`."""
ANY_CHAT_MEMBER: ClassVar[int] = 1
ANY_CHAT_MEMBER: Final[int] = 1
""":obj:`int`: Used as a constant to handle both :attr:`telegram.Update.my_chat_member`
and :attr:`telegram.Update.chat_member`."""

View file

@ -100,8 +100,7 @@ class ChosenInlineResultHandler(BaseHandler[Update, CCT]):
"""
if isinstance(update, Update) and update.chosen_inline_result:
if self.pattern:
match = re.match(self.pattern, update.chosen_inline_result.result_id)
if match:
if match := re.match(self.pattern, update.chosen_inline_result.result_id):
return match
else:
return True

View file

@ -23,8 +23,8 @@ from dataclasses import dataclass
from typing import (
TYPE_CHECKING,
Any,
ClassVar,
Dict,
Final,
Generic,
List,
NoReturn,
@ -283,13 +283,13 @@ class ConversationHandler(BaseHandler[Update, CCT]):
"timeout_jobs",
)
END: ClassVar[int] = -1
END: Final[int] = -1
""":obj:`int`: Used as a constant to return when a conversation is ended."""
TIMEOUT: ClassVar[int] = -2
TIMEOUT: Final[int] = -2
""":obj:`int`: Used as a constant to handle state when a conversation is timed out
(exceeded :attr:`conversation_timeout`).
"""
WAITING: ClassVar[int] = -3
WAITING: Final[int] = -3
""":obj:`int`: Used as a constant to handle state when a conversation is still waiting on the
previous :attr:`block=False <block>` handler to finish."""
@ -831,6 +831,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
update, application, handler_check_result, context
),
update=update,
name=f"ConversationHandler:{update.update_id}:handle_update:non_blocking_cb",
)
except ApplicationHandlerStop as exception:
new_state = exception.state
@ -856,6 +857,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
new_state, application, update, context, conversation_key
),
update=update,
name=f"ConversationHandler:{update.update_id}:handle_update:timeout_job",
)
else:
self._schedule_job(new_state, application, update, context, conversation_key)

View file

@ -18,11 +18,12 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the class Defaults, which allows passing default values to Application."""
import datetime
from typing import Any, Dict, NoReturn, Optional
from typing import Any, Dict, NoReturn, Optional, final
from telegram._utils.datetime import UTC
@final
class Defaults:
"""Convenience Class to gather all parameters with a (user defined) default value

View file

@ -88,7 +88,14 @@ from telegram import (
from telegram._utils.datetime import to_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.logging import get_logger
from telegram._utils.types import DVInput, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram._utils.types import (
CorrectOptionID,
DVInput,
FileInput,
JSONDict,
ODVInput,
ReplyMarkup,
)
from telegram.ext._callbackdatacache import CallbackDataCache
from telegram.ext._utils.types import RLARGS
from telegram.request import BaseRequest
@ -2705,7 +2712,7 @@ class ExtBot(Bot, Generic[RLARGS]):
is_anonymous: Optional[bool] = None,
type: Optional[str] = None, # pylint: disable=redefined-builtin
allows_multiple_answers: Optional[bool] = None,
correct_option_id: Optional[int] = None,
correct_option_id: Optional[CorrectOptionID] = None,
is_closed: Optional[bool] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,

View file

@ -117,12 +117,13 @@ class InlineQueryHandler(BaseHandler[Update, CCT]):
update.inline_query.chat_type not in self.chat_types
):
return False
if self.pattern:
if update.inline_query.query:
match = re.match(self.pattern, update.inline_query.query)
if match:
return match
else:
if (
self.pattern
and update.inline_query.query
and (match := re.match(self.pattern, update.inline_query.query))
):
return match
if not self.pattern:
return True
return None

View file

@ -799,7 +799,10 @@ class Job(Generic[CCT]):
await context.refresh_data()
await self.callback(context)
except Exception as exc:
await application.create_task(application.process_error(None, exc, job=self))
await application.create_task(
application.process_error(None, exc, job=self),
name=f"Job:{self.id}:run:process_error",
)
finally:
# This is internal logic of application - let's keep it private for now
application._mark_for_persistence_update(job=self) # pylint: disable=protected-access

View file

@ -17,11 +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 PicklePersistence class."""
import copyreg
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, Union, cast, overload
from telegram import Bot, TelegramObject
@ -74,18 +72,15 @@ class _BotPickler(pickle.Pickler):
def __init__(self, bot: Bot, *args: Any, **kwargs: Any):
self._bot = bot
if py_ver < (3, 8): # self.reducer_override is used above this version
# Here we define a private dispatch_table, because we want to preserve the bot
# attribute of objects so persistent_id works as intended. Otherwise, the bot attribute
# is deleted in __getstate__, which is used during regular pickling (via pickle.dumps)
self.dispatch_table = copyreg.dispatch_table.copy()
for obj in _all_subclasses(TelegramObject):
self.dispatch_table[obj] = _custom_reduction
super().__init__(*args, **kwargs)
def reducer_override( # skipcq: PYL-R0201
self, obj: TelegramObj
) -> Tuple[Callable, Tuple[Type[TelegramObj], dict]]:
"""
This method is used for pickling. The bot attribute is preserved so
_BotPickler().persistent_id works as intended.
"""
if not isinstance(obj, TelegramObject):
return NotImplemented

View file

@ -96,10 +96,8 @@ class StringRegexHandler(BaseHandler[str, CCT]):
:obj:`None` | :obj:`re.match`
"""
if isinstance(update, str):
match = re.match(self.pattern, update)
if match:
return match
if isinstance(update, str) and (match := re.match(self.pattern, update)):
return match
return None
def collect_additional_context(

View file

@ -326,10 +326,6 @@ class Updater(AsyncContextManager["Updater"]):
pool_timeout=pool_timeout,
allowed_updates=allowed_updates,
)
except asyncio.CancelledError as exc:
# TODO: in py3.8+, CancelledError is a subclass of BaseException, so we can drop
# this clause when we drop py3.7
raise exc
except TelegramError as exc:
# TelegramErrors should be processed by the network retry loop
raise exc
@ -367,7 +363,8 @@ class Updater(AsyncContextManager["Updater"]):
on_err_cb=error_callback or default_error_callback,
description="getting Updates",
interval=poll_interval,
)
),
name="Updater:start_polling:polling_task",
)
if ready is not None:

View file

@ -26,7 +26,7 @@ Warning:
the changelog.
"""
from collections import UserDict
from typing import ClassVar, Generic, List, Mapping, Optional, Set, Tuple, TypeVar, Union
from typing import Final, Generic, List, Mapping, Optional, Set, Tuple, TypeVar, Union
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
@ -45,7 +45,7 @@ class TrackingDict(UserDict, Generic[_KT, _VT]):
* deleting values is considered writing
"""
DELETED: ClassVar = object()
DELETED: Final = object()
"""Special marker indicating that an entry was deleted."""
__slots__ = ("_write_access_keys",)

View file

@ -599,10 +599,8 @@ class CaptionRegex(MessageFilter):
super().__init__(name=f"filters.CaptionRegex({self.pattern})", data_filter=True)
def filter(self, message: Message) -> Optional[Dict[str, List[Match[str]]]]:
if message.caption:
match = self.pattern.search(message.caption)
if match:
return {"matches": [match]}
if message.caption and (match := self.pattern.search(message.caption)):
return {"matches": [match]}
return {}
@ -974,15 +972,15 @@ class _Dice(MessageFilter):
self.name = "filters.Dice.ALL"
def filter(self, message: Message) -> bool:
if not message.dice: # no dice
if not (dice := message.dice): # no dice
return False
if self.emoji:
emoji_match = message.dice.emoji == self.emoji
emoji_match = dice.emoji == self.emoji
if self.values:
return message.dice.value in self.values and emoji_match # emoji and value
return dice.value in self.values and emoji_match # emoji and value
return emoji_match # emoji, no value
return message.dice.value in self.values if self.values else True # no emoji, only value
return dice.value in self.values if self.values else True # no emoji, only value
class Dice(_Dice):
@ -1597,10 +1595,8 @@ class Regex(MessageFilter):
super().__init__(name=f"filters.Regex({self.pattern})", data_filter=True)
def filter(self, message: Message) -> Optional[Dict[str, List[Match[str]]]]:
if message.text:
match = self.pattern.search(message.text)
if match:
return {"matches": [match]}
if message.text and (match := self.pattern.search(message.text)):
return {"matches": [match]}
return {}

View file

@ -35,13 +35,16 @@ import re
from html import escape
from typing import TYPE_CHECKING, Optional, Union
from telegram._utils.types import MarkdownVersion
from telegram.constants import MessageType
if TYPE_CHECKING:
from telegram import Message, Update
def escape_markdown(text: str, version: int = 1, entity_type: Optional[str] = None) -> str:
def escape_markdown(
text: str, version: MarkdownVersion = 1, entity_type: Optional[str] = None
) -> str:
"""Helper function to escape telegram markup symbols.
.. versionchanged:: 20.3
@ -88,7 +91,7 @@ def mention_html(user_id: Union[int, str], name: str) -> str:
return f'<a href="tg://user?id={user_id}">{escape(name)}</a>'
def mention_markdown(user_id: Union[int, str], name: str, version: int = 1) -> str:
def mention_markdown(user_id: Union[int, str], name: str, version: MarkdownVersion = 1) -> str:
"""
Helper function to create a user mention in Markdown syntax.

View file

@ -18,11 +18,10 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an abstract class to make POST and GET requests."""
import abc
import asyncio
import json
from http import HTTPStatus
from types import TracebackType
from typing import AsyncContextManager, ClassVar, List, Optional, Tuple, Type, TypeVar, Union
from typing import AsyncContextManager, Final, List, Optional, Tuple, Type, TypeVar, Union, final
from telegram._utils.defaultvalue import DEFAULT_NONE as _DEFAULT_NONE
from telegram._utils.defaultvalue import DefaultValue
@ -84,10 +83,10 @@ class BaseRequest(
__slots__ = ()
USER_AGENT: ClassVar[str] = f"python-telegram-bot v{ptb_ver} (https://python-telegram-bot.org)"
USER_AGENT: Final[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[DefaultValue[None]] = _DEFAULT_NONE
DEFAULT_NONE: Final[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`.
@ -125,6 +124,7 @@ class BaseRequest(
async def shutdown(self) -> None:
"""Stop & clear resources used by this class. Must be implemented by a subclass."""
@final
async def post(
self,
url: str,
@ -179,6 +179,7 @@ class BaseRequest(
# see https://core.telegram.org/bots/api#making-requests
return json_data["result"]
@final
async def retrieve(
self,
url: str,
@ -283,10 +284,6 @@ class BaseRequest(
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
)
except asyncio.CancelledError as exc:
# TODO: in py3.8+, CancelledError is a subclass of BaseException, so we can drop this
# clause when we drop py3.7
raise exc
except TelegramError as exc:
raise exc
except Exception as exc:

View file

@ -23,7 +23,7 @@ import httpx
from telegram._utils.defaultvalue import DefaultValue
from telegram._utils.logging import get_logger
from telegram._utils.types import ODVInput
from telegram._utils.types import HTTPVersion, ODVInput
from telegram.error import NetworkError, TimedOut
from telegram.request._baserequest import BaseRequest
from telegram.request._requestdata import RequestData
@ -101,7 +101,7 @@ class HTTPXRequest(BaseRequest):
write_timeout: Optional[float] = 5.0,
connect_timeout: Optional[float] = 5.0,
pool_timeout: Optional[float] = 1.0,
http_version: str = "1.1",
http_version: HTTPVersion = "1.1",
):
self._http_version = http_version
timeout = httpx.Timeout(

View file

@ -18,13 +18,14 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains a class that holds the parameters of a request to the Bot API."""
import json
from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, List, Optional, Union, final
from urllib.parse import urlencode
from telegram._utils.types import UploadFileDict
from telegram.request._requestparameter import RequestParameter
@final
class RequestData:
"""Instances of this class collect the data needed for one request to the Bot API, including
all parameters and files to be sent along with the request.

View file

@ -20,7 +20,7 @@
import json
from dataclasses import dataclass
from datetime import datetime
from typing import List, Optional, Sequence, Tuple
from typing import List, Optional, Sequence, Tuple, final
from telegram._files.inputfile import InputFile
from telegram._files.inputmedia import InputMedia
@ -31,6 +31,7 @@ from telegram._utils.enum import StringEnum
from telegram._utils.types import UploadFileDict
@final
@dataclass(repr=True, eq=False, order=False, frozen=True)
class RequestParameter:
"""Instances of this class represent a single parameter to be sent along with a request to

View file

@ -87,12 +87,8 @@ Bots used in tests
If you run the tests locally, the test setup will use one of the two public bots available. Which
bot of the two gets chosen for the test session is random. Whereas when the tests on the
Github Actions CI are run, the test setup allocates a different, but same bot for every combination of Python version and
OS.
Thus, number of bots used for testing locally is 2 (called as fallback bots), and on the CI,
its [3.7, 3.8, 3.9, 3.10, 3.11] x [ubuntu-latest, macos-latest, windows-latest] = 15. Bringing the
total number of bots used for testing to 17.
Github Actions CI are run, the test setup allocates a different, but same bot is for every combination of Python version and
OS. The operating systems and Python versions the CI runs the tests on can be viewed in the `corresponding workflow`_.
That's it! If you have any questions, feel free to ask them in the `PTB dev
@ -100,4 +96,5 @@ group`_.
.. _pytest: https://docs.pytest.org/en/stable/
.. _pytest-xdist: https://pypi.org/project/pytest-xdist/
.. _PTB dev group: https://t.me/pythontelegrambotgroup
.. _PTB dev group: https://t.me/pythontelegrambotgroup
.. _corresponding workflow: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/.github/workflows/test.yml

View file

@ -262,8 +262,7 @@ class TestAnimationWithRequest(TestAnimationBase):
async def test_get_and_download(self, bot, animation):
path = Path("game.gif")
if path.is_file():
path.unlink()
path.unlink(missing_ok=True)
new_file = await bot.get_file(animation.file_id)

View file

@ -261,8 +261,7 @@ class TestAudioWithRequest(TestAudioBase):
async def test_get_and_download(self, bot, chat_id, audio):
path = Path("telegram.mp3")
if path.is_file():
path.unlink()
path.unlink(missing_ok=True)
new_file = await bot.get_file(audio.file_id)

View file

@ -157,8 +157,7 @@ class TestChatPhotoWithoutRequest(TestChatPhotoBase):
class TestChatPhotoWithRequest:
async def test_get_and_download(self, bot, chat_photo):
jpg_file = Path("telegram.jpg")
if jpg_file.is_file():
jpg_file.unlink()
jpg_file.unlink(missing_ok=True)
tasks = {bot.get_file(chat_photo.small_file_id), bot.get_file(chat_photo.big_file_id)}
asserts = []

View file

@ -231,8 +231,7 @@ class TestDocumentWithRequest(TestDocumentBase):
async def test_get_and_download(self, bot, document, chat_id):
path = Path("telegram.png")
if path.is_file():
path.unlink()
path.unlink(missing_ok=True)
new_file = await bot.get_file(document.file_id)

View file

@ -161,7 +161,7 @@ class TestFileWithoutRequest(TestFileBase):
try:
assert out_file.read_bytes() == self.file_content
finally:
out_file.unlink()
out_file.unlink(missing_ok=True)
@pytest.mark.parametrize(
"custom_path_type", [str, Path], ids=["str custom_path", "pathlib.Path custom_path"]
@ -179,7 +179,7 @@ class TestFileWithoutRequest(TestFileBase):
assert out_file.read_bytes() == self.file_content
finally:
os.close(file_handle)
custom_path.unlink()
custom_path.unlink(missing_ok=True)
async def test_download_no_filename(self, monkeypatch, file):
async def test(*args, **kwargs):
@ -194,7 +194,7 @@ class TestFileWithoutRequest(TestFileBase):
try:
assert out_file.read_bytes() == self.file_content
finally:
out_file.unlink()
out_file.unlink(missing_ok=True)
async def test_download_file_obj(self, monkeypatch, file):
async def test(*args, **kwargs):
@ -233,7 +233,7 @@ class TestFileWithoutRequest(TestFileBase):
try:
assert out_file.read_bytes() == data_file("image_decrypted.jpg").read_bytes()
finally:
out_file.unlink()
out_file.unlink(missing_ok=True)
async def test_download_file_obj_encrypted(self, monkeypatch, encrypted_file):
async def test(*args, **kwargs):
@ -293,7 +293,7 @@ class TestFileWithRequest(TestFileBase):
assert out_file.read_bytes() == self.file_content
finally:
os.close(file_handle)
custom_path.unlink()
custom_path.unlink(missing_ok=True)
async def test_download_file_obj_local_file(self, local_file):
with TemporaryFile() as custom_fobj:
@ -315,14 +315,14 @@ class TestFileWithRequest(TestFileBase):
assert out_file.read_bytes() == data_file("image_decrypted.jpg").read_bytes()
finally:
os.close(file_handle)
custom_path.unlink()
custom_path.unlink(missing_ok=True)
async def test_download_local_file_encrypted(self, encrypted_local_file):
out_file = await encrypted_local_file.download_to_drive()
try:
assert out_file.read_bytes() == data_file("image_decrypted.jpg").read_bytes()
finally:
out_file.unlink()
out_file.unlink(missing_ok=True)
async def test_download_bytearray_local_file(self, local_file):
# Check that a download to a newly allocated bytearray works.

View file

@ -362,8 +362,7 @@ class TestPhotoWithRequest(TestPhotoBase):
async def test_get_and_download(self, bot, photo):
path = Path("telegram.jpg")
if path.is_file():
path.unlink()
path.unlink(missing_ok=True)
new_file = await bot.getFile(photo.file_id)

View file

@ -336,8 +336,7 @@ class TestStickerWithRequest(TestStickerBase):
async def test_get_and_download(self, bot, sticker):
path = Path("telegram.webp")
if path.is_file():
path.unlink()
path.unlink(missing_ok=True)
new_file = await bot.get_file(sticker.file_id)

View file

@ -279,8 +279,7 @@ class TestVideoWithRequest(TestVideoBase):
async def test_get_and_download(self, bot, video, chat_id):
path = Path("telegram.mp4")
if path.is_file():
path.unlink()
path.unlink(missing_ok=True)
new_file = await bot.get_file(video.file_id)

View file

@ -250,8 +250,7 @@ class TestVideoNoteWithRequest(TestVideoNoteBase):
async def test_get_and_download(self, bot, video_note, chat_id):
path = Path("telegram2.mp4")
if path.is_file():
path.unlink()
path.unlink(missing_ok=True)
new_file = await bot.get_file(video_note.file_id)

View file

@ -201,8 +201,7 @@ class TestVoiceWithRequest(TestVoiceBase):
async def test_get_and_download(self, bot, voice, chat_id):
path = Path("telegram.ogg")
if path.is_file():
path.unlink()
path.unlink(missing_ok=True)
new_file = await bot.get_file(voice.file_id)

View file

@ -94,7 +94,7 @@ def event_loop(request):
# ever since ProactorEventLoop became the default in Win 3.8+, the app crashes after the loop
# is closed. Hence, we use SelectorEventLoop on Windows to avoid this. See
# https://github.com/python/cpython/issues/83413, https://github.com/encode/httpx/issues/914
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith("win"):
if sys.platform.startswith("win"):
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
return asyncio.get_event_loop_policy().new_event_loop()
# loop.close() # instead of closing here, do that at the every end of the test session

View file

@ -453,6 +453,8 @@ class TestApplication:
async with app:
await app.start()
assert app.running
tasks = asyncio.all_tasks()
assert any(":update_fetcher" in task.get_name() for task in tasks)
if job_queue:
assert app.job_queue.scheduler.running
else:
@ -551,7 +553,6 @@ class TestApplication:
app.remove_handler(handler)
app.remove_handler(handler, group=2)
#
async def test_handler_order_in_group(self, app):
app.add_handler(MessageHandler(filters.PHOTO, self.callback_set_count(1)))
app.add_handler(MessageHandler(filters.ALL, self.callback_set_count(2)))
@ -964,6 +965,8 @@ class TestApplication:
await app.update_queue.put(1)
task = asyncio.create_task(app.stop())
await asyncio.sleep(0.05)
tasks = asyncio.all_tasks()
assert any(":process_update_non_blocking" in t.get_name() for t in tasks)
assert self.count == 1
# Make sure that app stops only once all non blocking callbacks are done
assert not task.done()
@ -1029,6 +1032,8 @@ class TestApplication:
await app.update_queue.put(self.message_update)
task = asyncio.create_task(app.stop())
await asyncio.sleep(0.05)
tasks = asyncio.all_tasks()
assert any(":process_error:non_blocking" in t.get_name() for t in tasks)
assert self.count == 42
assert self.received is None
event.set()
@ -1196,7 +1201,8 @@ class TestApplication:
self.count = 42
return 43
task = app.create_task(callback())
task = app.create_task(callback(), name="test_task")
assert task.get_name() == "test_task"
await asyncio.sleep(0.01)
assert not task.done()
out = await task
@ -1377,6 +1383,8 @@ class TestApplication:
assert not events[i].is_set()
await asyncio.sleep(0.9)
tasks = asyncio.all_tasks()
assert any(":process_concurrent_update" in task.get_name() for task in tasks)
for i in range(app.update_processor.max_concurrent_updates):
assert events[i].is_set()
for i in range(

View file

@ -552,6 +552,8 @@ class TestBasePersistence:
papp.add_handler(conversation)
assert len(recwarn) >= 1
tasks = asyncio.all_tasks()
assert any("conversation_handler_after_init" in t.get_name() for t in tasks)
found = False
for warning in recwarn:
if "after `Application.initialize` was called" in str(warning.message):
@ -584,6 +586,28 @@ class TestBasePersistence:
with pytest.raises(ValueError, match="when handler is unnamed"):
papp.add_handler(build_conversation_handler(name=None, persistent=True))
@pytest.mark.parametrize(
"papp",
[
PappInput(update_interval=0.0),
],
indirect=True,
)
async def test_update_persistence_called(self, papp: Application, monkeypatch):
"""Tests if Application.update_persistence is called from app.start()"""
called = asyncio.Event()
async def update_persistence(*args, **kwargs):
called.set()
monkeypatch.setattr(papp, "update_persistence", update_persistence)
async with papp:
await papp.start()
tasks = asyncio.all_tasks()
assert any(":persistence_updater" in task.get_name() for task in tasks)
assert await called.wait()
await papp.stop()
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize(
"papp",

View file

@ -2068,6 +2068,9 @@ class TestConversationHandler:
assert conv_handler.check_update(Update(0, message=message))
await app.process_update(Update(0, message=message))
await asyncio.sleep(0.7)
tasks = asyncio.all_tasks()
assert any(":handle_update:non_blocking_cb" in t.get_name() for t in tasks)
assert any(":handle_update:timeout_job" in t.get_name() for t in tasks)
assert not self.is_timeout
event.set()
await asyncio.sleep(0.7)

View file

@ -991,7 +991,7 @@ class TestPicklePersistence:
await pickle_persistence.update_callback_data(callback_data)
assert pickle_persistence.filepath.is_file()
pickle_persistence.filepath.unlink()
pickle_persistence.filepath.unlink(missing_ok=True)
assert not pickle_persistence.filepath.is_file()
await pickle_persistence.update_bot_data(bot_data)

View file

@ -248,6 +248,8 @@ class TestUpdater:
# We call the same logic twice to make sure that restarting the updater works as well
await updater.start_polling(drop_pending_updates=drop_pending_updates)
assert updater.running
tasks = asyncio.all_tasks()
assert any("Updater:start_polling:polling_task" in t.get_name() for t in tasks)
await updates.join()
await updater.stop()
assert not updater.running

View file

@ -137,9 +137,8 @@ class TestHelpers:
assert helpers.effective_message_type(empty_update) is None
def test_effective_message_type_wrong_type(self):
entity = {}
with pytest.raises(
TypeError, match=re.escape(f"neither Message nor Update (got: {type(entity)})")
TypeError, match=re.escape(f"neither Message nor Update (got: {type(entity := {})})")
):
helpers.effective_message_type(entity)