Supplement Codacy with DeepSource (#2454)

* Add deepsource config

* Update Badges

* Update Badges some more

* Stupid change to trigger analysis of all files

* Try to get ignore right

* Update badges again

* Get started on fixing issues

* Fix some more issues

* Remove more plank lines

* Docs for de_json/list & to_dict/json

* Some improvements from deepcode.ai

* Some more improvements

* Some more improvements

* More docstrnigs & let's run DS on the tests just for fun

* Autofix issues in 10 files

Resolved issues in the following files via DeepSource Autofix:
1. tests/conftest.py
2. tests/test_bot.py
3. tests/test_commandhandler.py
4. tests/test_conversationhandler.py
5. tests/test_dispatcher.py
6. tests/test_filters.py
7. tests/test_inputmedia.py
8. tests/test_messagehandler.py
9. tests/test_official.py
10. tests/test_persistence.py

* Some more improvements for tests, but that shall be enough

* Some more docstrings for functions

* Some minor stuff, try to fix tests

* Update DS config

* Still more docs

* Doc fixes

* More fixes

* Fix: indent docstring

* Some fixes

* Revert "Stupid change to trigger analysis of all files"

This reverts commit dd46c260

* Review

Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com>
This commit is contained in:
Bibo-Joshi 2021-05-27 09:38:17 +02:00 committed by GitHub
parent 1572c61063
commit 8bf88c3231
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
95 changed files with 674 additions and 492 deletions

20
.deepsource.toml Normal file
View file

@ -0,0 +1,20 @@
version = 1
test_patterns = ["tests/**"]
exclude_patterns = [
"tests/**",
"docs/**",
"telegram/vendor/**",
"setup.py",
"setup-raw.py"
]
[[analyzers]]
name = "python"
enabled = true
[analyzers.meta]
runtime_version = "3.x.x"
max_line_length = 99
skip_doc_coverage = ["module", "magic", "init", "nonpublic"]

View file

@ -50,10 +50,14 @@ We have a vibrant community of developers helping each other in our `Telegram gr
.. image:: https://api.codacy.com/project/badge/Grade/99d901eaa09b44b4819aec05c330c968 .. image:: https://api.codacy.com/project/badge/Grade/99d901eaa09b44b4819aec05c330c968
:target: https://www.codacy.com/app/python-telegram-bot/python-telegram-bot?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=python-telegram-bot/python-telegram-bot&amp;utm_campaign=Badge_Grade :target: https://www.codacy.com/app/python-telegram-bot/python-telegram-bot?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=python-telegram-bot/python-telegram-bot&amp;utm_campaign=Badge_Grade
:alt: Code quality :alt: Code quality: Codacy
.. image:: https://deepsource.io/gh/python-telegram-bot/python-telegram-bot.svg/?label=active+issues
:target: https://deepsource.io/gh/python-telegram-bot/python-telegram-bot/?ref=repository-badge
:alt: Code quality: DeepSource
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/psf/black :target: https://github.com/psf/black
.. image:: https://img.shields.io/badge/Telegram-Group-blue.svg?logo=telegram .. image:: https://img.shields.io/badge/Telegram-Group-blue.svg?logo=telegram
:target: https://telegram.me/pythontelegrambotgroup :target: https://telegram.me/pythontelegrambotgroup

View file

@ -50,10 +50,14 @@ We have a vibrant community of developers helping each other in our `Telegram gr
.. image:: https://api.codacy.com/project/badge/Grade/99d901eaa09b44b4819aec05c330c968 .. image:: https://api.codacy.com/project/badge/Grade/99d901eaa09b44b4819aec05c330c968
:target: https://www.codacy.com/app/python-telegram-bot/python-telegram-bot?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=python-telegram-bot/python-telegram-bot&amp;utm_campaign=Badge_Grade :target: https://www.codacy.com/app/python-telegram-bot/python-telegram-bot?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=python-telegram-bot/python-telegram-bot&amp;utm_campaign=Badge_Grade
:alt: Code quality :alt: Code quality: Codacy
.. image:: https://deepsource.io/gh/python-telegram-bot/python-telegram-bot.svg/?label=active+issues
:target: https://deepsource.io/gh/python-telegram-bot/python-telegram-bot/?ref=repository-badge
:alt: Code quality: DeepSource
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/psf/black :target: https://github.com/psf/black
.. image:: https://img.shields.io/badge/Telegram-Group-blue.svg?logo=telegram .. image:: https://img.shields.io/badge/Telegram-Group-blue.svg?logo=telegram
:target: https://telegram.me/pythontelegrambotgroup :target: https://telegram.me/pythontelegrambotgroup

View file

@ -24,6 +24,7 @@ logger = logging.getLogger(__name__)
def msg(update: Update, _: CallbackContext) -> None: def msg(update: Update, _: CallbackContext) -> None:
"""Downloads and prints the received passport data."""
# Retrieve passport data # Retrieve passport data
passport_data = update.message.passport_data passport_data = update.message.passport_data
# If our nonce doesn't match what we think, this Update did not originate from us # If our nonce doesn't match what we think, this Update did not originate from us
@ -61,21 +62,24 @@ def msg(update: Update, _: CallbackContext) -> None:
actual_file = file.get_file() actual_file = file.get_file()
print(actual_file) print(actual_file)
actual_file.download() actual_file.download()
if data.type in ('passport', 'driver_license', 'identity_card', 'internal_passport'): if (
if data.front_side: data.type in ('passport', 'driver_license', 'identity_card', 'internal_passport')
front_file = data.front_side.get_file() and data.front_side
print(data.type, front_file) ):
front_file.download() front_file = data.front_side.get_file()
if data.type in ('driver_license' and 'identity_card'): print(data.type, front_file)
if data.reverse_side: front_file.download()
reverse_file = data.reverse_side.get_file() if data.type in ('driver_license' and 'identity_card') and data.reverse_side:
print(data.type, reverse_file) reverse_file = data.reverse_side.get_file()
reverse_file.download() print(data.type, reverse_file)
if data.type in ('passport', 'driver_license', 'identity_card', 'internal_passport'): reverse_file.download()
if data.selfie: if (
selfie_file = data.selfie.get_file() data.type in ('passport', 'driver_license', 'identity_card', 'internal_passport')
print(data.type, selfie_file) and data.selfie
selfie_file.download() ):
selfie_file = data.selfie.get_file()
print(data.type, selfie_file)
selfie_file.download()
if data.type in ( if data.type in (
'passport', 'passport',
'driver_license', 'driver_license',
@ -97,7 +101,8 @@ def msg(update: Update, _: CallbackContext) -> None:
def main() -> None: def main() -> None:
"""Start the bot.""" """Start the bot."""
# Create the Updater and pass it your token and private key # Create the Updater and pass it your token and private key
updater = Updater("TOKEN", private_key=open('private.key', 'rb').read()) with open('private.key', 'rb') as private_key:
updater = Updater("TOKEN", private_key=private_key.read())
# Get the dispatcher to register handlers # Get the dispatcher to register handlers
dispatcher = updater.dispatcher dispatcher = updater.dispatcher

View file

@ -29,7 +29,7 @@ from .constants import BOT_API_VERSION
def _git_revision() -> Optional[str]: def _git_revision() -> Optional[str]:
try: try:
output = subprocess.check_output( output = subprocess.check_output( # skipcq: BAN-B607
["git", "describe", "--long", "--tags"], stderr=subprocess.STDOUT ["git", "describe", "--long", "--tags"], stderr=subprocess.STDOUT
) )
except (subprocess.SubprocessError, OSError): except (subprocess.SubprocessError, OSError):
@ -37,7 +37,7 @@ def _git_revision() -> Optional[str]:
return output.decode().strip() return output.decode().strip()
def print_ver_info() -> None: def print_ver_info() -> None: # skipcq: PY-D0003
git_revision = _git_revision() git_revision = _git_revision()
print(f'python-telegram-bot {telegram_ver}' + (f' ({git_revision})' if git_revision else '')) print(f'python-telegram-bot {telegram_ver}' + (f' ({git_revision})' if git_revision else ''))
print(f'Bot API {BOT_API_VERSION}') print(f'Bot API {BOT_API_VERSION}')
@ -46,7 +46,7 @@ def print_ver_info() -> None:
print(f'Python {sys_version}') print(f'Python {sys_version}')
def main() -> None: def main() -> None: # skipcq: PY-D0003
print_ver_info() print_ver_info()

View file

@ -34,7 +34,7 @@ TO = TypeVar('TO', bound='TelegramObject', covariant=True)
class TelegramObject: class TelegramObject:
"""Base class for most telegram objects.""" """Base class for most Telegram objects."""
_id_attrs: Tuple[object, ...] = () _id_attrs: Tuple[object, ...] = ()
@ -45,12 +45,22 @@ class TelegramObject:
return self.__dict__[item] return self.__dict__[item]
@staticmethod @staticmethod
def parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]: def _parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]:
return None if data is None else data.copy() return None if data is None else data.copy()
@classmethod @classmethod
def de_json(cls: Type[TO], data: Optional[JSONDict], bot: 'Bot') -> Optional[TO]: def de_json(cls: Type[TO], data: Optional[JSONDict], bot: 'Bot') -> Optional[TO]:
data = cls.parse_data(data) """Converts JSON data to a Telegram object.
Args:
data (Dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot`): The bot associated with this object.
Returns:
The Telegram object.
"""
data = cls._parse_data(data)
if data is None: if data is None:
return None return None
@ -61,21 +71,35 @@ class TelegramObject:
@classmethod @classmethod
def de_list(cls: Type[TO], data: Optional[List[JSONDict]], bot: 'Bot') -> List[Optional[TO]]: def de_list(cls: Type[TO], data: Optional[List[JSONDict]], bot: 'Bot') -> List[Optional[TO]]:
"""Converts JSON data to a list of Telegram objects.
Args:
data (Dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot`): The bot associated with these objects.
Returns:
A list of Telegram objects.
"""
if not data: if not data:
return [] return []
return [cls.de_json(d, bot) for d in data] return [cls.de_json(d, bot) for d in data]
def to_json(self) -> str: def to_json(self) -> str:
""" """Gives a JSON representation of object.
Returns: Returns:
:obj:`str` :obj:`str`
""" """
return json.dumps(self.to_dict()) return json.dumps(self.to_dict())
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""Gives representation of object as :obj:`dict`.
Returns:
:obj:`dict`
"""
data = {} data = {}
for key in iter(self.__dict__): for key in iter(self.__dict__):

View file

@ -116,7 +116,7 @@ if TYPE_CHECKING:
RT = TypeVar('RT') RT = TypeVar('RT')
def log( def log( # skipcq: PY-D0003
func: Callable[..., RT], *args: object, **kwargs: object # pylint: disable=W0613 func: Callable[..., RT], *args: object, **kwargs: object # pylint: disable=W0613
) -> Callable[..., RT]: ) -> Callable[..., RT]:
logger = logging.getLogger(func.__module__) logger = logging.getLogger(func.__module__)
@ -301,7 +301,7 @@ class Bot(TelegramObject):
return Message.de_json(result, self) # type: ignore[return-value, arg-type] return Message.de_json(result, self) # type: ignore[return-value, arg-type]
@property @property
def request(self) -> Request: def request(self) -> Request: # skip-cq: PY-D0003
return self._request return self._request
@staticmethod @staticmethod
@ -319,7 +319,6 @@ class Bot(TelegramObject):
@property @property
def bot(self) -> User: def bot(self) -> User:
""":class:`telegram.User`: User instance for the bot as returned by :meth:`get_me`.""" """:class:`telegram.User`: User instance for the bot as returned by :meth:`get_me`."""
if self._bot is None: if self._bot is None:
self._bot = self.get_me() self._bot = self.get_me()
return self._bot return self._bot
@ -327,55 +326,46 @@ class Bot(TelegramObject):
@property @property
def id(self) -> int: # pylint: disable=C0103 def id(self) -> int: # pylint: disable=C0103
""":obj:`int`: Unique identifier for this bot.""" """:obj:`int`: Unique identifier for this bot."""
return self.bot.id return self.bot.id
@property @property
def first_name(self) -> str: def first_name(self) -> str:
""":obj:`str`: Bot's first name.""" """:obj:`str`: Bot's first name."""
return self.bot.first_name return self.bot.first_name
@property @property
def last_name(self) -> str: def last_name(self) -> str:
""":obj:`str`: Optional. Bot's last name.""" """:obj:`str`: Optional. Bot's last name."""
return self.bot.last_name # type: ignore return self.bot.last_name # type: ignore
@property @property
def username(self) -> str: def username(self) -> str:
""":obj:`str`: Bot's username.""" """:obj:`str`: Bot's username."""
return self.bot.username # type: ignore return self.bot.username # type: ignore
@property @property
def link(self) -> str: def link(self) -> str:
""":obj:`str`: Convenience property. Returns the t.me link of the bot.""" """:obj:`str`: Convenience property. Returns the t.me link of the bot."""
return f"https://t.me/{self.username}" return f"https://t.me/{self.username}"
@property @property
def can_join_groups(self) -> bool: def can_join_groups(self) -> bool:
""":obj:`bool`: Bot's :attr:`telegram.User.can_join_groups` attribute.""" """:obj:`bool`: Bot's :attr:`telegram.User.can_join_groups` attribute."""
return self.bot.can_join_groups # type: ignore return self.bot.can_join_groups # type: ignore
@property @property
def can_read_all_group_messages(self) -> bool: def can_read_all_group_messages(self) -> bool:
""":obj:`bool`: Bot's :attr:`telegram.User.can_read_all_group_messages` attribute.""" """:obj:`bool`: Bot's :attr:`telegram.User.can_read_all_group_messages` attribute."""
return self.bot.can_read_all_group_messages # type: ignore return self.bot.can_read_all_group_messages # type: ignore
@property @property
def supports_inline_queries(self) -> bool: def supports_inline_queries(self) -> bool:
""":obj:`bool`: Bot's :attr:`telegram.User.supports_inline_queries` attribute.""" """:obj:`bool`: Bot's :attr:`telegram.User.supports_inline_queries` attribute."""
return self.bot.supports_inline_queries # type: ignore return self.bot.supports_inline_queries # type: ignore
@property @property
def commands(self) -> List[BotCommand]: def commands(self) -> List[BotCommand]:
"""List[:class:`BotCommand`]: Bot's commands.""" """List[:class:`BotCommand`]: Bot's commands."""
if self._commands is None: if self._commands is None:
self._commands = self.get_my_commands() self._commands = self.get_my_commands()
return self._commands return self._commands
@ -383,7 +373,6 @@ class Bot(TelegramObject):
@property @property
def name(self) -> str: def name(self) -> str:
""":obj:`str`: Bot's @username.""" """:obj:`str`: Bot's @username."""
return f'@{self.username}' return f'@{self.username}'
@log @log
@ -2606,7 +2595,6 @@ class Bot(TelegramObject):
Raises: Raises:
:class:`telegram.error.TelegramError` :class:`telegram.error.TelegramError`
""" """
if inline_message_id is None and (chat_id is None or message_id is None): if inline_message_id is None and (chat_id is None or message_id is None):
raise ValueError( raise ValueError(
'edit_message_media: Both chat_id and message_id are required when ' 'edit_message_media: Both chat_id and message_id are required when '
@ -4257,7 +4245,6 @@ class Bot(TelegramObject):
:class:`telegram.error.TelegramError` :class:`telegram.error.TelegramError`
""" """
data: JSONDict = {'chat_id': chat_id} data: JSONDict = {'chat_id': chat_id}
return self._post( # type: ignore[return-value] return self._post( # type: ignore[return-value]
@ -5089,6 +5076,7 @@ class Bot(TelegramObject):
return MessageId.de_json(result, self) # type: ignore[return-value, arg-type] return MessageId.de_json(result, self) # type: ignore[return-value, arg-type]
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data: JSONDict = {'id': self.id, 'username': self.username, 'first_name': self.first_name} data: JSONDict = {'id': self.id, 'username': self.username, 'first_name': self.first_name}
if self.last_name: if self.last_name:

View file

@ -113,7 +113,8 @@ class CallbackQuery(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['CallbackQuery']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['CallbackQuery']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -229,14 +229,16 @@ class Chat(TelegramObject):
@property @property
def link(self) -> Optional[str]: def link(self) -> Optional[str]:
""":obj:`str`: Convenience property. If the chat has a :attr:`username`, returns a t.me """:obj:`str`: Convenience property. If the chat has a :attr:`username`, returns a t.me
link of the chat.""" link of the chat.
"""
if self.username: if self.username:
return f"https://t.me/{self.username}" return f"https://t.me/{self.username}"
return None return None
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Chat']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Chat']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -84,7 +84,8 @@ class ChatInviteLink(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatInviteLink']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatInviteLink']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -95,6 +96,7 @@ class ChatInviteLink(TelegramObject):
return cls(**data) return cls(**data)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
data['expire_date'] = to_timestamp(self.expire_date) data['expire_date'] = to_timestamp(self.expire_date)

View file

@ -60,7 +60,8 @@ class ChatLocation(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatLocation']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatLocation']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -207,7 +207,8 @@ class ChatMember(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatMember']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatMember']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -218,6 +219,7 @@ class ChatMember(TelegramObject):
return cls(**data) return cls(**data)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
data['until_date'] = to_timestamp(self.until_date) data['until_date'] = to_timestamp(self.until_date)

View file

@ -92,7 +92,8 @@ class ChatMemberUpdated(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatMemberUpdated']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatMemberUpdated']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -107,6 +108,7 @@ class ChatMemberUpdated(TelegramObject):
return cls(**data) return cls(**data)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
# Required # Required

View file

@ -82,7 +82,8 @@ class ChosenInlineResult(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChosenInlineResult']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChosenInlineResult']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -70,7 +70,7 @@ class Dice(TelegramObject):
self._id_attrs = (self.value, self.emoji) self._id_attrs = (self.value, self.emoji)
DICE: ClassVar[str] = constants.DICE_DICE DICE: ClassVar[str] = constants.DICE_DICE # skipcq: PTC-W0052
""":const:`telegram.constants.DICE_DICE`""" """:const:`telegram.constants.DICE_DICE`"""
DARTS: ClassVar[str] = constants.DICE_DARTS DARTS: ClassVar[str] = constants.DICE_DARTS
""":const:`telegram.constants.DICE_DARTS`""" """:const:`telegram.constants.DICE_DARTS`"""

View file

@ -39,6 +39,8 @@ def _lstrip_str(in_s: str, lstr: str) -> str:
class TelegramError(Exception): class TelegramError(Exception):
"""Base class for Telegram errors."""
def __init__(self, message: str): def __init__(self, message: str):
super().__init__() super().__init__()
@ -58,10 +60,12 @@ class TelegramError(Exception):
class Unauthorized(TelegramError): class Unauthorized(TelegramError):
pass """Raised when the bot has not enough rights to perform the requested action."""
class InvalidToken(TelegramError): class InvalidToken(TelegramError):
"""Raised when the token is invalid."""
def __init__(self) -> None: def __init__(self) -> None:
super().__init__('Invalid token') super().__init__('Invalid token')
@ -70,14 +74,16 @@ class InvalidToken(TelegramError):
class NetworkError(TelegramError): class NetworkError(TelegramError):
pass """Base class for exceptions due to networking errors."""
class BadRequest(NetworkError): class BadRequest(NetworkError):
pass """Raised when Telegram could not process the request correctly."""
class TimedOut(NetworkError): class TimedOut(NetworkError):
"""Raised when a request took too long to finish."""
def __init__(self) -> None: def __init__(self) -> None:
super().__init__('Timed out') super().__init__('Timed out')
@ -87,6 +93,8 @@ class TimedOut(NetworkError):
class ChatMigrated(TelegramError): class ChatMigrated(TelegramError):
""" """
Raised when the requested group chat migrated to supergroup and has a new chat id.
Args: Args:
new_chat_id (:obj:`int`): The new chat id of the group. new_chat_id (:obj:`int`): The new chat id of the group.
@ -102,6 +110,8 @@ class ChatMigrated(TelegramError):
class RetryAfter(TelegramError): class RetryAfter(TelegramError):
""" """
Raised when flood limits where exceeded.
Args: Args:
retry_after (:obj:`int`): Time in seconds, after which the bot can retry the request. retry_after (:obj:`int`): Time in seconds, after which the bot can retry the request.
@ -116,13 +126,7 @@ class RetryAfter(TelegramError):
class Conflict(TelegramError): class Conflict(TelegramError):
""" """Raised when a long poll or webhook conflicts with another one."""
Raised when a long poll or webhook conflicts with another one.
Args:
msg (:obj:`str`): The message from telegrams server.
"""
def __reduce__(self) -> Tuple[type, Tuple[str]]: def __reduce__(self) -> Tuple[type, Tuple[str]]:
return self.__class__, (self.message,) return self.__class__, (self.message,)

View file

@ -51,19 +51,6 @@ class CallbackContext:
that you think you added will not be present. that you think you added will not be present.
Attributes: Attributes:
bot_data (:obj:`dict`): Optional. A dict that can be used to keep any data in. For each
update it will be the same ``dict``.
chat_data (:obj:`dict`): Optional. A dict that can be used to keep any data in. For each
update from the same chat id it will be the same ``dict``.
Warning:
When a group chat migrates to a supergroup, its chat id will change and the
``chat_data`` needs to be transferred. For details see our `wiki page
<https://github.com/python-telegram-bot/python-telegram-bot/wiki/
Storing-user--and-chat-related-data#chat-migration>`_.
user_data (:obj:`dict`): Optional. A dict that can be used to keep any data in. For each
update from the same user it will be the same ``dict``.
matches (List[:obj:`re match object`]): Optional. If the associated update originated from matches (List[:obj:`re match object`]): Optional. If the associated update originated from
a regex-supported handler or had a :class:`Filters.regex`, this will contain a list of a regex-supported handler or had a :class:`Filters.regex`, this will contain a list of
match objects for every pattern where ``re.search(pattern, string)`` returned a match. match objects for every pattern where ``re.search(pattern, string)`` returned a match.
@ -113,6 +100,9 @@ class CallbackContext:
@property @property
def bot_data(self) -> Dict: def bot_data(self) -> Dict:
""":obj:`dict`: Optional. A dict that can be used to keep any data in. For each
update it will be the same ``dict``.
"""
return self._bot_data return self._bot_data
@bot_data.setter @bot_data.setter
@ -123,6 +113,15 @@ class CallbackContext:
@property @property
def chat_data(self) -> Optional[Dict]: def chat_data(self) -> Optional[Dict]:
""":obj:`dict`: Optional. A dict that can be used to keep any data in. For each
update from the same chat id it will be the same ``dict``.
Warning:
When a group chat migrates to a supergroup, its chat id will change and the
``chat_data`` needs to be transferred. For details see our `wiki page
<https://github.com/python-telegram-bot/python-telegram-bot/wiki/
Storing-bot,-user-and-chat-related-data#chat-migration>`_.
"""
return self._chat_data return self._chat_data
@chat_data.setter @chat_data.setter
@ -133,6 +132,9 @@ class CallbackContext:
@property @property
def user_data(self) -> Optional[Dict]: def user_data(self) -> Optional[Dict]:
""":obj:`dict`: Optional. A dict that can be used to keep any data in. For each
update from the same user it will be the same ``dict``.
"""
return self._user_data return self._user_data
@user_data.setter @user_data.setter
@ -150,6 +152,28 @@ class CallbackContext:
async_args: Union[List, Tuple] = None, async_args: Union[List, Tuple] = None,
async_kwargs: Dict[str, object] = None, async_kwargs: Dict[str, object] = None,
) -> 'CallbackContext': ) -> 'CallbackContext':
"""
Constructs an instance of :class:`telegram.ext.CallbackContext` to be passed to the error
handlers.
.. seealso:: :meth:`telegram.ext.Dispatcher.add_error_handler`
Args:
update (:obj:`object` | :class:`telegram.Update`): The update associated with the
error. May be :obj:`None`, e.g. for errors in job callbacks.
error (:obj:`Exception`): The error.
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher associated with this
context.
async_args (List[:obj:`object`]): Optional. Positional arguments of the function that
raised the error. Pass only when the raising function was run asynchronously using
:meth:`telegram.ext.Dispatcher.run_async`.
async_kwargs (Dict[:obj:`str`, :obj:`object`]): Optional. Keyword arguments of the
function that raised the error. Pass only when the raising function was run
asynchronously using :meth:`telegram.ext.Dispatcher.run_async`.
Returns:
:class:`telegram.ext.CallbackContext`
"""
self = cls.from_update(update, dispatcher) self = cls.from_update(update, dispatcher)
self.error = error self.error = error
self.async_args = async_args self.async_args = async_args
@ -158,6 +182,20 @@ class CallbackContext:
@classmethod @classmethod
def from_update(cls, update: object, dispatcher: 'Dispatcher') -> 'CallbackContext': def from_update(cls, update: object, dispatcher: 'Dispatcher') -> 'CallbackContext':
"""
Constructs an instance of :class:`telegram.ext.CallbackContext` to be passed to the
handlers.
.. seealso:: :meth:`telegram.ext.Dispatcher.add_handler`
Args:
update (:obj:`object` | :class:`telegram.Update`): The update.
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher associated with this
context.
Returns:
:class:`telegram.ext.CallbackContext`
"""
self = cls(dispatcher) self = cls(dispatcher)
if update is not None and isinstance(update, Update): if update is not None and isinstance(update, Update):
@ -172,11 +210,30 @@ class CallbackContext:
@classmethod @classmethod
def from_job(cls, job: 'Job', dispatcher: 'Dispatcher') -> 'CallbackContext': def from_job(cls, job: 'Job', dispatcher: 'Dispatcher') -> 'CallbackContext':
"""
Constructs an instance of :class:`telegram.ext.CallbackContext` to be passed to a
job callback.
.. seealso:: :meth:`telegram.ext.JobQueue`
Args:
job (:class:`telegram.ext.Job`): The job.
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher associated with this
context.
Returns:
:class:`telegram.ext.CallbackContext`
"""
self = cls(dispatcher) self = cls(dispatcher)
self.job = job self.job = job
return self return self
def update(self, data: Dict[str, object]) -> None: def update(self, data: Dict[str, object]) -> None:
"""Updates ``self.__dict__`` with the passed data.
Args:
data (Dict[:obj:`str`, :obj:`object`]): The data.
"""
self.__dict__.update(data) self.__dict__.update(data)
@property @property

View file

@ -174,6 +174,10 @@ class CallbackQueryHandler(Handler[Update]):
update: Update = None, update: Update = None,
check_result: Union[bool, Match] = None, check_result: Union[bool, Match] = None,
) -> Dict[str, object]: ) -> Dict[str, object]:
"""Pass the results of ``re.match(pattern, data).{groups(), groupdict()}`` to the
callback as a keyword arguments called ``groups`` and ``groupdict``, respectively, if
needed.
"""
optional_args = super().collect_optional_args(dispatcher, update, check_result) optional_args = super().collect_optional_args(dispatcher, update, check_result)
if self.pattern: if self.pattern:
check_result = cast(Match, check_result) check_result = cast(Match, check_result)
@ -190,6 +194,9 @@ class CallbackQueryHandler(Handler[Update]):
dispatcher: 'Dispatcher', dispatcher: 'Dispatcher',
check_result: Union[bool, Match], check_result: Union[bool, Match],
) -> None: ) -> None:
"""Add the result of ``re.match(pattern, update.callback_query.data)`` to
:attr:`CallbackContext.matches` as list with one element.
"""
if self.pattern: if self.pattern:
check_result = cast(Match, check_result) check_result = cast(Match, check_result)
context.matches = [check_result] context.matches = [check_result]

View file

@ -150,7 +150,8 @@ class ChosenInlineResultHandler(Handler[Update]):
check_result: Union[bool, Match], check_result: Union[bool, Match],
) -> None: ) -> None:
"""This function adds the matched regex pattern result to """This function adds the matched regex pattern result to
:attr:`telegram.ext.CallbackContext.matches`.""" :attr:`telegram.ext.CallbackContext.matches`.
"""
if self.pattern: if self.pattern:
check_result = cast(Match, check_result) check_result = cast(Match, check_result)
context.matches = [check_result] context.matches = [check_result]

View file

@ -219,6 +219,9 @@ class CommandHandler(Handler[Update]):
update: Update = None, update: Update = None,
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]] = None, check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]] = None,
) -> Dict[str, object]: ) -> Dict[str, object]:
"""Provide text after the command to the callback the ``args`` argument as list, split on
single whitespaces.
"""
optional_args = super().collect_optional_args(dispatcher, update) optional_args = super().collect_optional_args(dispatcher, update)
if self.pass_args and isinstance(check_result, tuple): if self.pass_args and isinstance(check_result, tuple):
optional_args['args'] = check_result[0] optional_args['args'] = check_result[0]
@ -231,6 +234,9 @@ class CommandHandler(Handler[Update]):
dispatcher: 'Dispatcher', dispatcher: 'Dispatcher',
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]], check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]],
) -> None: ) -> None:
"""Add text after the command to :attr:`CallbackContext.args` as list, split on single
whitespaces and add output of data filters to :attr:`CallbackContext` as well.
"""
if isinstance(check_result, tuple): if isinstance(check_result, tuple):
context.args = check_result[0] context.args = check_result[0]
if isinstance(check_result[1], dict): if isinstance(check_result[1], dict):
@ -238,7 +244,7 @@ class CommandHandler(Handler[Update]):
class PrefixHandler(CommandHandler): class PrefixHandler(CommandHandler):
"""Handler class to handle custom prefix commands """Handler class to handle custom prefix commands.
This is a intermediate handler between :class:`MessageHandler` and :class:`CommandHandler`. This is a intermediate handler between :class:`MessageHandler` and :class:`CommandHandler`.
It supports configurable commands with the same options as CommandHandler. It will respond to It supports configurable commands with the same options as CommandHandler. It will respond to
@ -265,7 +271,7 @@ class PrefixHandler(CommandHandler):
.. code:: python .. code:: python
PrefixHandler(['!', '#'], ['test', 'help'], callback) # will respond to '!test', \ PrefixHandler(['!', '#'], ['test', 'help'], callback) # will respond to '!test', \
'#test', '!help' and '#help'. '#test', '!help' and '#help'.
By default the handler listens to messages as well as edited messages. To change this behavior By default the handler listens to messages as well as edited messages. To change this behavior
@ -442,15 +448,3 @@ class PrefixHandler(CommandHandler):
return text_list[1:], filter_result return text_list[1:], filter_result
return False return False
return None return None
def collect_additional_context(
self,
context: 'CallbackContext',
update: Update,
dispatcher: 'Dispatcher',
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]],
) -> None:
if isinstance(check_result, tuple):
context.args = check_result[0]
if isinstance(check_result[1], dict):
context.update(check_result[1])

View file

@ -173,33 +173,8 @@ class ConversationHandler(Handler[Update]):
ValueError ValueError
Attributes: Attributes:
entry_points (List[:class:`telegram.ext.Handler`]): A list of ``Handler`` objects that can
trigger the start of the conversation.
states (Dict[:obj:`object`, List[:class:`telegram.ext.Handler`]]): A :obj:`dict` that
defines the different states of conversation a user can be in and one or more
associated ``Handler`` objects that should be used in that state.
fallbacks (List[:class:`telegram.ext.Handler`]): 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 :attr:`check_update`.
allow_reentry (:obj:`bool`): Determines if a user can restart a conversation with
an entry point.
per_chat (:obj:`bool`): If the conversationkey should contain the Chat's ID.
per_user (:obj:`bool`): If the conversationkey should contain the User's ID.
per_message (:obj:`bool`): If the conversationkey should contain the Message's
ID.
conversation_timeout (:obj:`float` | :obj:`datetime.timedelta`): Optional. When this
handler is inactive more than this timeout (in seconds), it will be automatically
ended. If this value is 0 (default), there will be no timeout. When it's triggered, the
last received update and the corresponding ``context`` will be handled by ALL the
handler's who's :attr:`check_update` method returns :obj:`True` that are in the state
:attr:`ConversationHandler.TIMEOUT`.
name (:obj:`str`): Optional. The name for this conversationhandler. Required for
persistence
persistent (:obj:`bool`): Optional. If the conversations dict for this handler should be persistent (:obj:`bool`): Optional. If the conversations dict for this handler should be
saved. Name is required and persistence has to be set in :class:`telegram.ext.Updater` saved. Name is required and persistence has to be set in :class:`telegram.ext.Updater`
map_to_parent (Dict[:obj:`object`, :obj:`object`]): Optional. A :obj:`dict` that can be
used to instruct a nested conversationhandler to transition into a mapped state on
its parent conversationhandler in place of a specified nested state.
run_async (:obj:`bool`): If :obj:`True`, will override the run_async (:obj:`bool`): If :obj:`True`, will override the
:attr:`Handler.run_async` setting of all internal handlers on initialization. :attr:`Handler.run_async` setting of all internal handlers on initialization.
@ -316,6 +291,9 @@ class ConversationHandler(Handler[Update]):
@property @property
def entry_points(self) -> List[Handler]: def entry_points(self) -> List[Handler]:
"""List[:class:`telegram.ext.Handler`]: A list of ``Handler`` objects that can trigger the
start of the conversation.
"""
return self._entry_points return self._entry_points
@entry_points.setter @entry_points.setter
@ -324,6 +302,10 @@ class ConversationHandler(Handler[Update]):
@property @property
def states(self) -> Dict[object, List[Handler]]: def states(self) -> Dict[object, List[Handler]]:
"""Dict[:obj:`object`, List[:class:`telegram.ext.Handler`]]: A :obj:`dict` that
defines the different states of conversation a user can be in and one or more
associated ``Handler`` objects that should be used in that state.
"""
return self._states return self._states
@states.setter @states.setter
@ -332,6 +314,10 @@ class ConversationHandler(Handler[Update]):
@property @property
def fallbacks(self) -> List[Handler]: def fallbacks(self) -> List[Handler]:
"""List[:class:`telegram.ext.Handler`]: 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 :attr:`check_update`.
"""
return self._fallbacks return self._fallbacks
@fallbacks.setter @fallbacks.setter
@ -344,10 +330,12 @@ class ConversationHandler(Handler[Update]):
@allow_reentry.setter @allow_reentry.setter
def allow_reentry(self, value: object) -> NoReturn: def allow_reentry(self, value: object) -> NoReturn:
""":obj:`bool`: Determines if a user can restart a conversation with an entry point."""
raise ValueError('You can not assign a new value to allow_reentry after initialization.') raise ValueError('You can not assign a new value to allow_reentry after initialization.')
@property @property
def per_user(self) -> bool: def per_user(self) -> bool:
""":obj:`bool`: If the conversation key should contain the User's ID."""
return self._per_user return self._per_user
@per_user.setter @per_user.setter
@ -356,6 +344,7 @@ class ConversationHandler(Handler[Update]):
@property @property
def per_chat(self) -> bool: def per_chat(self) -> bool:
""":obj:`bool`: If the conversation key should contain the Chat's ID."""
return self._per_chat return self._per_chat
@per_chat.setter @per_chat.setter
@ -364,6 +353,7 @@ class ConversationHandler(Handler[Update]):
@property @property
def per_message(self) -> bool: def per_message(self) -> bool:
""":obj:`bool`: If the conversation key should contain the message's ID."""
return self._per_message return self._per_message
@per_message.setter @per_message.setter
@ -374,16 +364,21 @@ class ConversationHandler(Handler[Update]):
def conversation_timeout( def conversation_timeout(
self, self,
) -> Optional[Union[float, datetime.timedelta]]: ) -> Optional[Union[float, datetime.timedelta]]:
""":obj:`float` | :obj:`datetime.timedelta`: Optional. When this
handler is inactive more than this timeout (in seconds), it will be automatically
ended.
"""
return self._conversation_timeout return self._conversation_timeout
@conversation_timeout.setter @conversation_timeout.setter
def conversation_timeout(self, value: object) -> NoReturn: def conversation_timeout(self, value: object) -> NoReturn:
raise ValueError( raise ValueError(
'You can not assign a new value to conversation_timeout after ' 'initialization.' 'You can not assign a new value to conversation_timeout after initialization.'
) )
@property @property
def name(self) -> Optional[str]: def name(self) -> Optional[str]:
""":obj:`str`: Optional. The name for this :class:`ConversationHandler`."""
return self._name return self._name
@name.setter @name.setter
@ -392,6 +387,10 @@ class ConversationHandler(Handler[Update]):
@property @property
def map_to_parent(self) -> Optional[Dict[object, object]]: def map_to_parent(self) -> Optional[Dict[object, object]]:
"""Dict[:obj:`object`, :obj:`object`]: Optional. A :obj:`dict` that can be
used to instruct a nested :class:`ConversationHandler` to transition into a mapped state on
its parent :class:`ConversationHandler` in place of a specified nested state.
"""
return self._map_to_parent return self._map_to_parent
@map_to_parent.setter @map_to_parent.setter
@ -400,6 +399,7 @@ class ConversationHandler(Handler[Update]):
@property @property
def persistence(self) -> Optional[BasePersistence]: def persistence(self) -> Optional[BasePersistence]:
"""The persistence class as provided by the :class:`Dispatcher`."""
return self._persistence return self._persistence
@persistence.setter @persistence.setter
@ -412,7 +412,7 @@ class ConversationHandler(Handler[Update]):
handler.persistence = self.persistence handler.persistence = self.persistence
@property @property
def conversations(self) -> ConversationDict: def conversations(self) -> ConversationDict: # skipcq: PY-D0003
return self._conversations return self._conversations
@conversations.setter @conversations.setter
@ -518,7 +518,7 @@ class ConversationHandler(Handler[Update]):
# check if promise is finished or not # check if promise is finished or not
if state[1].done.wait(0): if state[1].done.wait(0):
res = self._resolve_promise(state) res = self._resolve_promise(state)
self.update_state(res, key) self._update_state(res, key)
with self._conversations_lock: with self._conversations_lock:
state = self.conversations.get(key) state = self.conversations.get(key)
@ -627,19 +627,19 @@ class ConversationHandler(Handler[Update]):
) )
if isinstance(self.map_to_parent, dict) and new_state in self.map_to_parent: if isinstance(self.map_to_parent, dict) and new_state in self.map_to_parent:
self.update_state(self.END, conversation_key) self._update_state(self.END, conversation_key)
if raise_dp_handler_stop: if raise_dp_handler_stop:
raise DispatcherHandlerStop(self.map_to_parent.get(new_state)) raise DispatcherHandlerStop(self.map_to_parent.get(new_state))
return self.map_to_parent.get(new_state) return self.map_to_parent.get(new_state)
self.update_state(new_state, conversation_key) self._update_state(new_state, conversation_key)
if raise_dp_handler_stop: if raise_dp_handler_stop:
# Don't pass the new state here. If we're in a nested conversation, the parent is # Don't pass the new state here. If we're in a nested conversation, the parent is
# expecting None as return value. # expecting None as return value.
raise DispatcherHandlerStop() raise DispatcherHandlerStop()
return None return None
def update_state(self, new_state: object, key: Tuple[int, ...]) -> None: def _update_state(self, new_state: object, key: Tuple[int, ...]) -> None:
if new_state == self.END: if new_state == self.END:
with self._conversations_lock: with self._conversations_lock:
if key in self.conversations: if key in self.conversations:
@ -698,4 +698,4 @@ class ConversationHandler(Handler[Update]):
'ConversationHandler has no effect. Ignoring.' 'ConversationHandler has no effect. Ignoring.'
) )
self.update_state(self.END, ctxt.conversation_key) self._update_state(self.END, ctxt.conversation_key)

View file

@ -54,29 +54,6 @@ class Defaults:
run_async (:obj:`bool`, optional): Default setting for the ``run_async`` parameter of run_async (:obj:`bool`, optional): Default setting for the ``run_async`` parameter of
handlers and error handlers registered through :meth:`Dispatcher.add_handler` and handlers and error handlers registered through :meth:`Dispatcher.add_handler` and
:meth:`Dispatcher.add_error_handler`. Defaults to :obj:`False`. :meth:`Dispatcher.add_error_handler`. Defaults to :obj:`False`.
Attributes:
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
bold, italic, fixed-width text or URLs in your bot's message.
explanation_parse_mode (:obj:`str`): Optional. Alias for :attr:`parse_mode`, used for
the corresponding parameter of :meth:`telegram.Bot.send_poll`.
disable_notification (:obj:`bool`): Optional. Sends the message silently. Users will
receive a notification with no sound.
disable_web_page_preview (:obj:`bool`): Optional. Disables link previews for links in this
message.
allow_sending_without_reply (:obj:`bool`): Optional. Pass :obj:`True`, if the message
should be sent even if the specified replied-to message is not found.
timeout (:obj:`int` | :obj:`float`): Optional. If this value is specified, use it as the
read timeout from the server (instead of the one specified during creation of the
connection pool).
quote (:obj:`bool`): Optional. If set to :obj:`True`, the reply is sent as an actual reply
to the message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter will
be ignored. Default: :obj:`True` in group chats and :obj:`False` in private chats.
tzinfo (:obj:`tzinfo`): A timezone to be used for all date(time) objects appearing
throughout PTB.
run_async (:obj:`bool`): Optional. Default setting for the ``run_async`` parameter of
handlers and error handlers registered through :meth:`Dispatcher.add_handler` and
:meth:`Dispatcher.add_error_handler`.
""" """
def __init__( def __init__(
@ -118,11 +95,14 @@ class Defaults:
self._api_defaults['timeout'] = self.timeout self._api_defaults['timeout'] = self.timeout
@property @property
def api_defaults(self) -> Dict[str, Any]: def api_defaults(self) -> Dict[str, Any]: # skip-cq: PY-D0003
return self._api_defaults return self._api_defaults
@property @property
def parse_mode(self) -> Optional[str]: def parse_mode(self) -> Optional[str]:
""":obj:`str`: Optional. Send Markdown or HTML, if you want Telegram apps to show
bold, italic, fixed-width text or URLs in your bot's message.
"""
return self._parse_mode return self._parse_mode
@parse_mode.setter @parse_mode.setter
@ -134,6 +114,9 @@ class Defaults:
@property @property
def explanation_parse_mode(self) -> Optional[str]: def explanation_parse_mode(self) -> Optional[str]:
""":obj:`str`: Optional. Alias for :attr:`parse_mode`, used for
the corresponding parameter of :meth:`telegram.Bot.send_poll`.
"""
return self._parse_mode return self._parse_mode
@explanation_parse_mode.setter @explanation_parse_mode.setter
@ -145,6 +128,9 @@ class Defaults:
@property @property
def disable_notification(self) -> Optional[bool]: def disable_notification(self) -> Optional[bool]:
""":obj:`bool`: Optional. Sends the message silently. Users will
receive a notification with no sound.
"""
return self._disable_notification return self._disable_notification
@disable_notification.setter @disable_notification.setter
@ -156,6 +142,9 @@ class Defaults:
@property @property
def disable_web_page_preview(self) -> Optional[bool]: def disable_web_page_preview(self) -> Optional[bool]:
""":obj:`bool`: Optional. Disables link previews for links in this
message.
"""
return self._disable_web_page_preview return self._disable_web_page_preview
@disable_web_page_preview.setter @disable_web_page_preview.setter
@ -167,6 +156,9 @@ class Defaults:
@property @property
def allow_sending_without_reply(self) -> Optional[bool]: def allow_sending_without_reply(self) -> Optional[bool]:
""":obj:`bool`: Optional. Pass :obj:`True`, if the message
should be sent even if the specified replied-to message is not found.
"""
return self._allow_sending_without_reply return self._allow_sending_without_reply
@allow_sending_without_reply.setter @allow_sending_without_reply.setter
@ -178,6 +170,10 @@ class Defaults:
@property @property
def timeout(self) -> ODVInput[float]: def timeout(self) -> ODVInput[float]:
""":obj:`int` | :obj:`float`: Optional. If this value is specified, use it as the
read timeout from the server (instead of the one specified during creation of the
connection pool).
"""
return self._timeout return self._timeout
@timeout.setter @timeout.setter
@ -189,6 +185,10 @@ class Defaults:
@property @property
def quote(self) -> Optional[bool]: def quote(self) -> Optional[bool]:
""":obj:`bool`: Optional. If set to :obj:`True`, the reply is sent as an actual reply
to the message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter will
be ignored. Default: :obj:`True` in group chats and :obj:`False` in private chats.
"""
return self._quote return self._quote
@quote.setter @quote.setter
@ -200,6 +200,9 @@ class Defaults:
@property @property
def tzinfo(self) -> pytz.BaseTzInfo: def tzinfo(self) -> pytz.BaseTzInfo:
""":obj:`tzinfo`: A timezone to be used for all date(time) objects appearing
throughout PTB.
"""
return self._tzinfo return self._tzinfo
@tzinfo.setter @tzinfo.setter
@ -211,6 +214,10 @@ class Defaults:
@property @property
def run_async(self) -> bool: def run_async(self) -> bool:
""":obj:`bool`: Optional. Default setting for the ``run_async`` parameter of
handlers and error handlers registered through :meth:`Dispatcher.add_handler` and
:meth:`Dispatcher.add_error_handler`.
"""
return self._run_async return self._run_async
@run_async.setter @run_async.setter

View file

@ -216,7 +216,7 @@ class Dispatcher:
self._set_singleton(None) self._set_singleton(None)
@property @property
def exception_event(self) -> Event: def exception_event(self) -> Event: # skipcq: PY-D0003
return self.__exception_event return self.__exception_event
def _init_async_threads(self, base_name: str, workers: int) -> None: def _init_async_threads(self, base_name: str, workers: int) -> None:
@ -404,7 +404,7 @@ class Dispatcher:
self.logger.debug('async thread %s/%s has ended', i + 1, total) self.logger.debug('async thread %s/%s has ended', i + 1, total)
@property @property
def has_running_threads(self) -> bool: def has_running_threads(self) -> bool: # skipcq: PY-D0003
return self.running or bool(self.__async_threads) return self.running or bool(self.__async_threads)
def process_update(self, update: object) -> None: def process_update(self, update: object) -> None:
@ -422,7 +422,6 @@ class Dispatcher:
The update to process. The update to process.
""" """
# An error happened while polling # An error happened while polling
if isinstance(update, TelegramError): if isinstance(update, TelegramError):
try: try:
@ -637,9 +636,8 @@ class Dispatcher:
self.logger.debug('The callback is already registered as an error handler. Ignoring.') self.logger.debug('The callback is already registered as an error handler. Ignoring.')
return return
if run_async is DEFAULT_FALSE and self.bot.defaults: if run_async is DEFAULT_FALSE and self.bot.defaults and self.bot.defaults.run_async:
if self.bot.defaults.run_async: run_async = True
run_async = True
self.error_handlers[callback] = run_async self.error_handlers[callback] = run_async

View file

@ -117,7 +117,7 @@ class BaseFilter(ABC):
@abstractmethod @abstractmethod
def __call__(self, update: Update) -> Optional[Union[bool, DataDict]]: def __call__(self, update: Update) -> Optional[Union[bool, DataDict]]:
pass ...
def __and__(self, other: 'BaseFilter') -> 'BaseFilter': def __and__(self, other: 'BaseFilter') -> 'BaseFilter':
return MergedFilter(self, and_filter=other) return MergedFilter(self, and_filter=other)
@ -652,17 +652,13 @@ class Filters:
""" """
def __init__(self, category: Optional[str]): def __init__(self, category: Optional[str]):
"""Initialize the category you want to filter self._category = category
self.name = f"Filters.document.category('{self._category}')"
Args:
category (str, optional): category of the media you want to filter"""
self.category = category
self.name = f"Filters.document.category('{self.category}')"
def filter(self, message: Message) -> bool: def filter(self, message: Message) -> bool:
"""""" # remove method from docs """""" # remove method from docs
if message.document: if message.document:
return message.document.mime_type.startswith(self.category) return message.document.mime_type.startswith(self._category)
return False return False
application = category('application/') application = category('application/')
@ -685,10 +681,6 @@ class Filters:
""" """
def __init__(self, mimetype: Optional[str]): def __init__(self, mimetype: Optional[str]):
"""Initialize the category you want to filter
Args:
mimetype (str, optional): mime_type of the media you want to filter"""
self.mimetype = mimetype self.mimetype = mimetype
self.name = f"Filters.document.mime_type('{self.mimetype}')" self.name = f"Filters.document.mime_type('{self.mimetype}')"
@ -752,29 +744,29 @@ class Filters:
""" """
self.is_case_sensitive = case_sensitive self.is_case_sensitive = case_sensitive
if file_extension is None: if file_extension is None:
self.file_extension = None self._file_extension = None
self.name = "Filters.document.file_extension(None)" self.name = "Filters.document.file_extension(None)"
elif case_sensitive: elif self.is_case_sensitive:
self.file_extension = f".{file_extension}" self._file_extension = f".{file_extension}"
self.name = ( self.name = (
f"Filters.document.file_extension({file_extension!r}," f"Filters.document.file_extension({file_extension!r},"
" case_sensitive=True)" " case_sensitive=True)"
) )
else: else:
self.file_extension = f".{file_extension}".lower() self._file_extension = f".{file_extension}".lower()
self.name = f"Filters.document.file_extension({file_extension.lower()!r})" self.name = f"Filters.document.file_extension({file_extension.lower()!r})"
def filter(self, message: Message) -> bool: def filter(self, message: Message) -> bool:
"""""" # remove method from docs """""" # remove method from docs
if message.document is None: if message.document is None:
return False return False
if self.file_extension is None: if self._file_extension is None:
return "." not in message.document.file_name return "." not in message.document.file_name
if self.is_case_sensitive: if self.is_case_sensitive:
filename = message.document.file_name filename = message.document.file_name
else: else:
filename = message.document.file_name.lower() filename = message.document.file_name.lower()
return filename.endswith(self.file_extension) return filename.endswith(self._file_extension)
def filter(self, message: Message) -> bool: def filter(self, message: Message) -> bool:
return bool(message.document) return bool(message.document)
@ -1328,7 +1320,7 @@ officedocument.wordprocessingml.document")``.
private: Updates sent in private chat private: Updates sent in private chat
""" """
class _ChatUserBaseFilter(MessageFilter): class _ChatUserBaseFilter(MessageFilter, ABC):
def __init__( def __init__(
self, self,
chat_id: SLT[int] = None, chat_id: SLT[int] = None,
@ -1348,7 +1340,7 @@ officedocument.wordprocessingml.document")``.
@abstractmethod @abstractmethod
def get_chat_or_user(self, message: Message) -> Union[Chat, User, None]: def get_chat_or_user(self, message: Message) -> Union[Chat, User, None]:
pass ...
@staticmethod @staticmethod
def _parse_chat_id(chat_id: SLT[int]) -> Set[int]: def _parse_chat_id(chat_id: SLT[int]) -> Set[int]:

View file

@ -149,9 +149,12 @@ class Handler(Generic[UT], ABC):
""" """
run_async = self.run_async run_async = self.run_async
if self.run_async is DEFAULT_FALSE and dispatcher.bot.defaults: if (
if dispatcher.bot.defaults.run_async: self.run_async is DEFAULT_FALSE
run_async = True and dispatcher.bot.defaults
and dispatcher.bot.defaults.run_async
):
run_async = True
if context: if context:
self.collect_additional_context(context, update, dispatcher, check_result) self.collect_additional_context(context, update, dispatcher, check_result)

View file

@ -16,7 +16,7 @@
# #
# You should have received a copy of the GNU Lesser Public License # You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
""" This module contains the InlineQueryHandler class """ """This module contains the InlineQueryHandler class."""
import re import re
from typing import ( from typing import (
TYPE_CHECKING, TYPE_CHECKING,
@ -170,7 +170,6 @@ class InlineQueryHandler(Handler[Update]):
:obj:`bool` :obj:`bool`
""" """
if isinstance(update, Update) and update.inline_query: if isinstance(update, Update) and update.inline_query:
if (self.chat_types is not None) and ( if (self.chat_types is not None) and (
update.inline_query.chat_type not in self.chat_types update.inline_query.chat_type not in self.chat_types
@ -191,6 +190,10 @@ class InlineQueryHandler(Handler[Update]):
update: Update = None, update: Update = None,
check_result: Optional[Union[bool, Match]] = None, check_result: Optional[Union[bool, Match]] = None,
) -> Dict[str, object]: ) -> Dict[str, object]:
"""Pass the results of ``re.match(pattern, query).{groups(), groupdict()}`` to the
callback as a keyword arguments called ``groups`` and ``groupdict``, respectively, if
needed.
"""
optional_args = super().collect_optional_args(dispatcher, update, check_result) optional_args = super().collect_optional_args(dispatcher, update, check_result)
if self.pattern: if self.pattern:
check_result = cast(Match, check_result) check_result = cast(Match, check_result)
@ -207,6 +210,9 @@ class InlineQueryHandler(Handler[Update]):
dispatcher: 'Dispatcher', dispatcher: 'Dispatcher',
check_result: Optional[Union[bool, Match]], check_result: Optional[Union[bool, Match]],
) -> None: ) -> None:
"""Add the result of ``re.match(pattern, update.inline_query.query)`` to
:attr:`CallbackContext.matches` as list with one element.
"""
if self.pattern: if self.pattern:
check_result = cast(Match, check_result) check_result = cast(Match, check_result)
context.matches = [check_result] context.matches = [check_result]

View file

@ -38,11 +38,6 @@ if TYPE_CHECKING:
import apscheduler.job # noqa: F401 import apscheduler.job # noqa: F401
class Days:
MON, TUE, WED, THU, FRI, SAT, SUN = range(7)
EVERY_DAY = tuple(range(7))
class JobQueue: class JobQueue:
"""This class allows you to periodically perform tasks with the bot. It is a convenience """This class allows you to periodically perform tasks with the bot. It is a convenience
wrapper for the APScheduler library. wrapper for the APScheduler library.
@ -136,8 +131,7 @@ class JobQueue:
""" """
self._dispatcher = dispatcher self._dispatcher = dispatcher
if dispatcher.bot.defaults: if dispatcher.bot.defaults:
if dispatcher.bot.defaults: self.scheduler.configure(timezone=dispatcher.bot.defaults.tzinfo or pytz.utc)
self.scheduler.configure(timezone=dispatcher.bot.defaults.tzinfo or pytz.utc)
def run_once( def run_once(
self, self,
@ -392,7 +386,7 @@ class JobQueue:
self, self,
callback: Callable[['CallbackContext'], None], callback: Callable[['CallbackContext'], None],
time: datetime.time, time: datetime.time,
days: Tuple[int, ...] = Days.EVERY_DAY, days: Tuple[int, ...] = tuple(range(7)),
context: object = None, context: object = None,
name: str = None, name: str = None,
job_kwargs: JSONDict = None, job_kwargs: JSONDict = None,
@ -499,14 +493,16 @@ class JobQueue:
self.scheduler.shutdown() self.scheduler.shutdown()
def jobs(self) -> Tuple['Job', ...]: def jobs(self) -> Tuple['Job', ...]:
""" """Returns a tuple of all *scheduled* jobs that are currently in the ``JobQueue``."""
Returns a tuple of all *pending/scheduled* jobs that are currently in the ``JobQueue``. return tuple(
""" Job._from_aps_job(job, self) # pylint: disable=W0212
return tuple(Job.from_aps_job(job, self) for job in self.scheduler.get_jobs()) for job in self.scheduler.get_jobs()
)
def get_jobs_by_name(self, name: str) -> Tuple['Job', ...]: def get_jobs_by_name(self, name: str) -> Tuple['Job', ...]:
"""Returns a tuple of all *pending/scheduled* jobs with the given name that are currently """Returns a tuple of all *pending/scheduled* jobs with the given name that are currently
in the ``JobQueue``""" in the ``JobQueue``.
"""
return tuple(job for job in self.jobs() if job.name == name) return tuple(job for job in self.jobs() if job.name == name)
@ -563,7 +559,7 @@ class Job:
self._removed = False self._removed = False
self._enabled = False self._enabled = False
self.job = cast(APSJob, job) self.job = cast(APSJob, job) # skipcq: PTC-W0052
def run(self, dispatcher: 'Dispatcher') -> None: def run(self, dispatcher: 'Dispatcher') -> None:
"""Executes the callback function independently of the jobs schedule.""" """Executes the callback function independently of the jobs schedule."""
@ -619,7 +615,7 @@ class Job:
return self.job.next_run_time return self.job.next_run_time
@classmethod @classmethod
def from_aps_job(cls, job: APSJob, job_queue: JobQueue) -> 'Job': def _from_aps_job(cls, job: APSJob, job_queue: JobQueue) -> 'Job':
# context based callbacks # context based callbacks
if len(job.args) == 1: if len(job.args) == 1:
context = job.args[0].job.context context = job.args[0].job.context

View file

@ -200,5 +200,6 @@ class MessageHandler(Handler[Update]):
dispatcher: 'Dispatcher', dispatcher: 'Dispatcher',
check_result: Optional[Union[bool, Dict[str, object]]], check_result: Optional[Union[bool, Dict[str, object]]],
) -> None: ) -> None:
"""Adds possible output of data filters to the :class:`CallbackContext`."""
if isinstance(check_result, dict): if isinstance(check_result, dict):
context.update(check_result) context.update(check_result)

View file

@ -113,7 +113,6 @@ class DelayQueue(threading.Thread):
automatically called by autostart argument. automatically called by autostart argument.
""" """
times: List[float] = [] # used to store each callable processing time times: List[float] = [] # used to store each callable processing time
while True: while True:
item = self._queue.get() item = self._queue.get()
@ -150,7 +149,6 @@ class DelayQueue(threading.Thread):
Defaults to :obj:`None`. Defaults to :obj:`None`.
""" """
self.__exit_req = True # gently request self.__exit_req = True # gently request
self._queue.put(None) # put something to unfreeze if frozen self._queue.put(None) # put something to unfreeze if frozen
super().join(timeout=timeout) super().join(timeout=timeout)
@ -162,7 +160,6 @@ class DelayQueue(threading.Thread):
by subclasses. by subclasses.
""" """
raise exc raise exc
def __call__(self, func: Callable, *args: object, **kwargs: object) -> None: def __call__(self, func: Callable, *args: object, **kwargs: object) -> None:
@ -175,7 +172,6 @@ class DelayQueue(threading.Thread):
**kwargs (:obj:`dict`): Arbitrary keyword-arguments to `func`. **kwargs (:obj:`dict`): Arbitrary keyword-arguments to `func`.
""" """
if not self.is_alive() or self.__exit_req: if not self.is_alive() or self.__exit_req:
raise DelayQueueError('Could not process callback in stopped thread') raise DelayQueueError('Could not process callback in stopped thread')
self._queue.put((func, args, kwargs)) self._queue.put((func, args, kwargs))
@ -252,6 +248,7 @@ class MessageQueue:
self._group_delayq.start() self._group_delayq.start()
def stop(self, timeout: float = None) -> None: def stop(self, timeout: float = None) -> None:
"""Stops the ``MessageQueue``."""
self._group_delayq.stop(timeout=timeout) self._group_delayq.stop(timeout=timeout)
self._all_delayq.stop(timeout=timeout) self._all_delayq.stop(timeout=timeout)
@ -281,7 +278,6 @@ class MessageQueue:
:obj:`callable`: Used as ``promise`` argument. :obj:`callable`: Used as ``promise`` argument.
""" """
if not is_group_msg: # ignore middle group delay if not is_group_msg: # ignore middle group delay
self._all_delayq(promise) self._all_delayq(promise)
else: # use middle group delay else: # use middle group delay

View file

@ -95,7 +95,7 @@ class PicklePersistence(BasePersistence):
self.bot_data: Optional[Dict] = None self.bot_data: Optional[Dict] = None
self.conversations: Optional[Dict[str, Dict[Tuple, object]]] = None self.conversations: Optional[Dict[str, Dict[Tuple, object]]] = None
def load_singlefile(self) -> None: def _load_singlefile(self) -> None:
try: try:
filename = self.filename filename = self.filename
with open(self.filename, "rb") as file: with open(self.filename, "rb") as file:
@ -116,7 +116,7 @@ class PicklePersistence(BasePersistence):
raise TypeError(f"Something went wrong unpickling {filename}") from exc raise TypeError(f"Something went wrong unpickling {filename}") from exc
@staticmethod @staticmethod
def load_file(filename: str) -> Any: def _load_file(filename: str) -> Any:
try: try:
with open(filename, "rb") as file: with open(filename, "rb") as file:
return pickle.load(file) return pickle.load(file)
@ -127,7 +127,7 @@ class PicklePersistence(BasePersistence):
except Exception as exc: except Exception as exc:
raise TypeError(f"Something went wrong unpickling {filename}") from exc raise TypeError(f"Something went wrong unpickling {filename}") from exc
def dump_singlefile(self) -> None: def _dump_singlefile(self) -> None:
with open(self.filename, "wb") as file: with open(self.filename, "wb") as file:
data = { data = {
'conversations': self.conversations, 'conversations': self.conversations,
@ -138,7 +138,7 @@ class PicklePersistence(BasePersistence):
pickle.dump(data, file) pickle.dump(data, file)
@staticmethod @staticmethod
def dump_file(filename: str, data: object) -> None: def _dump_file(filename: str, data: object) -> None:
with open(filename, "wb") as file: with open(filename, "wb") as file:
pickle.dump(data, file) pickle.dump(data, file)
@ -152,14 +152,14 @@ class PicklePersistence(BasePersistence):
pass pass
elif not self.single_file: elif not self.single_file:
filename = f"{self.filename}_user_data" filename = f"{self.filename}_user_data"
data = self.load_file(filename) data = self._load_file(filename)
if not data: if not data:
data = defaultdict(dict) data = defaultdict(dict)
else: else:
data = defaultdict(dict, data) data = defaultdict(dict, data)
self.user_data = data self.user_data = data
else: else:
self.load_singlefile() self._load_singlefile()
return deepcopy(self.user_data) # type: ignore[arg-type] return deepcopy(self.user_data) # type: ignore[arg-type]
def get_chat_data(self) -> DefaultDict[int, Dict[object, object]]: def get_chat_data(self) -> DefaultDict[int, Dict[object, object]]:
@ -172,14 +172,14 @@ class PicklePersistence(BasePersistence):
pass pass
elif not self.single_file: elif not self.single_file:
filename = f"{self.filename}_chat_data" filename = f"{self.filename}_chat_data"
data = self.load_file(filename) data = self._load_file(filename)
if not data: if not data:
data = defaultdict(dict) data = defaultdict(dict)
else: else:
data = defaultdict(dict, data) data = defaultdict(dict, data)
self.chat_data = data self.chat_data = data
else: else:
self.load_singlefile() self._load_singlefile()
return deepcopy(self.chat_data) # type: ignore[arg-type] return deepcopy(self.chat_data) # type: ignore[arg-type]
def get_bot_data(self) -> Dict[object, object]: def get_bot_data(self) -> Dict[object, object]:
@ -192,12 +192,12 @@ class PicklePersistence(BasePersistence):
pass pass
elif not self.single_file: elif not self.single_file:
filename = f"{self.filename}_bot_data" filename = f"{self.filename}_bot_data"
data = self.load_file(filename) data = self._load_file(filename)
if not data: if not data:
data = {} data = {}
self.bot_data = data self.bot_data = data
else: else:
self.load_singlefile() self._load_singlefile()
return deepcopy(self.bot_data) # type: ignore[arg-type] return deepcopy(self.bot_data) # type: ignore[arg-type]
def get_conversations(self, name: str) -> ConversationDict: def get_conversations(self, name: str) -> ConversationDict:
@ -213,12 +213,12 @@ class PicklePersistence(BasePersistence):
pass pass
elif not self.single_file: elif not self.single_file:
filename = f"{self.filename}_conversations" filename = f"{self.filename}_conversations"
data = self.load_file(filename) data = self._load_file(filename)
if not data: if not data:
data = {name: {}} data = {name: {}}
self.conversations = data self.conversations = data
else: else:
self.load_singlefile() self._load_singlefile()
return self.conversations.get(name, {}).copy() # type: ignore[union-attr] return self.conversations.get(name, {}).copy() # type: ignore[union-attr]
def update_conversation( def update_conversation(
@ -240,9 +240,9 @@ class PicklePersistence(BasePersistence):
if not self.on_flush: if not self.on_flush:
if not self.single_file: if not self.single_file:
filename = f"{self.filename}_conversations" filename = f"{self.filename}_conversations"
self.dump_file(filename, self.conversations) self._dump_file(filename, self.conversations)
else: else:
self.dump_singlefile() self._dump_singlefile()
def update_user_data(self, user_id: int, data: Dict) -> None: def update_user_data(self, user_id: int, data: Dict) -> None:
"""Will update the user_data and depending on :attr:`on_flush` save the pickle file. """Will update the user_data and depending on :attr:`on_flush` save the pickle file.
@ -259,9 +259,9 @@ class PicklePersistence(BasePersistence):
if not self.on_flush: if not self.on_flush:
if not self.single_file: if not self.single_file:
filename = f"{self.filename}_user_data" filename = f"{self.filename}_user_data"
self.dump_file(filename, self.user_data) self._dump_file(filename, self.user_data)
else: else:
self.dump_singlefile() self._dump_singlefile()
def update_chat_data(self, chat_id: int, data: Dict) -> None: def update_chat_data(self, chat_id: int, data: Dict) -> None:
"""Will update the chat_data and depending on :attr:`on_flush` save the pickle file. """Will update the chat_data and depending on :attr:`on_flush` save the pickle file.
@ -278,9 +278,9 @@ class PicklePersistence(BasePersistence):
if not self.on_flush: if not self.on_flush:
if not self.single_file: if not self.single_file:
filename = f"{self.filename}_chat_data" filename = f"{self.filename}_chat_data"
self.dump_file(filename, self.chat_data) self._dump_file(filename, self.chat_data)
else: else:
self.dump_singlefile() self._dump_singlefile()
def update_bot_data(self, data: Dict) -> None: def update_bot_data(self, data: Dict) -> None:
"""Will update the bot_data and depending on :attr:`on_flush` save the pickle file. """Will update the bot_data and depending on :attr:`on_flush` save the pickle file.
@ -294,21 +294,21 @@ class PicklePersistence(BasePersistence):
if not self.on_flush: if not self.on_flush:
if not self.single_file: if not self.single_file:
filename = f"{self.filename}_bot_data" filename = f"{self.filename}_bot_data"
self.dump_file(filename, self.bot_data) self._dump_file(filename, self.bot_data)
else: else:
self.dump_singlefile() self._dump_singlefile()
def flush(self) -> None: def flush(self) -> None:
"""Will save all data in memory to pickle file(s).""" """Will save all data in memory to pickle file(s)."""
if self.single_file: if self.single_file:
if self.user_data or self.chat_data or self.bot_data or self.conversations: if self.user_data or self.chat_data or self.bot_data or self.conversations:
self.dump_singlefile() self._dump_singlefile()
else: else:
if self.user_data: if self.user_data:
self.dump_file(f"{self.filename}_user_data", self.user_data) self._dump_file(f"{self.filename}_user_data", self.user_data)
if self.chat_data: if self.chat_data:
self.dump_file(f"{self.filename}_chat_data", self.chat_data) self._dump_file(f"{self.filename}_chat_data", self.chat_data)
if self.bot_data: if self.bot_data:
self.dump_file(f"{self.filename}_bot_data", self.bot_data) self._dump_file(f"{self.filename}_bot_data", self.bot_data)
if self.conversations: if self.conversations:
self.dump_file(f"{self.filename}_conversations", self.conversations) self._dump_file(f"{self.filename}_conversations", self.conversations)

View file

@ -150,6 +150,10 @@ class RegexHandler(MessageHandler):
update: Update = None, update: Update = None,
check_result: Optional[Union[bool, Dict[str, Any]]] = None, check_result: Optional[Union[bool, Dict[str, Any]]] = None,
) -> Dict[str, object]: ) -> Dict[str, object]:
"""Pass the results of ``re.match(pattern, text).{groups(), groupdict()}`` to the
callback as a keyword arguments called ``groups`` and ``groupdict``, respectively, if
needed.
"""
optional_args = super().collect_optional_args(dispatcher, update, check_result) optional_args = super().collect_optional_args(dispatcher, update, check_result)
if isinstance(check_result, dict): if isinstance(check_result, dict):
if self.pass_groups: if self.pass_groups:

View file

@ -32,6 +32,9 @@ RT = TypeVar('RT')
class StringCommandHandler(Handler[str]): class StringCommandHandler(Handler[str]):
"""Handler class to handle string commands. Commands are string updates that start with ``/``. """Handler class to handle string commands. Commands are string updates that start with ``/``.
The handler will add a ``list`` to the
:class:`CallbackContext` named :attr:`CallbackContext.args`. It will contain a list of strings,
which is the text following the command split on single whitespace characters.
Note: Note:
This handler is not used to handle Telegram :attr:`telegram.Update`, but strings manually This handler is not used to handle Telegram :attr:`telegram.Update`, but strings manually
@ -122,6 +125,9 @@ class StringCommandHandler(Handler[str]):
update: str = None, update: str = None,
check_result: Optional[List[str]] = None, check_result: Optional[List[str]] = None,
) -> Dict[str, object]: ) -> Dict[str, object]:
"""Provide text after the command to the callback the ``args`` argument as list, split on
single whitespaces.
"""
optional_args = super().collect_optional_args(dispatcher, update, check_result) optional_args = super().collect_optional_args(dispatcher, update, check_result)
if self.pass_args: if self.pass_args:
optional_args['args'] = check_result optional_args['args'] = check_result
@ -134,4 +140,7 @@ class StringCommandHandler(Handler[str]):
dispatcher: 'Dispatcher', dispatcher: 'Dispatcher',
check_result: Optional[List[str]], check_result: Optional[List[str]],
) -> None: ) -> None:
"""Add text after the command to :attr:`CallbackContext.args` as list, split on single
whitespaces.
"""
context.args = check_result context.args = check_result

View file

@ -137,6 +137,10 @@ class StringRegexHandler(Handler[str]):
update: str = None, update: str = None,
check_result: Optional[Match] = None, check_result: Optional[Match] = None,
) -> Dict[str, object]: ) -> Dict[str, object]:
"""Pass the results of ``re.match(pattern, update).{groups(), groupdict()}`` to the
callback as a keyword arguments called ``groups`` and ``groupdict``, respectively, if
needed.
"""
optional_args = super().collect_optional_args(dispatcher, update, check_result) optional_args = super().collect_optional_args(dispatcher, update, check_result)
if self.pattern: if self.pattern:
if self.pass_groups and check_result: if self.pass_groups and check_result:
@ -152,5 +156,8 @@ class StringRegexHandler(Handler[str]):
dispatcher: 'Dispatcher', dispatcher: 'Dispatcher',
check_result: Optional[Match], check_result: Optional[Match],
) -> None: ) -> None:
"""Add the result of ``re.match(pattern, update)`` to :attr:`CallbackContext.matches` as
list with one element.
"""
if self.pattern and check_result: if self.pattern and check_result:
context.matches = [check_result] context.matches = [check_result]

View file

@ -547,9 +547,9 @@ class Updater:
if current_interval == 0: if current_interval == 0:
current_interval = 1 current_interval = 1
elif current_interval < 30: elif current_interval < 30:
current_interval += current_interval / 2 current_interval *= 1.5
elif current_interval > 30: else:
current_interval = 30 current_interval = min(30.0, current_interval)
return current_interval return current_interval
@no_type_check @no_type_check
@ -598,14 +598,17 @@ class Updater:
webhook_url = self._gen_webhook_url(listen, port, url_path) webhook_url = self._gen_webhook_url(listen, port, url_path)
# We pass along the cert to the webhook if present. # We pass along the cert to the webhook if present.
cert_file = open(cert, 'rb') if cert is not None else None
self._bootstrap( self._bootstrap(
max_retries=bootstrap_retries, max_retries=bootstrap_retries,
drop_pending_updates=drop_pending_updates, drop_pending_updates=drop_pending_updates,
webhook_url=webhook_url, webhook_url=webhook_url,
allowed_updates=allowed_updates, allowed_updates=allowed_updates,
cert=open(cert, 'rb') if cert is not None else None, cert=cert_file,
ip_address=ip_address, ip_address=ip_address,
) )
if cert_file is not None:
cert_file.close()
self.httpd.serve_forever(ready=ready) self.httpd.serve_forever(ready=ready)
@ -681,7 +684,6 @@ class Updater:
def stop(self) -> None: def stop(self) -> None:
"""Stops the polling/webhook thread, the dispatcher and the job queue.""" """Stops the polling/webhook thread, the dispatcher and the job queue."""
self.job_queue.stop() self.job_queue.stop()
with self.__lock: with self.__lock:
if self.running or self.dispatcher.has_running_threads: if self.running or self.dispatcher.has_running_threads:
@ -722,7 +724,7 @@ class Updater:
self.__threads = [] self.__threads = []
@no_type_check @no_type_check
def signal_handler(self, signum, frame) -> None: def _signal_handler(self, signum, frame) -> None:
self.is_idle = False self.is_idle = False
if self.running: if self.running:
self.logger.info( self.logger.info(
@ -752,7 +754,7 @@ class Updater:
""" """
for sig in stop_signals: for sig in stop_signals:
signal(sig, self.signal_handler) signal(sig, self._signal_handler)
self.is_idle = True self.is_idle = True

View file

@ -75,7 +75,6 @@ class Promise:
def run(self) -> None: def run(self) -> None:
"""Calls the :attr:`pooled_function` callable.""" """Calls the :attr:`pooled_function` callable."""
try: try:
self._result = self.pooled_function(*self.args, **self.kwargs) self._result = self.pooled_function(*self.args, **self.kwargs)
@ -133,5 +132,6 @@ class Promise:
@property @property
def exception(self) -> Optional[Exception]: def exception(self) -> Optional[Exception]:
"""The exception raised by :attr:`pooled_function` or ``None`` if no exception has been """The exception raised by :attr:`pooled_function` or ``None`` if no exception has been
raised (yet).""" raised (yet).
"""
return self._exception return self._exception

View file

@ -91,7 +91,7 @@ class WebhookAppClass(tornado.web.Application):
handlers = [(rf"{webhook_path}/?", WebhookHandler, self.shared_objects)] # noqa handlers = [(rf"{webhook_path}/?", WebhookHandler, self.shared_objects)] # noqa
tornado.web.Application.__init__(self, handlers) # type: ignore tornado.web.Application.__init__(self, handlers) # type: ignore
def log_request(self, handler: tornado.web.RequestHandler) -> None: def log_request(self, handler: tornado.web.RequestHandler) -> None: # skipcq: PTC-W0049
pass pass

View file

@ -96,7 +96,8 @@ class Animation(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Animation']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Animation']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -100,7 +100,8 @@ class Audio(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Audio']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Audio']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -87,7 +87,8 @@ class Document(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Document']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Document']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -195,4 +195,9 @@ class File(TelegramObject):
return buf return buf
def set_credentials(self, credentials: 'FileCredentials') -> None: def set_credentials(self, credentials: 'FileCredentials') -> None:
"""Sets the passport credentials for the file.
Args:
credentials (:class:`telegram.FileCredentials`): The credentials.
"""
self._credentials = credentials self._credentials = credentials

View file

@ -75,7 +75,7 @@ class InputFile:
self.filename = self.mimetype.replace('/', '.') self.filename = self.mimetype.replace('/', '.')
@property @property
def field_tuple(self) -> Tuple[str, bytes, str]: def field_tuple(self) -> Tuple[str, bytes, str]: # skipcq: PY-D0003
return self.filename, self.input_file_content, self.mimetype return self.filename, self.input_file_content, self.mimetype
@staticmethod @staticmethod
@ -102,10 +102,11 @@ class InputFile:
return None return None
@staticmethod @staticmethod
def is_file(obj: object) -> bool: def is_file(obj: object) -> bool: # skipcq: PY-D0003
return hasattr(obj, 'read') return hasattr(obj, 'read')
def to_dict(self) -> Optional[str]: def to_dict(self) -> Optional[str]:
"""See :meth:`telegram.TelegramObject.to_dict`."""
if self.attach: if self.attach:
return 'attach://' + self.attach return 'attach://' + self.attach
return None return None

View file

@ -46,6 +46,7 @@ class InputMedia(TelegramObject):
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...], None] = None caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...], None] = None
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
if self.caption_entities: if self.caption_entities:

View file

@ -106,7 +106,8 @@ class Sticker(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Sticker']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Sticker']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -181,6 +182,7 @@ class StickerSet(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['StickerSet']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['StickerSet']:
"""See :meth:`telegram.TelegramObject.de_json`."""
if not data: if not data:
return None return None
@ -190,6 +192,7 @@ class StickerSet(TelegramObject):
return cls(bot=bot, **data) return cls(bot=bot, **data)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
data['stickers'] = [s.to_dict() for s in data.get('stickers')] data['stickers'] = [s.to_dict() for s in data.get('stickers')]
@ -249,7 +252,8 @@ class MaskPosition(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['MaskPosition']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['MaskPosition']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None: if data is None:
return None return None

View file

@ -85,7 +85,8 @@ class Venue(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Venue']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Venue']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -97,7 +97,8 @@ class Video(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Video']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Video']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -86,7 +86,8 @@ class VideoNote(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['VideoNote']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['VideoNote']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -90,7 +90,8 @@ class Game(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Game']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Game']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -102,6 +103,7 @@ class Game(TelegramObject):
return cls(**data) return cls(**data)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
data['photo'] = [p.to_dict() for p in self.photo] data['photo'] = [p.to_dict() for p in self.photo]

View file

@ -54,7 +54,8 @@ class GameHighScore(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['GameHighScore']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['GameHighScore']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -52,6 +52,7 @@ class InlineKeyboardMarkup(ReplyMarkup):
self._id_attrs = (self.inline_keyboard,) self._id_attrs = (self.inline_keyboard,)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
data['inline_keyboard'] = [] data['inline_keyboard'] = []
@ -62,7 +63,8 @@ class InlineKeyboardMarkup(ReplyMarkup):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['InlineKeyboardMarkup']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['InlineKeyboardMarkup']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -97,7 +97,8 @@ class InlineQuery(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['InlineQuery']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['InlineQuery']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -54,6 +54,7 @@ class InlineQueryResult(TelegramObject):
self._id_attrs = (self.id,) self._id_attrs = (self.id,)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
# pylint: disable=E1101 # pylint: disable=E1101

View file

@ -196,6 +196,7 @@ class InputInvoiceMessageContent(InputMessageContent):
) )
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
data['prices'] = [price.to_dict() for price in self.prices] data['prices'] = [price.to_dict() for price in self.prices]
@ -206,7 +207,8 @@ class InputInvoiceMessageContent(InputMessageContent):
def de_json( def de_json(
cls, data: Optional[JSONDict], bot: 'Bot' cls, data: Optional[JSONDict], bot: 'Bot'
) -> Optional['InputInvoiceMessageContent']: ) -> Optional['InputInvoiceMessageContent']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -58,6 +58,7 @@ class InputLocationMessageContent(InputMessageContent):
proximity alerts about approaching another chat member, in meters. proximity alerts about approaching another chat member, in meters.
""" """
# fmt: on # fmt: on
def __init__( def __init__(

View file

@ -77,6 +77,7 @@ class InputTextMessageContent(InputMessageContent):
self._id_attrs = (self.message_text,) self._id_attrs = (self.message_text,)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
if self.entities: if self.entities:

View file

@ -331,6 +331,7 @@ class Message(TelegramObject):
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
""" """
# fmt: on # fmt: on
_effective_attachment = _UNDEFINED _effective_attachment = _UNDEFINED
@ -506,7 +507,8 @@ class Message(TelegramObject):
@property @property
def link(self) -> Optional[str]: def link(self) -> Optional[str]:
""":obj:`str`: Convenience property. If the chat of the message is not """:obj:`str`: Convenience property. If the chat of the message is not
a private chat or normal group, returns a t.me link of the message.""" a private chat or normal group, returns a t.me link of the message.
"""
if self.chat.type not in [Chat.PRIVATE, Chat.GROUP]: if self.chat.type not in [Chat.PRIVATE, Chat.GROUP]:
if self.chat.username: if self.chat.username:
to_link = self.chat.username to_link = self.chat.username
@ -518,7 +520,8 @@ class Message(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Message']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Message']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -629,6 +632,7 @@ class Message(TelegramObject):
return self.chat.id return self.chat.id
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
# Required # Required

View file

@ -17,7 +17,8 @@
# You should have received a copy of the GNU Lesser Public License # You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a change in the Telegram message auto """This module contains an object that represents a change in the Telegram message auto
deletion.""" deletion.
"""
from typing import Any from typing import Any

View file

@ -82,7 +82,8 @@ class MessageEntity(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['MessageEntity']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['MessageEntity']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -49,9 +49,7 @@ if TYPE_CHECKING:
class TelegramDecryptionError(TelegramError): class TelegramDecryptionError(TelegramError):
""" """Something went wrong with decryption."""
Something went wrong with decryption.
"""
def __init__(self, message: Union[str, Exception]): def __init__(self, message: Union[str, Exception]):
super().__init__(f"TelegramDecryptionError: {message}") super().__init__(f"TelegramDecryptionError: {message}")
@ -181,7 +179,7 @@ class EncryptedCredentials(TelegramObject):
try: try:
self._decrypted_secret = self.bot.private_key.decrypt( self._decrypted_secret = self.bot.private_key.decrypt(
b64decode(self.secret), b64decode(self.secret),
OAEP(mgf=MGF1(algorithm=SHA1()), algorithm=SHA1(), label=None), OAEP(mgf=MGF1(algorithm=SHA1()), algorithm=SHA1(), label=None), # skipcq
) )
except ValueError as exception: except ValueError as exception:
# If decryption fails raise exception # If decryption fails raise exception
@ -223,7 +221,8 @@ class Credentials(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Credentials']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Credentials']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -294,7 +293,8 @@ class SecureData(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['SecureData']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['SecureData']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -367,7 +367,8 @@ class SecureValue(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['SecureValue']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['SecureValue']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -382,6 +383,7 @@ class SecureValue(TelegramObject):
return cls(bot=bot, **data) return cls(bot=bot, **data)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
data['files'] = [p.to_dict() for p in self.files] data['files'] = [p.to_dict() for p in self.files]
@ -422,6 +424,7 @@ class DataCredentials(_CredentialsBase):
super().__init__(data_hash, secret, **_kwargs) super().__init__(data_hash, secret, **_kwargs)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
del data['file_hash'] del data['file_hash']
@ -448,6 +451,7 @@ class FileCredentials(_CredentialsBase):
super().__init__(file_hash, secret, **_kwargs) super().__init__(file_hash, secret, **_kwargs)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
del data['data_hash'] del data['data_hash']

View file

@ -162,7 +162,8 @@ class EncryptedPassportElement(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['EncryptedPassportElement']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['EncryptedPassportElement']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -179,6 +180,18 @@ class EncryptedPassportElement(TelegramObject):
def de_json_decrypted( def de_json_decrypted(
cls, data: Optional[JSONDict], bot: 'Bot', credentials: 'Credentials' cls, data: Optional[JSONDict], bot: 'Bot', credentials: 'Credentials'
) -> Optional['EncryptedPassportElement']: ) -> Optional['EncryptedPassportElement']:
"""Variant of :meth:`telegram.TelegramObject.de_json` that also takes into account
passport credentials.
Args:
data (Dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot`): The bot associated with this object.
credentials (:class:`telegram.FileCredentials`): The credentials
Returns:
:class:`telegram.EncryptedPassportElement`:
"""
if not data: if not data:
return None return None
@ -227,6 +240,7 @@ class EncryptedPassportElement(TelegramObject):
return cls(bot=bot, **data) return cls(bot=bot, **data)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
if self.files: if self.files:

View file

@ -67,7 +67,8 @@ class PassportData(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['PassportData']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['PassportData']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -78,6 +79,7 @@ class PassportData(TelegramObject):
return cls(bot=bot, **data) return cls(bot=bot, **data)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
data['data'] = [e.to_dict() for e in self.data] data['data'] = [e.to_dict() for e in self.data]

View file

@ -83,7 +83,19 @@ class PassportFile(TelegramObject):
def de_json_decrypted( def de_json_decrypted(
cls, data: Optional[JSONDict], bot: 'Bot', credentials: 'FileCredentials' cls, data: Optional[JSONDict], bot: 'Bot', credentials: 'FileCredentials'
) -> Optional['PassportFile']: ) -> Optional['PassportFile']:
data = cls.parse_data(data) """Variant of :meth:`telegram.TelegramObject.de_json` that also takes into account
passport credentials.
Args:
data (Dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot`): The bot associated with this object.
credentials (:class:`telegram.FileCredentials`): The credentials
Returns:
:class:`telegram.PassportFile`:
"""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -96,6 +108,18 @@ class PassportFile(TelegramObject):
def de_list_decrypted( def de_list_decrypted(
cls, data: Optional[List[JSONDict]], bot: 'Bot', credentials: List['FileCredentials'] cls, data: Optional[List[JSONDict]], bot: 'Bot', credentials: List['FileCredentials']
) -> List[Optional['PassportFile']]: ) -> List[Optional['PassportFile']]:
"""Variant of :meth:`telegram.TelegramObject.de_list` that also takes into account
passport credentials.
Args:
data (Dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot`): The bot associated with these objects.
credentials (:class:`telegram.FileCredentials`): The credentials
Returns:
List[:class:`telegram.PassportFile`]:
"""
if not data: if not data:
return [] return []

View file

@ -66,7 +66,8 @@ class OrderInfo(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['OrderInfo']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['OrderInfo']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return cls() return cls()

View file

@ -93,7 +93,8 @@ class PreCheckoutQuery(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['PreCheckoutQuery']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['PreCheckoutQuery']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -60,6 +60,7 @@ class ShippingOption(TelegramObject):
self._id_attrs = (self.id,) self._id_attrs = (self.id,)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
data['prices'] = [p.to_dict() for p in self.prices] data['prices'] = [p.to_dict() for p in self.prices]

View file

@ -74,7 +74,8 @@ class ShippingQuery(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ShippingQuery']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ShippingQuery']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -85,7 +85,8 @@ class SuccessfulPayment(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['SuccessfulPayment']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['SuccessfulPayment']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -87,7 +87,8 @@ class PollAnswer(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['PollAnswer']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['PollAnswer']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -180,7 +181,8 @@ class Poll(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Poll']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Poll']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -192,6 +194,7 @@ class Poll(TelegramObject):
return cls(**data) return cls(**data)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
data['options'] = [x.to_dict() for x in self.options] data['options'] = [x.to_dict() for x in self.options]

View file

@ -55,7 +55,8 @@ class ProximityAlertTriggered(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ProximityAlertTriggered']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ProximityAlertTriggered']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -92,6 +92,7 @@ class ReplyKeyboardMarkup(ReplyMarkup):
self._id_attrs = (self.keyboard,) self._id_attrs = (self.keyboard,)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
data['keyboard'] = [] data['keyboard'] = []

View file

@ -342,7 +342,8 @@ class Update(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Update']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Update']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None

View file

@ -128,7 +128,8 @@ class User(TelegramObject):
@property @property
def name(self) -> str: def name(self) -> str:
""":obj:`str`: Convenience property. If available, returns the user's :attr:`username` """:obj:`str`: Convenience property. If available, returns the user's :attr:`username`
prefixed with "@". If :attr:`username` is not available, returns :attr:`full_name`.""" prefixed with "@". If :attr:`username` is not available, returns :attr:`full_name`.
"""
if self.username: if self.username:
return f'@{self.username}' return f'@{self.username}'
return self.full_name return self.full_name
@ -136,8 +137,8 @@ class User(TelegramObject):
@property @property
def full_name(self) -> str: def full_name(self) -> str:
""":obj:`str`: Convenience property. The user's :attr:`first_name`, followed by (if """:obj:`str`: Convenience property. The user's :attr:`first_name`, followed by (if
available) :attr:`last_name`.""" available) :attr:`last_name`.
"""
if self.last_name: if self.last_name:
return f'{self.first_name} {self.last_name}' return f'{self.first_name} {self.last_name}'
return self.first_name return self.first_name
@ -145,8 +146,8 @@ class User(TelegramObject):
@property @property
def link(self) -> Optional[str]: def link(self) -> Optional[str]:
""":obj:`str`: Convenience property. If :attr:`username` is available, returns a t.me link """:obj:`str`: Convenience property. If :attr:`username` is available, returns a t.me link
of the user.""" of the user.
"""
if self.username: if self.username:
return f"https://t.me/{self.username}" return f"https://t.me/{self.username}"
return None return None
@ -167,7 +168,6 @@ class User(TelegramObject):
:meth:`telegram.Bot.get_user_profile_photos`. :meth:`telegram.Bot.get_user_profile_photos`.
""" """
return self.bot.get_user_profile_photos( return self.bot.get_user_profile_photos(
user_id=self.id, user_id=self.id,
offset=offset, offset=offset,

View file

@ -53,7 +53,8 @@ class UserProfilePhotos(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['UserProfilePhotos']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['UserProfilePhotos']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -63,6 +64,7 @@ class UserProfilePhotos(TelegramObject):
return cls(**data) return cls(**data)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
data['photos'] = [] data['photos'] = []

View file

@ -18,32 +18,9 @@
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module facilitates the deprecation of functions.""" """This module facilitates the deprecation of functions."""
import warnings
from typing import Callable, TypeVar
RT = TypeVar('RT')
# We use our own DeprecationWarning since they are muted by default and "UserWarning" makes it # We use our own DeprecationWarning since they are muted by default and "UserWarning" makes it
# seem like it's the user that issued the warning # seem like it's the user that issued the warning
# We name it something else so that you don't get confused when you attempt to suppress it # We name it something else so that you don't get confused when you attempt to suppress it
class TelegramDeprecationWarning(Warning): class TelegramDeprecationWarning(Warning):
pass """Custom warning class for deprecations in this library."""
def warn_deprecate_obj(old: str, new: str, stacklevel: int = 3) -> None:
warnings.warn(
f'{old} is being deprecated, please use {new} from now on.',
category=TelegramDeprecationWarning,
stacklevel=stacklevel,
)
def deprecate(func: Callable[..., RT], old: str, new: str) -> Callable[..., RT]:
"""Warn users invoking old to switch to the new function."""
def wrapped(*args: object, **kwargs: object) -> RT:
warn_deprecate_obj(old, new)
return func(*args, **kwargs)
return wrapped

View file

@ -186,9 +186,7 @@ def _datetime_to_float_timestamp(dt_obj: dtm.datetime) -> float:
def _localize(datetime: dtm.datetime, tzinfo: dtm.tzinfo) -> dtm.datetime: def _localize(datetime: dtm.datetime, tzinfo: dtm.tzinfo) -> dtm.datetime:
""" """Localize the datetime, where UTC is handled depending on whether pytz is available or not"""
Localize the datetime, where UTC is handled depending on whether pytz is available or not
"""
if tzinfo is DTM_UTC: if tzinfo is DTM_UTC:
return datetime.replace(tzinfo=DTM_UTC) return datetime.replace(tzinfo=DTM_UTC)
return tzinfo.localize(datetime) # type: ignore[attr-defined] return tzinfo.localize(datetime) # type: ignore[attr-defined]
@ -250,7 +248,6 @@ def to_float_timestamp(
ValueError: If ``t`` is a :obj:`datetime.datetime` and :obj:`reference_timestamp` is not ValueError: If ``t`` is a :obj:`datetime.datetime` and :obj:`reference_timestamp` is not
:obj:`None`. :obj:`None`.
""" """
if reference_timestamp is None: if reference_timestamp is None:
reference_timestamp = time.time() reference_timestamp = time.time()
elif isinstance(time_object, dtm.datetime): elif isinstance(time_object, dtm.datetime):
@ -369,7 +366,6 @@ def effective_message_type(entity: Union['Message', 'Update']) -> Optional[str]:
:obj:`str`: One of ``Message.MESSAGE_TYPES`` :obj:`str`: One of ``Message.MESSAGE_TYPES``
""" """
# Importing on file-level yields cyclic Import Errors # Importing on file-level yields cyclic Import Errors
from telegram import Message, Update # pylint: disable=C0415 from telegram import Message, Update # pylint: disable=C0415
@ -482,7 +478,6 @@ def decode_user_chat_data_from_json(data: str) -> DefaultDict[int, Dict[object,
Returns: Returns:
:obj:`dict`: The user/chat_data defaultdict after decoding :obj:`dict`: The user/chat_data defaultdict after decoding
""" """
tmp: DefaultDict[int, Dict[object, object]] = defaultdict(dict) tmp: DefaultDict[int, Dict[object, object]] = defaultdict(dict)
decoded_data = json.loads(data) decoded_data = json.loads(data)
for user, user_data in decoded_data.items(): for user, user_data in decoded_data.items():

View file

@ -17,7 +17,8 @@
# You should have received a copy of the GNU Lesser Public License # You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the :class:`telegram.ext.utils.promise.Promise` class for backwards """This module contains the :class:`telegram.ext.utils.promise.Promise` class for backwards
compatibility.""" compatibility.
"""
import warnings import warnings
import telegram.ext.utils.promise as promise import telegram.ext.utils.promise as promise

View file

@ -73,7 +73,7 @@ from telegram.utils.types import JSONDict
def _render_part(self: RequestField, name: str, value: str) -> str: # pylint: disable=W0613 def _render_part(self: RequestField, name: str, value: str) -> str: # pylint: disable=W0613
""" r"""
Monkey patch urllib3.urllib3.fields.RequestField to make it *not* support RFC2231 compliant Monkey patch urllib3.urllib3.fields.RequestField to make it *not* support RFC2231 compliant
Content-Disposition headers since telegram servers don't understand it. Instead just escape Content-Disposition headers since telegram servers don't understand it. Instead just escape
\\ and " and replace any \n and \r with a space. \\ and " and replace any \n and \r with a space.
@ -195,6 +195,7 @@ class Request:
return self._con_pool_size return self._con_pool_size
def stop(self) -> None: def stop(self) -> None:
"""Performs cleanup on shutdown."""
self._con_pool.clear() # type: ignore self._con_pool.clear() # type: ignore
@staticmethod @staticmethod
@ -205,7 +206,6 @@ class Request:
dict: A JSON parsed as Python dict with results - on error this dict will be empty. dict: A JSON parsed as Python dict with results - on error this dict will be empty.
""" """
decoded_s = json_data.decode('utf-8', 'replace') decoded_s = json_data.decode('utf-8', 'replace')
try: try:
data = json.loads(decoded_s) data = json.loads(decoded_s)

View file

@ -17,7 +17,8 @@
# You should have received a copy of the GNU Lesser Public License # You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the :class:`telegram.ext.utils.webhookhandler.WebhookHandler` class for """This module contains the :class:`telegram.ext.utils.webhookhandler.WebhookHandler` class for
backwards compatibility.""" backwards compatibility.
"""
import warnings import warnings
import telegram.ext.utils.webhookhandler as webhook_handler import telegram.ext.utils.webhookhandler as webhook_handler

View file

@ -38,7 +38,7 @@ class VoiceChatStarted(TelegramObject):
.. versionadded:: 13.4 .. versionadded:: 13.4
""" """
def __init__(self, **_kwargs: Any): def __init__(self, **_kwargs: Any): # skipcq: PTC-W0049
pass pass
@ -100,7 +100,8 @@ class VoiceChatParticipantsInvited(TelegramObject):
def de_json( def de_json(
cls, data: Optional[JSONDict], bot: 'Bot' cls, data: Optional[JSONDict], bot: 'Bot'
) -> Optional['VoiceChatParticipantsInvited']: ) -> Optional['VoiceChatParticipantsInvited']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -109,6 +110,7 @@ class VoiceChatParticipantsInvited(TelegramObject):
return cls(**data) return cls(**data)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
data["users"] = [u.to_dict() for u in self.users] data["users"] = [u.to_dict() for u in self.users]
@ -139,7 +141,8 @@ class VoiceChatScheduled(TelegramObject):
@classmethod @classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['VoiceChatScheduled']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['VoiceChatScheduled']:
data = cls.parse_data(data) """See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data: if not data:
return None return None
@ -149,6 +152,7 @@ class VoiceChatScheduled(TelegramObject):
return cls(**data, bot=bot) return cls(**data, bot=bot)
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict() data = super().to_dict()
# Required # Required

View file

@ -97,10 +97,9 @@ def default_bot(request, bot_info):
default_bot = DEFAULT_BOTS.get(defaults) default_bot = DEFAULT_BOTS.get(defaults)
if default_bot: if default_bot:
return default_bot return default_bot
else: default_bot = make_bot(bot_info, **{'defaults': defaults})
default_bot = make_bot(bot_info, **{'defaults': defaults}) DEFAULT_BOTS[defaults] = default_bot
DEFAULT_BOTS[defaults] = default_bot return default_bot
return default_bot
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
@ -109,10 +108,9 @@ def tz_bot(timezone, bot_info):
default_bot = DEFAULT_BOTS.get(defaults) default_bot = DEFAULT_BOTS.get(defaults)
if default_bot: if default_bot:
return default_bot return default_bot
else: default_bot = make_bot(bot_info, **{'defaults': defaults})
default_bot = make_bot(bot_info, **{'defaults': defaults}) DEFAULT_BOTS[defaults] = default_bot
DEFAULT_BOTS[defaults] = default_bot return default_bot
return default_bot
@pytest.fixture(scope='session') @pytest.fixture(scope='session')

View file

@ -36,7 +36,7 @@ def terminal_summary_wrapper(original, plugin_name):
@pytest.mark.trylast @pytest.mark.trylast
def pytest_configure(config): def pytest_configure(config):
for hookimpl in config.pluginmanager.hook.pytest_terminal_summary._nonwrappers: for hookimpl in config.pluginmanager.hook.pytest_terminal_summary._nonwrappers:
if hookimpl.plugin_name in fold_plugins.keys(): if hookimpl.plugin_name in fold_plugins:
hookimpl.function = terminal_summary_wrapper(hookimpl.function, hookimpl.plugin_name) hookimpl.function = terminal_summary_wrapper(hookimpl.function, hookimpl.plugin_name)

View file

@ -260,7 +260,6 @@ class TestAnimation:
animation = Animation.de_json(json_dict, bot) animation = Animation.de_json(json_dict, bot)
assert animation.file_id == self.animation_file_id assert animation.file_id == self.animation_file_id
assert animation.file_unique_id == self.animation_file_unique_id assert animation.file_unique_id == self.animation_file_unique_id
assert animation.thumb == animation.thumb
assert animation.file_name == self.file_name assert animation.file_name == self.file_name
assert animation.mime_type == self.mime_type assert animation.mime_type == self.mime_type
assert animation.file_size == self.file_size assert animation.file_size == self.file_size

View file

@ -81,7 +81,7 @@ def chat_permissions():
def inline_results_callback(page=None): def inline_results_callback(page=None):
if not page: if not page:
return [InlineQueryResultArticle(i, str(i), None) for i in range(1, 254)] return [InlineQueryResultArticle(i, str(i), None) for i in range(1, 254)]
elif page <= 5: if page <= 5:
return [ return [
InlineQueryResultArticle(i, str(i), None) InlineQueryResultArticle(i, str(i), None)
for i in range(page * 5 + 1, (page + 1) * 5 + 1) for i in range(page * 5 + 1, (page + 1) * 5 + 1)
@ -217,11 +217,13 @@ class TestBot:
@flaky(3, 1) @flaky(3, 1)
def test_forward_message(self, bot, chat_id, message): def test_forward_message(self, bot, chat_id, message):
message = bot.forward_message(chat_id, from_chat_id=chat_id, message_id=message.message_id) forward_message = bot.forward_message(
chat_id, from_chat_id=chat_id, message_id=message.message_id
)
assert message.text == message.text assert forward_message.text == message.text
assert message.forward_from.username == message.from_user.username assert forward_message.forward_from.username == message.from_user.username
assert isinstance(message.forward_date, dtm.datetime) assert isinstance(forward_message.forward_date, dtm.datetime)
@flaky(3, 1) @flaky(3, 1)
def test_delete_message(self, bot, chat_id): def test_delete_message(self, bot, chat_id):
@ -787,7 +789,7 @@ class TestBot:
def make_assertion(url, data, *args, **kwargs): def make_assertion(url, data, *args, **kwargs):
results = data['results'] results = data['results']
length_matches = len(results) == num_results length_matches = len(results) == num_results
ids_match = all([int(res['id']) == id_offset + i for i, res in enumerate(results)]) ids_match = all(int(res['id']) == id_offset + i for i, res in enumerate(results))
next_offset_matches = data['next_offset'] == str(expected_next_offset) next_offset_matches = data['next_offset'] == str(expected_next_offset)
return length_matches and ids_match and next_offset_matches return length_matches and ids_match and next_offset_matches
@ -800,7 +802,7 @@ class TestBot:
def make_assertion(url, data, *args, **kwargs): def make_assertion(url, data, *args, **kwargs):
results = data['results'] results = data['results']
length_matches = len(results) == MAX_INLINE_QUERY_RESULTS length_matches = len(results) == MAX_INLINE_QUERY_RESULTS
ids_match = all([int(res['id']) == 1 + i for i, res in enumerate(results)]) ids_match = all(int(res['id']) == 1 + i for i, res in enumerate(results))
next_offset_matches = data['next_offset'] == '1' next_offset_matches = data['next_offset'] == '1'
return length_matches and ids_match and next_offset_matches return length_matches and ids_match and next_offset_matches
@ -813,7 +815,7 @@ class TestBot:
def make_assertion(url, data, *args, **kwargs): def make_assertion(url, data, *args, **kwargs):
results = data['results'] results = data['results']
length_matches = len(results) == 30 length_matches = len(results) == 30
ids_match = all([int(res['id']) == 1 + i for i, res in enumerate(results)]) ids_match = all(int(res['id']) == 1 + i for i, res in enumerate(results))
next_offset_matches = data['next_offset'] == '' next_offset_matches = data['next_offset'] == ''
return length_matches and ids_match and next_offset_matches return length_matches and ids_match and next_offset_matches
@ -826,7 +828,7 @@ class TestBot:
def make_assertion(url, data, *args, **kwargs): def make_assertion(url, data, *args, **kwargs):
results = data['results'] results = data['results']
length = len(results) == 5 length = len(results) == 5
ids = all([int(res['id']) == 6 + i for i, res in enumerate(results)]) ids = all(int(res['id']) == 6 + i for i, res in enumerate(results))
next_offset = data['next_offset'] == '2' next_offset = data['next_offset'] == '2'
return length and ids and next_offset return length and ids and next_offset
@ -1910,14 +1912,13 @@ class TestBot:
reply_to_message_id=reply_to_message.message_id, reply_to_message_id=reply_to_message.message_id,
) )
return return
else: returned = default_bot.copy_message(
returned = default_bot.copy_message( chat_id,
chat_id, from_chat_id=chat_id,
from_chat_id=chat_id, message_id=media_message.message_id,
message_id=media_message.message_id, caption="<b>Test</b>",
caption="<b>Test</b>", reply_to_message_id=reply_to_message.message_id,
reply_to_message_id=reply_to_message.message_id, )
)
# we send a temp message which replies to the returned message id in order to get a # we send a temp message which replies to the returned message id in order to get a
# message object # message object
temp_message = default_bot.send_message( temp_message = default_bot.send_message(

View file

@ -102,13 +102,13 @@ class BaseTest:
def callback_context_regex1(self, update, context): def callback_context_regex1(self, update, context):
if context.matches: if context.matches:
types = all([type(res) == self.SRE_TYPE for res in context.matches]) types = all(type(res) is self.SRE_TYPE for res in context.matches)
num = len(context.matches) == 1 num = len(context.matches) == 1
self.test_flag = types and num self.test_flag = types and num
def callback_context_regex2(self, update, context): def callback_context_regex2(self, update, context):
if context.matches: if context.matches:
types = all([type(res) == self.SRE_TYPE for res in context.matches]) types = all(type(res) is self.SRE_TYPE for res in context.matches)
num = len(context.matches) == 2 num = len(context.matches) == 2
self.test_flag = types and num self.test_flag = types and num

View file

@ -45,6 +45,5 @@ class TestConstants:
with pytest.raises( with pytest.raises(
BadRequest, BadRequest,
match="Media_caption_too_long", match="Media_caption_too_long",
): ), open('tests/data/telegram.png', 'rb') as f:
with open('tests/data/telegram.png', 'rb') as f: bot.send_photo(photo=f, caption=bad_caption, chat_id=chat_id)
bot.send_photo(photo=f, caption=bad_caption, chat_id=chat_id)

View file

@ -72,8 +72,7 @@ def raise_dphs(func):
result = func(self, *args, **kwargs) result = func(self, *args, **kwargs)
if self.raise_dp_handler_stop: if self.raise_dp_handler_stop:
raise DispatcherHandlerStop(result) raise DispatcherHandlerStop(result)
else: return result
return result
return decorator return decorator
@ -168,8 +167,7 @@ class TestConversationHandler:
def start(self, bot, update): def start(self, bot, update):
if isinstance(update, Update): if isinstance(update, Update):
return self._set_state(update, self.THIRSTY) return self._set_state(update, self.THIRSTY)
else: return self._set_state(bot, self.THIRSTY)
return self._set_state(bot, self.THIRSTY)
@raise_dphs @raise_dphs
def end(self, bot, update): def end(self, bot, update):
@ -187,8 +185,7 @@ class TestConversationHandler:
def brew(self, bot, update): def brew(self, bot, update):
if isinstance(update, Update): if isinstance(update, Update):
return self._set_state(update, self.BREWING) return self._set_state(update, self.BREWING)
else: return self._set_state(bot, self.BREWING)
return self._set_state(bot, self.BREWING)
@raise_dphs @raise_dphs
def drink(self, bot, update): def drink(self, bot, update):

View file

@ -683,7 +683,7 @@ class TestDispatcher:
assert self.received == 'Unauthorized.' assert self.received == 'Unauthorized.'
def test_sensible_worker_thread_names(self, dp2): def test_sensible_worker_thread_names(self, dp2):
thread_names = [thread.name for thread in getattr(dp2, '_Dispatcher__async_threads')] thread_names = [thread.name for thread in dp2._Dispatcher__async_threads]
print(thread_names) print(thread_names)
for thread_name in thread_names: for thread_name in thread_names:
assert thread_name.startswith(f"Bot:{dp2.bot.id}:worker:") assert thread_name.startswith(f"Bot:{dp2.bot.id}:worker:")

View file

@ -273,9 +273,8 @@ class TestDocument:
@flaky(3, 1) @flaky(3, 1)
def test_error_send_empty_file(self, bot, chat_id): def test_error_send_empty_file(self, bot, chat_id):
with open(os.devnull, 'rb') as f: with open(os.devnull, 'rb') as f, pytest.raises(TelegramError):
with pytest.raises(TelegramError): bot.send_document(chat_id=chat_id, document=f)
bot.send_document(chat_id=chat_id, document=f)
@flaky(3, 1) @flaky(3, 1)
def test_error_send_empty_file_id(self, bot, chat_id): def test_error_send_empty_file_id(self, bot, chat_id):

View file

@ -133,19 +133,19 @@ class TestFilters:
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
result = (Filters.regex('deep') | Filters.regex(r'linked param'))(update) result = (Filters.regex('deep') | Filters.regex(r'linked param'))(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
result = (Filters.regex('not int') | Filters.regex(r'linked param'))(update) result = (Filters.regex('not int') | Filters.regex(r'linked param'))(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
result = (Filters.regex('not int') & Filters.regex(r'linked param'))(update) result = (Filters.regex('not int') & Filters.regex(r'linked param'))(update)
assert not result assert not result
@ -158,19 +158,19 @@ class TestFilters:
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
result = (Filters.regex(r'linked param') & Filters.command)(update) result = (Filters.regex(r'linked param') & Filters.command)(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
result = (Filters.regex(r'linked param') | Filters.command)(update) result = (Filters.regex(r'linked param') | Filters.command)(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
# Should not give a match since it's a or filter and it short circuits # Should not give a match since it's a or filter and it short circuits
result = (Filters.command | Filters.regex(r'linked param'))(update) result = (Filters.command | Filters.regex(r'linked param'))(update)
assert result is True assert result is True
@ -178,91 +178,91 @@ class TestFilters:
def test_regex_complex_merges(self, update): def test_regex_complex_merges(self, update):
SRE_TYPE = type(re.match("", "")) SRE_TYPE = type(re.match("", ""))
update.message.text = 'test it out' update.message.text = 'test it out'
filter = Filters.regex('test') & ( test_filter = Filters.regex('test') & (
(Filters.status_update | Filters.forwarded) | Filters.regex('out') (Filters.status_update | Filters.forwarded) | Filters.regex('out')
) )
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert len(matches) == 2 assert len(matches) == 2
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
update.message.forward_date = datetime.datetime.utcnow() update.message.forward_date = datetime.datetime.utcnow()
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
update.message.text = 'test it' update.message.text = 'test it'
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
update.message.forward_date = None update.message.forward_date = None
result = filter(update) result = test_filter(update)
assert not result assert not result
update.message.text = 'test it out' update.message.text = 'test it out'
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
update.message.pinned_message = True update.message.pinned_message = True
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
update.message.text = 'it out' update.message.text = 'it out'
result = filter(update) result = test_filter(update)
assert not result assert not result
update.message.text = 'test it out' update.message.text = 'test it out'
update.message.forward_date = None update.message.forward_date = None
update.message.pinned_message = None update.message.pinned_message = None
filter = (Filters.regex('test') | Filters.command) & ( test_filter = (Filters.regex('test') | Filters.command) & (
Filters.regex('it') | Filters.status_update Filters.regex('it') | Filters.status_update
) )
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert len(matches) == 2 assert len(matches) == 2
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
update.message.text = 'test' update.message.text = 'test'
result = filter(update) result = test_filter(update)
assert not result assert not result
update.message.pinned_message = True update.message.pinned_message = True
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert len(matches) == 1 assert len(matches) == 1
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
update.message.text = 'nothing' update.message.text = 'nothing'
result = filter(update) result = test_filter(update)
assert not result assert not result
update.message.text = '/start' update.message.text = '/start'
update.message.entities = [MessageEntity(MessageEntity.BOT_COMMAND, 0, 6)] update.message.entities = [MessageEntity(MessageEntity.BOT_COMMAND, 0, 6)]
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, bool) assert isinstance(result, bool)
update.message.text = '/start it' update.message.text = '/start it'
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert len(matches) == 1 assert len(matches) == 1
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
def test_regex_inverted(self, update): def test_regex_inverted(self, update):
update.message.text = '/start deep-linked param' update.message.text = '/start deep-linked param'
@ -336,13 +336,13 @@ class TestFilters:
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
result = (Filters.caption_regex('deep') | Filters.caption_regex(r'linked param'))(update) result = (Filters.caption_regex('deep') | Filters.caption_regex(r'linked param'))(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
result = (Filters.caption_regex('not int') | Filters.caption_regex(r'linked param'))( result = (Filters.caption_regex('not int') | Filters.caption_regex(r'linked param'))(
update update
) )
@ -350,7 +350,7 @@ class TestFilters:
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
result = (Filters.caption_regex('not int') & Filters.caption_regex(r'linked param'))( result = (Filters.caption_regex('not int') & Filters.caption_regex(r'linked param'))(
update update
) )
@ -365,19 +365,19 @@ class TestFilters:
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
result = (Filters.caption_regex(r'linked param') & Filters.command)(update) result = (Filters.caption_regex(r'linked param') & Filters.command)(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
result = (Filters.caption_regex(r'linked param') | Filters.command)(update) result = (Filters.caption_regex(r'linked param') | Filters.command)(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
# Should not give a match since it's a or filter and it short circuits # Should not give a match since it's a or filter and it short circuits
result = (Filters.command | Filters.caption_regex(r'linked param'))(update) result = (Filters.command | Filters.caption_regex(r'linked param'))(update)
assert result is True assert result is True
@ -385,130 +385,130 @@ class TestFilters:
def test_caption_regex_complex_merges(self, update): def test_caption_regex_complex_merges(self, update):
SRE_TYPE = type(re.match("", "")) SRE_TYPE = type(re.match("", ""))
update.message.caption = 'test it out' update.message.caption = 'test it out'
filter = Filters.caption_regex('test') & ( test_filter = Filters.caption_regex('test') & (
(Filters.status_update | Filters.forwarded) | Filters.caption_regex('out') (Filters.status_update | Filters.forwarded) | Filters.caption_regex('out')
) )
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert len(matches) == 2 assert len(matches) == 2
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
update.message.forward_date = datetime.datetime.utcnow() update.message.forward_date = datetime.datetime.utcnow()
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
update.message.caption = 'test it' update.message.caption = 'test it'
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
update.message.forward_date = None update.message.forward_date = None
result = filter(update) result = test_filter(update)
assert not result assert not result
update.message.caption = 'test it out' update.message.caption = 'test it out'
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
update.message.pinned_message = True update.message.pinned_message = True
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
update.message.caption = 'it out' update.message.caption = 'it out'
result = filter(update) result = test_filter(update)
assert not result assert not result
update.message.caption = 'test it out' update.message.caption = 'test it out'
update.message.forward_date = None update.message.forward_date = None
update.message.pinned_message = None update.message.pinned_message = None
filter = (Filters.caption_regex('test') | Filters.command) & ( test_filter = (Filters.caption_regex('test') | Filters.command) & (
Filters.caption_regex('it') | Filters.status_update Filters.caption_regex('it') | Filters.status_update
) )
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert len(matches) == 2 assert len(matches) == 2
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
update.message.caption = 'test' update.message.caption = 'test'
result = filter(update) result = test_filter(update)
assert not result assert not result
update.message.pinned_message = True update.message.pinned_message = True
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert len(matches) == 1 assert len(matches) == 1
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
update.message.caption = 'nothing' update.message.caption = 'nothing'
result = filter(update) result = test_filter(update)
assert not result assert not result
update.message.caption = '/start' update.message.caption = '/start'
update.message.entities = [MessageEntity(MessageEntity.BOT_COMMAND, 0, 6)] update.message.entities = [MessageEntity(MessageEntity.BOT_COMMAND, 0, 6)]
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, bool) assert isinstance(result, bool)
update.message.caption = '/start it' update.message.caption = '/start it'
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, dict) assert isinstance(result, dict)
matches = result['matches'] matches = result['matches']
assert isinstance(matches, list) assert isinstance(matches, list)
assert len(matches) == 1 assert len(matches) == 1
assert all([type(res) == SRE_TYPE for res in matches]) assert all(type(res) is SRE_TYPE for res in matches)
def test_caption_regex_inverted(self, update): def test_caption_regex_inverted(self, update):
update.message.caption = '/start deep-linked param' update.message.caption = '/start deep-linked param'
update.message.entities = [MessageEntity(MessageEntity.BOT_COMMAND, 0, 5)] update.message.entities = [MessageEntity(MessageEntity.BOT_COMMAND, 0, 5)]
filter = ~Filters.caption_regex(r'deep-linked param') test_filter = ~Filters.caption_regex(r'deep-linked param')
result = filter(update) result = test_filter(update)
assert not result assert not result
update.message.caption = 'not it' update.message.caption = 'not it'
result = filter(update) result = test_filter(update)
assert result assert result
assert isinstance(result, bool) assert isinstance(result, bool)
filter = ~Filters.caption_regex('linked') & Filters.command test_filter = ~Filters.caption_regex('linked') & Filters.command
update.message.caption = "it's linked" update.message.caption = "it's linked"
result = filter(update) result = test_filter(update)
assert not result assert not result
update.message.caption = '/start' update.message.caption = '/start'
update.message.entities = [MessageEntity(MessageEntity.BOT_COMMAND, 0, 6)] update.message.entities = [MessageEntity(MessageEntity.BOT_COMMAND, 0, 6)]
result = filter(update) result = test_filter(update)
assert result assert result
update.message.caption = '/linked' update.message.caption = '/linked'
result = filter(update) result = test_filter(update)
assert not result assert not result
filter = ~Filters.caption_regex('linked') | Filters.command test_filter = ~Filters.caption_regex('linked') | Filters.command
update.message.caption = "it's linked" update.message.caption = "it's linked"
update.message.entities = [] update.message.entities = []
result = filter(update) result = test_filter(update)
assert not result assert not result
update.message.caption = '/start linked' update.message.caption = '/start linked'
update.message.entities = [MessageEntity(MessageEntity.BOT_COMMAND, 0, 6)] update.message.entities = [MessageEntity(MessageEntity.BOT_COMMAND, 0, 6)]
result = filter(update) result = test_filter(update)
assert result assert result
update.message.caption = '/start' update.message.caption = '/start'
result = filter(update) result = test_filter(update)
assert result assert result
update.message.caption = 'nothig' update.message.caption = 'nothig'
update.message.entities = [] update.message.entities = []
result = filter(update) result = test_filter(update)
assert result assert result
def test_filters_reply(self, update): def test_filters_reply(self, update):

View file

@ -406,8 +406,8 @@ class TestSendMediaGroup:
messages = bot.send_media_group(chat_id, media_group) messages = bot.send_media_group(chat_id, media_group)
assert isinstance(messages, list) assert isinstance(messages, list)
assert len(messages) == 3 assert len(messages) == 3
assert all([isinstance(mes, Message) for mes in messages]) assert all(isinstance(mes, Message) for mes in messages)
assert all([mes.media_group_id == messages[0].media_group_id for mes in messages]) assert all(mes.media_group_id == messages[0].media_group_id for mes in messages)
assert all(mes.caption == f'photo {idx+1}' for idx, mes in enumerate(messages)) assert all(mes.caption == f'photo {idx+1}' for idx, mes in enumerate(messages))
assert all( assert all(
mes.caption_entities == [MessageEntity(MessageEntity.BOLD, 0, 5)] for mes in messages mes.caption_entities == [MessageEntity(MessageEntity.BOLD, 0, 5)] for mes in messages
@ -421,8 +421,8 @@ class TestSendMediaGroup:
) )
assert isinstance(messages, list) assert isinstance(messages, list)
assert len(messages) == 3 assert len(messages) == 3
assert all([isinstance(mes, Message) for mes in messages]) assert all(isinstance(mes, Message) for mes in messages)
assert all([mes.media_group_id == messages[0].media_group_id for mes in messages]) assert all(mes.media_group_id == messages[0].media_group_id for mes in messages)
assert all(mes.caption == f'photo {idx+1}' for idx, mes in enumerate(messages)) assert all(mes.caption == f'photo {idx+1}' for idx, mes in enumerate(messages))
assert all( assert all(
mes.caption_entities == [MessageEntity(MessageEntity.BOLD, 0, 5)] for mes in messages mes.caption_entities == [MessageEntity(MessageEntity.BOLD, 0, 5)] for mes in messages
@ -491,8 +491,8 @@ class TestSendMediaGroup:
assert isinstance(messages, list) assert isinstance(messages, list)
assert len(messages) == 3 assert len(messages) == 3
assert all([isinstance(mes, Message) for mes in messages]) assert all(isinstance(mes, Message) for mes in messages)
assert all([mes.media_group_id == messages[0].media_group_id for mes in messages]) assert all(mes.media_group_id == messages[0].media_group_id for mes in messages)
@flaky(3, 1) @flaky(3, 1)
@pytest.mark.parametrize( @pytest.mark.parametrize(

View file

@ -185,10 +185,8 @@ class TestInvoice:
def test_send_object_as_provider_data(self, monkeypatch, bot, chat_id, provider_token): def test_send_object_as_provider_data(self, monkeypatch, bot, chat_id, provider_token):
def test(url, data, **kwargs): def test(url, data, **kwargs):
return ( # depends on whether we're using ujson
data['provider_data'] == '{"test_data": 123456789}' # Depends if using return data['provider_data'] in ['{"test_data": 123456789}', '{"test_data":123456789}']
or data['provider_data'] == '{"test_data":123456789}'
) # ujson or not
monkeypatch.setattr(bot.request, 'post', test) monkeypatch.setattr(bot.request, 'post', test)

View file

@ -606,26 +606,26 @@ class TestMessage:
def test_chat_id(self, message): def test_chat_id(self, message):
assert message.chat_id == message.chat.id assert message.chat_id == message.chat.id
@pytest.mark.parametrize('type', argvalues=[Chat.SUPERGROUP, Chat.CHANNEL]) @pytest.mark.parametrize('_type', argvalues=[Chat.SUPERGROUP, Chat.CHANNEL])
def test_link_with_username(self, message, type): def test_link_with_username(self, message, _type):
message.chat.username = 'username' message.chat.username = 'username'
message.chat.type = type message.chat.type = _type
assert message.link == f'https://t.me/{message.chat.username}/{message.message_id}' assert message.link == f'https://t.me/{message.chat.username}/{message.message_id}'
@pytest.mark.parametrize( @pytest.mark.parametrize(
'type, id', argvalues=[(Chat.CHANNEL, -1003), (Chat.SUPERGROUP, -1003)] '_type, _id', argvalues=[(Chat.CHANNEL, -1003), (Chat.SUPERGROUP, -1003)]
) )
def test_link_with_id(self, message, type, id): def test_link_with_id(self, message, _type, _id):
message.chat.username = None message.chat.username = None
message.chat.id = id message.chat.id = _id
message.chat.type = type message.chat.type = _type
# The leading - for group ids/ -100 for supergroup ids isn't supposed to be in the link # The leading - for group ids/ -100 for supergroup ids isn't supposed to be in the link
assert message.link == f'https://t.me/c/{3}/{message.message_id}' assert message.link == f'https://t.me/c/{3}/{message.message_id}'
@pytest.mark.parametrize('id, username', argvalues=[(None, 'username'), (-3, None)]) @pytest.mark.parametrize('_id, username', argvalues=[(None, 'username'), (-3, None)])
def test_link_private_chats(self, message, id, username): def test_link_private_chats(self, message, _id, username):
message.chat.type = Chat.PRIVATE message.chat.type = Chat.PRIVATE
message.chat.id = id message.chat.id = _id
message.chat.username = username message.chat.username = username
assert message.link is None assert message.link is None
message.chat.type = Chat.GROUP message.chat.type = Chat.GROUP

View file

@ -121,13 +121,13 @@ class TestMessageHandler:
def callback_context_regex1(self, update, context): def callback_context_regex1(self, update, context):
if context.matches: if context.matches:
types = all([type(res) == self.SRE_TYPE for res in context.matches]) types = all(type(res) is self.SRE_TYPE for res in context.matches)
num = len(context.matches) == 1 num = len(context.matches) == 1
self.test_flag = types and num self.test_flag = types and num
def callback_context_regex2(self, update, context): def callback_context_regex2(self, update, context):
if context.matches: if context.matches:
types = all([type(res) == self.SRE_TYPE for res in context.matches]) types = all(type(res) is self.SRE_TYPE for res in context.matches)
num = len(context.matches) == 2 num = len(context.matches) == 2
self.test_flag = types and num self.test_flag = types and num

View file

@ -121,9 +121,9 @@ def check_object(h4):
name.startswith('InlineQueryResult') or name.startswith('InputMedia') name.startswith('InlineQueryResult') or name.startswith('InputMedia')
) and field == 'type': ) and field == 'type':
continue continue
elif name.startswith('PassportElementError') and field == 'source': elif (
continue name.startswith('PassportElementError') and field == 'source'
elif field == 'remove_keyboard': ) or field == 'remove_keyboard':
continue continue
param = sig.parameters.get(field) param = sig.parameters.get(field)
@ -135,7 +135,7 @@ def check_object(h4):
ignored = IGNORED_PARAMETERS.copy() ignored = IGNORED_PARAMETERS.copy()
if name == 'InputFile': if name == 'InputFile':
return return
elif name == 'InlineQueryResult': if name == 'InlineQueryResult':
ignored |= {'id', 'type'} ignored |= {'id', 'type'}
elif name == 'User': elif name == 'User':
ignored |= {'type'} # TODO: Deprecation ignored |= {'type'} # TODO: Deprecation

View file

@ -1195,9 +1195,6 @@ class TestPickelPersistence:
h2 = MessageHandler(None, second, pass_user_data=True, pass_chat_data=True) h2 = MessageHandler(None, second, pass_user_data=True, pass_chat_data=True)
dp.add_handler(h1) dp.add_handler(h1)
dp.process_update(update) dp.process_update(update)
del dp
del u
del pickle_persistence
pickle_persistence_2 = PicklePersistence( pickle_persistence_2 = PicklePersistence(
filename='pickletest', filename='pickletest',
store_user_data=True, store_user_data=True,
@ -1218,10 +1215,7 @@ class TestPickelPersistence:
dp.user_data[4242424242]['my_test'] = 'Working!' dp.user_data[4242424242]['my_test'] = 'Working!'
dp.chat_data[-4242424242]['my_test2'] = 'Working2!' dp.chat_data[-4242424242]['my_test2'] = 'Working2!'
dp.bot_data['test'] = 'Working3!' dp.bot_data['test'] = 'Working3!'
u.signal_handler(signal.SIGINT, None) u._signal_handler(signal.SIGINT, None)
del dp
del u
del pickle_persistence
pickle_persistence_2 = PicklePersistence( pickle_persistence_2 = PicklePersistence(
filename='pickletest', filename='pickletest',
store_user_data=True, store_user_data=True,
@ -1240,10 +1234,7 @@ class TestPickelPersistence:
dp.user_data[4242424242]['my_test'] = 'Working!' dp.user_data[4242424242]['my_test'] = 'Working!'
dp.chat_data[-4242424242]['my_test2'] = 'Working2!' dp.chat_data[-4242424242]['my_test2'] = 'Working2!'
dp.bot_data['my_test3'] = 'Working3!' dp.bot_data['my_test3'] = 'Working3!'
u.signal_handler(signal.SIGINT, None) u._signal_handler(signal.SIGINT, None)
del dp
del u
del pickle_persistence_only_bot
pickle_persistence_2 = PicklePersistence( pickle_persistence_2 = PicklePersistence(
filename='pickletest', filename='pickletest',
store_user_data=False, store_user_data=False,
@ -1262,10 +1253,7 @@ class TestPickelPersistence:
u.running = True u.running = True
dp.user_data[4242424242]['my_test'] = 'Working!' dp.user_data[4242424242]['my_test'] = 'Working!'
dp.chat_data[-4242424242]['my_test2'] = 'Working2!' dp.chat_data[-4242424242]['my_test2'] = 'Working2!'
u.signal_handler(signal.SIGINT, None) u._signal_handler(signal.SIGINT, None)
del dp
del u
del pickle_persistence_only_chat
pickle_persistence_2 = PicklePersistence( pickle_persistence_2 = PicklePersistence(
filename='pickletest', filename='pickletest',
store_user_data=False, store_user_data=False,
@ -1284,10 +1272,7 @@ class TestPickelPersistence:
u.running = True u.running = True
dp.user_data[4242424242]['my_test'] = 'Working!' dp.user_data[4242424242]['my_test'] = 'Working!'
dp.chat_data[-4242424242]['my_test2'] = 'Working2!' dp.chat_data[-4242424242]['my_test2'] = 'Working2!'
u.signal_handler(signal.SIGINT, None) u._signal_handler(signal.SIGINT, None)
del dp
del u
del pickle_persistence_only_user
pickle_persistence_2 = PicklePersistence( pickle_persistence_2 = PicklePersistence(
filename='pickletest', filename='pickletest',
store_user_data=True, store_user_data=True,
@ -1310,10 +1295,10 @@ class TestPickelPersistence:
start = CommandHandler('start', start) start = CommandHandler('start', start)
def next(update, context): def next_callback(update, context):
return NEXT2 return NEXT2
next = MessageHandler(None, next) next_handler = MessageHandler(None, next_callback)
def next2(update, context): def next2(update, context):
return ConversationHandler.END return ConversationHandler.END
@ -1321,7 +1306,7 @@ class TestPickelPersistence:
next2 = MessageHandler(None, next2) next2 = MessageHandler(None, next2)
ch = ConversationHandler( ch = ConversationHandler(
[start], {NEXT: [next], NEXT2: [next2]}, [], name='name2', persistent=True [start], {NEXT: [next_handler], NEXT2: [next2]}, [], name='name2', persistent=True
) )
dp.add_handler(ch) dp.add_handler(ch)
assert ch.conversations[ch._get_key(update)] == 1 assert ch.conversations[ch._get_key(update)] == 1
@ -1345,10 +1330,10 @@ class TestPickelPersistence:
start = CommandHandler('start', start) start = CommandHandler('start', start)
def next(update, context): def next_callback(update, context):
return NEXT2 return NEXT2
next = MessageHandler(None, next) next_handler = MessageHandler(None, next_callback)
def next2(update, context): def next2(update, context):
return ConversationHandler.END return ConversationHandler.END
@ -1356,7 +1341,7 @@ class TestPickelPersistence:
next2 = MessageHandler(None, next2) next2 = MessageHandler(None, next2)
nested_ch = ConversationHandler( nested_ch = ConversationHandler(
[next], [next_handler],
{NEXT2: [next2]}, {NEXT2: [next2]},
[], [],
name='name3', name='name3',
@ -1612,12 +1597,9 @@ class TestDictPersistence:
h2 = MessageHandler(None, second, pass_user_data=True, pass_chat_data=True) h2 = MessageHandler(None, second, pass_user_data=True, pass_chat_data=True)
dp.add_handler(h1) dp.add_handler(h1)
dp.process_update(update) dp.process_update(update)
del dp
del u
user_data = dict_persistence.user_data_json user_data = dict_persistence.user_data_json
chat_data = dict_persistence.chat_data_json chat_data = dict_persistence.chat_data_json
bot_data = dict_persistence.bot_data_json bot_data = dict_persistence.bot_data_json
del dict_persistence
dict_persistence_2 = DictPersistence( dict_persistence_2 = DictPersistence(
user_data_json=user_data, chat_data_json=chat_data, bot_data_json=bot_data user_data_json=user_data, chat_data_json=chat_data, bot_data_json=bot_data
) )
@ -1638,10 +1620,10 @@ class TestDictPersistence:
start = CommandHandler('start', start) start = CommandHandler('start', start)
def next(update, context): def next_callback(update, context):
return NEXT2 return NEXT2
next = MessageHandler(None, next) next_handler = MessageHandler(None, next_callback)
def next2(update, context): def next2(update, context):
return ConversationHandler.END return ConversationHandler.END
@ -1649,7 +1631,7 @@ class TestDictPersistence:
next2 = MessageHandler(None, next2) next2 = MessageHandler(None, next2)
ch = ConversationHandler( ch = ConversationHandler(
[start], {NEXT: [next], NEXT2: [next2]}, [], name='name2', persistent=True [start], {NEXT: [next_handler], NEXT2: [next2]}, [], name='name2', persistent=True
) )
dp.add_handler(ch) dp.add_handler(ch)
assert ch.conversations[ch._get_key(update)] == 1 assert ch.conversations[ch._get_key(update)] == 1
@ -1672,10 +1654,10 @@ class TestDictPersistence:
start = CommandHandler('start', start) start = CommandHandler('start', start)
def next(update, context): def next_callback(update, context):
return NEXT2 return NEXT2
next = MessageHandler(None, next) next_handler = MessageHandler(None, next_callback)
def next2(update, context): def next2(update, context):
return ConversationHandler.END return ConversationHandler.END
@ -1683,7 +1665,7 @@ class TestDictPersistence:
next2 = MessageHandler(None, next2) next2 = MessageHandler(None, next2)
nested_ch = ConversationHandler( nested_ch = ConversationHandler(
[next], [next_handler],
{NEXT2: [next2]}, {NEXT2: [next2]},
[], [],
name='name3', name='name3',

View file

@ -102,10 +102,10 @@ class TestUpdate:
# Make sure only one thing in the update (other than update_id) is not None # Make sure only one thing in the update (other than update_id) is not None
i = 0 i = 0
for type in all_types: for _type in all_types:
if getattr(update, type) is not None: if getattr(update, _type) is not None:
i += 1 i += 1
assert getattr(update, type) == paramdict[type] assert getattr(update, _type) == paramdict[_type]
assert i == 1 assert i == 1
def test_update_de_json_empty(self, bot): def test_update_de_json_empty(self, bot):
@ -118,9 +118,9 @@ class TestUpdate:
assert isinstance(update_dict, dict) assert isinstance(update_dict, dict)
assert update_dict['update_id'] == update.update_id assert update_dict['update_id'] == update.update_id
for type in all_types: for _type in all_types:
if getattr(update, type) is not None: if getattr(update, _type) is not None:
assert update_dict[type] == getattr(update, type).to_dict() assert update_dict[_type] == getattr(update, _type).to_dict()
def test_effective_chat(self, update): def test_effective_chat(self, update):
# Test that it's sometimes None per docstring # Test that it's sometimes None per docstring