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
: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
:target: https://github.com/psf/black
:target: https://github.com/psf/black
.. image:: https://img.shields.io/badge/Telegram-Group-blue.svg?logo=telegram
: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
: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
:target: https://github.com/psf/black
:target: https://github.com/psf/black
.. image:: https://img.shields.io/badge/Telegram-Group-blue.svg?logo=telegram
:target: https://telegram.me/pythontelegrambotgroup

View file

@ -24,6 +24,7 @@ logger = logging.getLogger(__name__)
def msg(update: Update, _: CallbackContext) -> None:
"""Downloads and prints the received passport data."""
# Retrieve passport data
passport_data = update.message.passport_data
# 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()
print(actual_file)
actual_file.download()
if data.type in ('passport', 'driver_license', 'identity_card', 'internal_passport'):
if data.front_side:
front_file = data.front_side.get_file()
print(data.type, front_file)
front_file.download()
if data.type in ('driver_license' and 'identity_card'):
if data.reverse_side:
reverse_file = data.reverse_side.get_file()
print(data.type, reverse_file)
reverse_file.download()
if data.type in ('passport', 'driver_license', 'identity_card', 'internal_passport'):
if data.selfie:
selfie_file = data.selfie.get_file()
print(data.type, selfie_file)
selfie_file.download()
if (
data.type in ('passport', 'driver_license', 'identity_card', 'internal_passport')
and data.front_side
):
front_file = data.front_side.get_file()
print(data.type, front_file)
front_file.download()
if data.type in ('driver_license' and 'identity_card') and data.reverse_side:
reverse_file = data.reverse_side.get_file()
print(data.type, reverse_file)
reverse_file.download()
if (
data.type in ('passport', 'driver_license', 'identity_card', 'internal_passport')
and data.selfie
):
selfie_file = data.selfie.get_file()
print(data.type, selfie_file)
selfie_file.download()
if data.type in (
'passport',
'driver_license',
@ -97,7 +101,8 @@ def msg(update: Update, _: CallbackContext) -> None:
def main() -> None:
"""Start the bot."""
# 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
dispatcher = updater.dispatcher

View file

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

View file

@ -34,7 +34,7 @@ TO = TypeVar('TO', bound='TelegramObject', covariant=True)
class TelegramObject:
"""Base class for most telegram objects."""
"""Base class for most Telegram objects."""
_id_attrs: Tuple[object, ...] = ()
@ -45,12 +45,22 @@ class TelegramObject:
return self.__dict__[item]
@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()
@classmethod
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:
return None
@ -61,21 +71,35 @@ class TelegramObject:
@classmethod
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:
return []
return [cls.de_json(d, bot) for d in data]
def to_json(self) -> str:
"""
"""Gives a JSON representation of object.
Returns:
:obj:`str`
"""
return json.dumps(self.to_dict())
def to_dict(self) -> JSONDict:
"""Gives representation of object as :obj:`dict`.
Returns:
:obj:`dict`
"""
data = {}
for key in iter(self.__dict__):

View file

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

View file

@ -113,7 +113,8 @@ class CallbackQuery(TelegramObject):
@classmethod
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:
return None

View file

@ -229,14 +229,16 @@ class Chat(TelegramObject):
@property
def link(self) -> Optional[str]:
""":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:
return f"https://t.me/{self.username}"
return None
@classmethod
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:
return None

View file

@ -84,7 +84,8 @@ class ChatInviteLink(TelegramObject):
@classmethod
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:
return None
@ -95,6 +96,7 @@ class ChatInviteLink(TelegramObject):
return cls(**data)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict()
data['expire_date'] = to_timestamp(self.expire_date)

View file

@ -60,7 +60,8 @@ class ChatLocation(TelegramObject):
@classmethod
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:
return None

View file

@ -207,7 +207,8 @@ class ChatMember(TelegramObject):
@classmethod
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:
return None
@ -218,6 +219,7 @@ class ChatMember(TelegramObject):
return cls(**data)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict()
data['until_date'] = to_timestamp(self.until_date)

View file

@ -92,7 +92,8 @@ class ChatMemberUpdated(TelegramObject):
@classmethod
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:
return None
@ -107,6 +108,7 @@ class ChatMemberUpdated(TelegramObject):
return cls(**data)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict()
# Required

View file

@ -82,7 +82,8 @@ class ChosenInlineResult(TelegramObject):
@classmethod
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:
return None

View file

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

View file

@ -51,19 +51,6 @@ class CallbackContext:
that you think you added will not be present.
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
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.
@ -113,6 +100,9 @@ class CallbackContext:
@property
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
@bot_data.setter
@ -123,6 +113,15 @@ class CallbackContext:
@property
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
@chat_data.setter
@ -133,6 +132,9 @@ class CallbackContext:
@property
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
@user_data.setter
@ -150,6 +152,28 @@ class CallbackContext:
async_args: Union[List, Tuple] = None,
async_kwargs: Dict[str, object] = None,
) -> '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.error = error
self.async_args = async_args
@ -158,6 +182,20 @@ class CallbackContext:
@classmethod
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)
if update is not None and isinstance(update, Update):
@ -172,11 +210,30 @@ class CallbackContext:
@classmethod
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.job = job
return self
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)
@property

View file

@ -174,6 +174,10 @@ class CallbackQueryHandler(Handler[Update]):
update: Update = None,
check_result: Union[bool, Match] = None,
) -> 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)
if self.pattern:
check_result = cast(Match, check_result)
@ -190,6 +194,9 @@ class CallbackQueryHandler(Handler[Update]):
dispatcher: 'Dispatcher',
check_result: Union[bool, Match],
) -> None:
"""Add the result of ``re.match(pattern, update.callback_query.data)`` to
:attr:`CallbackContext.matches` as list with one element.
"""
if self.pattern:
check_result = cast(Match, check_result)
context.matches = [check_result]

View file

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

View file

@ -219,6 +219,9 @@ class CommandHandler(Handler[Update]):
update: Update = None,
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]] = None,
) -> 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)
if self.pass_args and isinstance(check_result, tuple):
optional_args['args'] = check_result[0]
@ -231,6 +234,9 @@ class CommandHandler(Handler[Update]):
dispatcher: 'Dispatcher',
check_result: Optional[Union[bool, Tuple[List[str], Optional[bool]]]],
) -> None:
"""Add text after the command to :attr:`CallbackContext.args` as list, split on single
whitespaces and add output of data filters to :attr:`CallbackContext` as well.
"""
if isinstance(check_result, tuple):
context.args = check_result[0]
if isinstance(check_result[1], dict):
@ -238,7 +244,7 @@ class CommandHandler(Handler[Update]):
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`.
It supports configurable commands with the same options as CommandHandler. It will respond to
@ -265,7 +271,7 @@ class PrefixHandler(CommandHandler):
.. code:: python
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
@ -442,15 +448,3 @@ class PrefixHandler(CommandHandler):
return text_list[1:], filter_result
return False
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
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
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
:attr:`Handler.run_async` setting of all internal handlers on initialization.
@ -316,6 +291,9 @@ class ConversationHandler(Handler[Update]):
@property
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
@entry_points.setter
@ -324,6 +302,10 @@ class ConversationHandler(Handler[Update]):
@property
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
@states.setter
@ -332,6 +314,10 @@ class ConversationHandler(Handler[Update]):
@property
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
@fallbacks.setter
@ -344,10 +330,12 @@ class ConversationHandler(Handler[Update]):
@allow_reentry.setter
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.')
@property
def per_user(self) -> bool:
""":obj:`bool`: If the conversation key should contain the User's ID."""
return self._per_user
@per_user.setter
@ -356,6 +344,7 @@ class ConversationHandler(Handler[Update]):
@property
def per_chat(self) -> bool:
""":obj:`bool`: If the conversation key should contain the Chat's ID."""
return self._per_chat
@per_chat.setter
@ -364,6 +353,7 @@ class ConversationHandler(Handler[Update]):
@property
def per_message(self) -> bool:
""":obj:`bool`: If the conversation key should contain the message's ID."""
return self._per_message
@per_message.setter
@ -374,16 +364,21 @@ class ConversationHandler(Handler[Update]):
def conversation_timeout(
self,
) -> 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
@conversation_timeout.setter
def conversation_timeout(self, value: object) -> NoReturn:
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
def name(self) -> Optional[str]:
""":obj:`str`: Optional. The name for this :class:`ConversationHandler`."""
return self._name
@name.setter
@ -392,6 +387,10 @@ class ConversationHandler(Handler[Update]):
@property
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
@map_to_parent.setter
@ -400,6 +399,7 @@ class ConversationHandler(Handler[Update]):
@property
def persistence(self) -> Optional[BasePersistence]:
"""The persistence class as provided by the :class:`Dispatcher`."""
return self._persistence
@persistence.setter
@ -412,7 +412,7 @@ class ConversationHandler(Handler[Update]):
handler.persistence = self.persistence
@property
def conversations(self) -> ConversationDict:
def conversations(self) -> ConversationDict: # skipcq: PY-D0003
return self._conversations
@conversations.setter
@ -518,7 +518,7 @@ class ConversationHandler(Handler[Update]):
# check if promise is finished or not
if state[1].done.wait(0):
res = self._resolve_promise(state)
self.update_state(res, key)
self._update_state(res, key)
with self._conversations_lock:
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:
self.update_state(self.END, conversation_key)
self._update_state(self.END, conversation_key)
if raise_dp_handler_stop:
raise DispatcherHandlerStop(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:
# Don't pass the new state here. If we're in a nested conversation, the parent is
# expecting None as return value.
raise DispatcherHandlerStop()
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:
with self._conversations_lock:
if key in self.conversations:
@ -698,4 +698,4 @@ class ConversationHandler(Handler[Update]):
'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
handlers and error handlers registered through :meth:`Dispatcher.add_handler` and
: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__(
@ -118,11 +95,14 @@ class Defaults:
self._api_defaults['timeout'] = self.timeout
@property
def api_defaults(self) -> Dict[str, Any]:
def api_defaults(self) -> Dict[str, Any]: # skip-cq: PY-D0003
return self._api_defaults
@property
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
@parse_mode.setter
@ -134,6 +114,9 @@ class Defaults:
@property
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
@explanation_parse_mode.setter
@ -145,6 +128,9 @@ class Defaults:
@property
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
@disable_notification.setter
@ -156,6 +142,9 @@ class Defaults:
@property
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
@disable_web_page_preview.setter
@ -167,6 +156,9 @@ class Defaults:
@property
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
@allow_sending_without_reply.setter
@ -178,6 +170,10 @@ class Defaults:
@property
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
@timeout.setter
@ -189,6 +185,10 @@ class Defaults:
@property
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
@quote.setter
@ -200,6 +200,9 @@ class Defaults:
@property
def tzinfo(self) -> pytz.BaseTzInfo:
""":obj:`tzinfo`: A timezone to be used for all date(time) objects appearing
throughout PTB.
"""
return self._tzinfo
@tzinfo.setter
@ -211,6 +214,10 @@ class Defaults:
@property
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
@run_async.setter

View file

@ -216,7 +216,7 @@ class Dispatcher:
self._set_singleton(None)
@property
def exception_event(self) -> Event:
def exception_event(self) -> Event: # skipcq: PY-D0003
return self.__exception_event
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)
@property
def has_running_threads(self) -> bool:
def has_running_threads(self) -> bool: # skipcq: PY-D0003
return self.running or bool(self.__async_threads)
def process_update(self, update: object) -> None:
@ -422,7 +422,6 @@ class Dispatcher:
The update to process.
"""
# An error happened while polling
if isinstance(update, TelegramError):
try:
@ -637,9 +636,8 @@ class Dispatcher:
self.logger.debug('The callback is already registered as an error handler. Ignoring.')
return
if run_async is DEFAULT_FALSE and self.bot.defaults:
if self.bot.defaults.run_async:
run_async = True
if run_async is DEFAULT_FALSE and self.bot.defaults and self.bot.defaults.run_async:
run_async = True
self.error_handlers[callback] = run_async

View file

@ -117,7 +117,7 @@ class BaseFilter(ABC):
@abstractmethod
def __call__(self, update: Update) -> Optional[Union[bool, DataDict]]:
pass
...
def __and__(self, other: 'BaseFilter') -> 'BaseFilter':
return MergedFilter(self, and_filter=other)
@ -652,17 +652,13 @@ class Filters:
"""
def __init__(self, category: Optional[str]):
"""Initialize the category you want to filter
Args:
category (str, optional): category of the media you want to filter"""
self.category = category
self.name = f"Filters.document.category('{self.category}')"
self._category = category
self.name = f"Filters.document.category('{self._category}')"
def filter(self, message: Message) -> bool:
"""""" # remove method from docs
if message.document:
return message.document.mime_type.startswith(self.category)
return message.document.mime_type.startswith(self._category)
return False
application = category('application/')
@ -685,10 +681,6 @@ class Filters:
"""
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.name = f"Filters.document.mime_type('{self.mimetype}')"
@ -752,29 +744,29 @@ class Filters:
"""
self.is_case_sensitive = case_sensitive
if file_extension is None:
self.file_extension = None
self._file_extension = None
self.name = "Filters.document.file_extension(None)"
elif case_sensitive:
self.file_extension = f".{file_extension}"
elif self.is_case_sensitive:
self._file_extension = f".{file_extension}"
self.name = (
f"Filters.document.file_extension({file_extension!r},"
" case_sensitive=True)"
)
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})"
def filter(self, message: Message) -> bool:
"""""" # remove method from docs
if message.document is None:
return False
if self.file_extension is None:
if self._file_extension is None:
return "." not in message.document.file_name
if self.is_case_sensitive:
filename = message.document.file_name
else:
filename = message.document.file_name.lower()
return filename.endswith(self.file_extension)
return filename.endswith(self._file_extension)
def filter(self, message: Message) -> bool:
return bool(message.document)
@ -1328,7 +1320,7 @@ officedocument.wordprocessingml.document")``.
private: Updates sent in private chat
"""
class _ChatUserBaseFilter(MessageFilter):
class _ChatUserBaseFilter(MessageFilter, ABC):
def __init__(
self,
chat_id: SLT[int] = None,
@ -1348,7 +1340,7 @@ officedocument.wordprocessingml.document")``.
@abstractmethod
def get_chat_or_user(self, message: Message) -> Union[Chat, User, None]:
pass
...
@staticmethod
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
if self.run_async is DEFAULT_FALSE and dispatcher.bot.defaults:
if dispatcher.bot.defaults.run_async:
run_async = True
if (
self.run_async is DEFAULT_FALSE
and dispatcher.bot.defaults
and dispatcher.bot.defaults.run_async
):
run_async = True
if context:
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
# 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
from typing import (
TYPE_CHECKING,
@ -170,7 +170,6 @@ class InlineQueryHandler(Handler[Update]):
:obj:`bool`
"""
if isinstance(update, Update) and update.inline_query:
if (self.chat_types is not None) and (
update.inline_query.chat_type not in self.chat_types
@ -191,6 +190,10 @@ class InlineQueryHandler(Handler[Update]):
update: Update = None,
check_result: Optional[Union[bool, Match]] = None,
) -> 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)
if self.pattern:
check_result = cast(Match, check_result)
@ -207,6 +210,9 @@ class InlineQueryHandler(Handler[Update]):
dispatcher: 'Dispatcher',
check_result: Optional[Union[bool, Match]],
) -> None:
"""Add the result of ``re.match(pattern, update.inline_query.query)`` to
:attr:`CallbackContext.matches` as list with one element.
"""
if self.pattern:
check_result = cast(Match, check_result)
context.matches = [check_result]

View file

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

View file

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

View file

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

View file

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

View file

@ -32,6 +32,9 @@ RT = TypeVar('RT')
class StringCommandHandler(Handler[str]):
"""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:
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,
check_result: Optional[List[str]] = None,
) -> 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)
if self.pass_args:
optional_args['args'] = check_result
@ -134,4 +140,7 @@ class StringCommandHandler(Handler[str]):
dispatcher: 'Dispatcher',
check_result: Optional[List[str]],
) -> None:
"""Add text after the command to :attr:`CallbackContext.args` as list, split on single
whitespaces.
"""
context.args = check_result

View file

@ -137,6 +137,10 @@ class StringRegexHandler(Handler[str]):
update: str = None,
check_result: Optional[Match] = None,
) -> 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)
if self.pattern:
if self.pass_groups and check_result:
@ -152,5 +156,8 @@ class StringRegexHandler(Handler[str]):
dispatcher: 'Dispatcher',
check_result: Optional[Match],
) -> None:
"""Add the result of ``re.match(pattern, update)`` to :attr:`CallbackContext.matches` as
list with one element.
"""
if self.pattern and check_result:
context.matches = [check_result]

View file

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

View file

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

View file

@ -91,7 +91,7 @@ class WebhookAppClass(tornado.web.Application):
handlers = [(rf"{webhook_path}/?", WebhookHandler, self.shared_objects)] # noqa
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

View file

@ -96,7 +96,8 @@ class Animation(TelegramObject):
@classmethod
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:
return None

View file

@ -100,7 +100,8 @@ class Audio(TelegramObject):
@classmethod
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:
return None

View file

@ -87,7 +87,8 @@ class Document(TelegramObject):
@classmethod
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:
return None

View file

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

View file

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

View file

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

View file

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

View file

@ -85,7 +85,8 @@ class Venue(TelegramObject):
@classmethod
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:
return None

View file

@ -97,7 +97,8 @@ class Video(TelegramObject):
@classmethod
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:
return None

View file

@ -86,7 +86,8 @@ class VideoNote(TelegramObject):
@classmethod
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:
return None

View file

@ -90,7 +90,8 @@ class Game(TelegramObject):
@classmethod
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:
return None
@ -102,6 +103,7 @@ class Game(TelegramObject):
return cls(**data)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict()
data['photo'] = [p.to_dict() for p in self.photo]

View file

@ -54,7 +54,8 @@ class GameHighScore(TelegramObject):
@classmethod
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:
return None

View file

@ -52,6 +52,7 @@ class InlineKeyboardMarkup(ReplyMarkup):
self._id_attrs = (self.inline_keyboard,)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict()
data['inline_keyboard'] = []
@ -62,7 +63,8 @@ class InlineKeyboardMarkup(ReplyMarkup):
@classmethod
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:
return None

View file

@ -97,7 +97,8 @@ class InlineQuery(TelegramObject):
@classmethod
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:
return None

View file

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

View file

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

View file

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

View file

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

View file

@ -331,6 +331,7 @@ class Message(TelegramObject):
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
"""
# fmt: on
_effective_attachment = _UNDEFINED
@ -506,7 +507,8 @@ class Message(TelegramObject):
@property
def link(self) -> Optional[str]:
""":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.username:
to_link = self.chat.username
@ -518,7 +520,8 @@ class Message(TelegramObject):
@classmethod
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:
return None
@ -629,6 +632,7 @@ class Message(TelegramObject):
return self.chat.id
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict()
# Required

View file

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

View file

@ -82,7 +82,8 @@ class MessageEntity(TelegramObject):
@classmethod
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:
return None

View file

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

View file

@ -162,7 +162,8 @@ class EncryptedPassportElement(TelegramObject):
@classmethod
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:
return None
@ -179,6 +180,18 @@ class EncryptedPassportElement(TelegramObject):
def de_json_decrypted(
cls, data: Optional[JSONDict], bot: 'Bot', credentials: 'Credentials'
) -> 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:
return None
@ -227,6 +240,7 @@ class EncryptedPassportElement(TelegramObject):
return cls(bot=bot, **data)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict()
if self.files:

View file

@ -67,7 +67,8 @@ class PassportData(TelegramObject):
@classmethod
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:
return None
@ -78,6 +79,7 @@ class PassportData(TelegramObject):
return cls(bot=bot, **data)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict()
data['data'] = [e.to_dict() for e in self.data]

View file

@ -83,7 +83,19 @@ class PassportFile(TelegramObject):
def de_json_decrypted(
cls, data: Optional[JSONDict], bot: 'Bot', credentials: 'FileCredentials'
) -> 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:
return None
@ -96,6 +108,18 @@ class PassportFile(TelegramObject):
def de_list_decrypted(
cls, data: Optional[List[JSONDict]], bot: 'Bot', credentials: List['FileCredentials']
) -> 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:
return []

View file

@ -66,7 +66,8 @@ class OrderInfo(TelegramObject):
@classmethod
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:
return cls()

View file

@ -93,7 +93,8 @@ class PreCheckoutQuery(TelegramObject):
@classmethod
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:
return None

View file

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

View file

@ -74,7 +74,8 @@ class ShippingQuery(TelegramObject):
@classmethod
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:
return None

View file

@ -85,7 +85,8 @@ class SuccessfulPayment(TelegramObject):
@classmethod
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:
return None

View file

@ -87,7 +87,8 @@ class PollAnswer(TelegramObject):
@classmethod
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:
return None
@ -180,7 +181,8 @@ class Poll(TelegramObject):
@classmethod
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:
return None
@ -192,6 +194,7 @@ class Poll(TelegramObject):
return cls(**data)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict()
data['options'] = [x.to_dict() for x in self.options]

View file

@ -55,7 +55,8 @@ class ProximityAlertTriggered(TelegramObject):
@classmethod
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:
return None

View file

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

View file

@ -342,7 +342,8 @@ class Update(TelegramObject):
@classmethod
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:
return None

View file

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

View file

@ -53,7 +53,8 @@ class UserProfilePhotos(TelegramObject):
@classmethod
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:
return None
@ -63,6 +64,7 @@ class UserProfilePhotos(TelegramObject):
return cls(**data)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data = super().to_dict()
data['photos'] = []

View file

@ -18,32 +18,9 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""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
# 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
class TelegramDeprecationWarning(Warning):
pass
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
"""Custom warning class for deprecations in this library."""

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:
"""
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:
return datetime.replace(tzinfo=DTM_UTC)
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
:obj:`None`.
"""
if reference_timestamp is None:
reference_timestamp = time.time()
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``
"""
# Importing on file-level yields cyclic Import Errors
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:
:obj:`dict`: The user/chat_data defaultdict after decoding
"""
tmp: DefaultDict[int, Dict[object, object]] = defaultdict(dict)
decoded_data = json.loads(data)
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
# 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
compatibility."""
compatibility.
"""
import warnings
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
"""
r"""
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
\\ and " and replace any \n and \r with a space.
@ -195,6 +195,7 @@ class Request:
return self._con_pool_size
def stop(self) -> None:
"""Performs cleanup on shutdown."""
self._con_pool.clear() # type: ignore
@staticmethod
@ -205,7 +206,6 @@ class Request:
dict: A JSON parsed as Python dict with results - on error this dict will be empty.
"""
decoded_s = json_data.decode('utf-8', 'replace')
try:
data = json.loads(decoded_s)

View file

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

View file

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

View file

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

View file

@ -36,7 +36,7 @@ def terminal_summary_wrapper(original, plugin_name):
@pytest.mark.trylast
def pytest_configure(config):
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)

View file

@ -260,7 +260,6 @@ class TestAnimation:
animation = Animation.de_json(json_dict, bot)
assert animation.file_id == self.animation_file_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.mime_type == self.mime_type
assert animation.file_size == self.file_size

View file

@ -81,7 +81,7 @@ def chat_permissions():
def inline_results_callback(page=None):
if not page:
return [InlineQueryResultArticle(i, str(i), None) for i in range(1, 254)]
elif page <= 5:
if page <= 5:
return [
InlineQueryResultArticle(i, str(i), None)
for i in range(page * 5 + 1, (page + 1) * 5 + 1)
@ -217,11 +217,13 @@ class TestBot:
@flaky(3, 1)
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 message.forward_from.username == message.from_user.username
assert isinstance(message.forward_date, dtm.datetime)
assert forward_message.text == message.text
assert forward_message.forward_from.username == message.from_user.username
assert isinstance(forward_message.forward_date, dtm.datetime)
@flaky(3, 1)
def test_delete_message(self, bot, chat_id):
@ -787,7 +789,7 @@ class TestBot:
def make_assertion(url, data, *args, **kwargs):
results = data['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)
return length_matches and ids_match and next_offset_matches
@ -800,7 +802,7 @@ class TestBot:
def make_assertion(url, data, *args, **kwargs):
results = data['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'
return length_matches and ids_match and next_offset_matches
@ -813,7 +815,7 @@ class TestBot:
def make_assertion(url, data, *args, **kwargs):
results = data['results']
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'] == ''
return length_matches and ids_match and next_offset_matches
@ -826,7 +828,7 @@ class TestBot:
def make_assertion(url, data, *args, **kwargs):
results = data['results']
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'
return length and ids and next_offset
@ -1910,14 +1912,13 @@ class TestBot:
reply_to_message_id=reply_to_message.message_id,
)
return
else:
returned = default_bot.copy_message(
chat_id,
from_chat_id=chat_id,
message_id=media_message.message_id,
caption="<b>Test</b>",
reply_to_message_id=reply_to_message.message_id,
)
returned = default_bot.copy_message(
chat_id,
from_chat_id=chat_id,
message_id=media_message.message_id,
caption="<b>Test</b>",
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
# message object
temp_message = default_bot.send_message(

View file

@ -102,13 +102,13 @@ class BaseTest:
def callback_context_regex1(self, update, context):
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
self.test_flag = types and num
def callback_context_regex2(self, update, context):
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
self.test_flag = types and num

View file

@ -45,6 +45,5 @@ class TestConstants:
with pytest.raises(
BadRequest,
match="Media_caption_too_long",
):
with open('tests/data/telegram.png', 'rb') as f:
bot.send_photo(photo=f, caption=bad_caption, chat_id=chat_id)
), open('tests/data/telegram.png', 'rb') as f:
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)
if self.raise_dp_handler_stop:
raise DispatcherHandlerStop(result)
else:
return result
return result
return decorator
@ -168,8 +167,7 @@ class TestConversationHandler:
def start(self, bot, update):
if isinstance(update, Update):
return self._set_state(update, self.THIRSTY)
else:
return self._set_state(bot, self.THIRSTY)
return self._set_state(bot, self.THIRSTY)
@raise_dphs
def end(self, bot, update):
@ -187,8 +185,7 @@ class TestConversationHandler:
def brew(self, bot, update):
if isinstance(update, Update):
return self._set_state(update, self.BREWING)
else:
return self._set_state(bot, self.BREWING)
return self._set_state(bot, self.BREWING)
@raise_dphs
def drink(self, bot, update):

View file

@ -683,7 +683,7 @@ class TestDispatcher:
assert self.received == 'Unauthorized.'
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)
for thread_name in thread_names:
assert thread_name.startswith(f"Bot:{dp2.bot.id}:worker:")

View file

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

View file

@ -133,19 +133,19 @@ class TestFilters:
assert isinstance(result, dict)
matches = result['matches']
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)
assert result
assert isinstance(result, dict)
matches = result['matches']
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)
assert result
assert isinstance(result, dict)
matches = result['matches']
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)
assert not result
@ -158,19 +158,19 @@ class TestFilters:
assert isinstance(result, dict)
matches = result['matches']
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)
assert result
assert isinstance(result, dict)
matches = result['matches']
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)
assert result
assert isinstance(result, dict)
matches = result['matches']
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
result = (Filters.command | Filters.regex(r'linked param'))(update)
assert result is True
@ -178,91 +178,91 @@ class TestFilters:
def test_regex_complex_merges(self, update):
SRE_TYPE = type(re.match("", ""))
update.message.text = 'test it out'
filter = Filters.regex('test') & (
test_filter = Filters.regex('test') & (
(Filters.status_update | Filters.forwarded) | Filters.regex('out')
)
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, dict)
matches = result['matches']
assert isinstance(matches, list)
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()
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, dict)
matches = result['matches']
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'
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, dict)
matches = result['matches']
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
result = filter(update)
result = test_filter(update)
assert not result
update.message.text = 'test it out'
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, dict)
matches = result['matches']
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
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, dict)
matches = result['matches']
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'
result = filter(update)
result = test_filter(update)
assert not result
update.message.text = 'test it out'
update.message.forward_date = 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
)
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, dict)
matches = result['matches']
assert isinstance(matches, list)
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'
result = filter(update)
result = test_filter(update)
assert not result
update.message.pinned_message = True
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, dict)
matches = result['matches']
assert isinstance(matches, list)
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'
result = filter(update)
result = test_filter(update)
assert not result
update.message.text = '/start'
update.message.entities = [MessageEntity(MessageEntity.BOT_COMMAND, 0, 6)]
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, bool)
update.message.text = '/start it'
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, dict)
matches = result['matches']
assert isinstance(matches, list)
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):
update.message.text = '/start deep-linked param'
@ -336,13 +336,13 @@ class TestFilters:
assert isinstance(result, dict)
matches = result['matches']
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)
assert result
assert isinstance(result, dict)
matches = result['matches']
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'))(
update
)
@ -350,7 +350,7 @@ class TestFilters:
assert isinstance(result, dict)
matches = result['matches']
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'))(
update
)
@ -365,19 +365,19 @@ class TestFilters:
assert isinstance(result, dict)
matches = result['matches']
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)
assert result
assert isinstance(result, dict)
matches = result['matches']
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)
assert result
assert isinstance(result, dict)
matches = result['matches']
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
result = (Filters.command | Filters.caption_regex(r'linked param'))(update)
assert result is True
@ -385,130 +385,130 @@ class TestFilters:
def test_caption_regex_complex_merges(self, update):
SRE_TYPE = type(re.match("", ""))
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')
)
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, dict)
matches = result['matches']
assert isinstance(matches, list)
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()
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, dict)
matches = result['matches']
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'
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, dict)
matches = result['matches']
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
result = filter(update)
result = test_filter(update)
assert not result
update.message.caption = 'test it out'
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, dict)
matches = result['matches']
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
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, dict)
matches = result['matches']
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'
result = filter(update)
result = test_filter(update)
assert not result
update.message.caption = 'test it out'
update.message.forward_date = 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
)
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, dict)
matches = result['matches']
assert isinstance(matches, list)
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'
result = filter(update)
result = test_filter(update)
assert not result
update.message.pinned_message = True
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, dict)
matches = result['matches']
assert isinstance(matches, list)
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'
result = filter(update)
result = test_filter(update)
assert not result
update.message.caption = '/start'
update.message.entities = [MessageEntity(MessageEntity.BOT_COMMAND, 0, 6)]
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, bool)
update.message.caption = '/start it'
result = filter(update)
result = test_filter(update)
assert result
assert isinstance(result, dict)
matches = result['matches']
assert isinstance(matches, list)
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):
update.message.caption = '/start deep-linked param'
update.message.entities = [MessageEntity(MessageEntity.BOT_COMMAND, 0, 5)]
filter = ~Filters.caption_regex(r'deep-linked param')
result = filter(update)
test_filter = ~Filters.caption_regex(r'deep-linked param')
result = test_filter(update)
assert not result
update.message.caption = 'not it'
result = filter(update)
result = test_filter(update)
assert result
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"
result = filter(update)
result = test_filter(update)
assert not result
update.message.caption = '/start'
update.message.entities = [MessageEntity(MessageEntity.BOT_COMMAND, 0, 6)]
result = filter(update)
result = test_filter(update)
assert result
update.message.caption = '/linked'
result = filter(update)
result = test_filter(update)
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.entities = []
result = filter(update)
result = test_filter(update)
assert not result
update.message.caption = '/start linked'
update.message.entities = [MessageEntity(MessageEntity.BOT_COMMAND, 0, 6)]
result = filter(update)
result = test_filter(update)
assert result
update.message.caption = '/start'
result = filter(update)
result = test_filter(update)
assert result
update.message.caption = 'nothig'
update.message.entities = []
result = filter(update)
result = test_filter(update)
assert result
def test_filters_reply(self, update):

View file

@ -406,8 +406,8 @@ class TestSendMediaGroup:
messages = bot.send_media_group(chat_id, media_group)
assert isinstance(messages, list)
assert len(messages) == 3
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(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.caption == f'photo {idx+1}' for idx, mes in enumerate(messages))
assert all(
mes.caption_entities == [MessageEntity(MessageEntity.BOLD, 0, 5)] for mes in messages
@ -421,8 +421,8 @@ class TestSendMediaGroup:
)
assert isinstance(messages, list)
assert len(messages) == 3
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(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.caption == f'photo {idx+1}' for idx, mes in enumerate(messages))
assert all(
mes.caption_entities == [MessageEntity(MessageEntity.BOLD, 0, 5)] for mes in messages
@ -491,8 +491,8 @@ class TestSendMediaGroup:
assert isinstance(messages, list)
assert len(messages) == 3
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(isinstance(mes, Message) for mes in messages)
assert all(mes.media_group_id == messages[0].media_group_id for mes in messages)
@flaky(3, 1)
@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(url, data, **kwargs):
return (
data['provider_data'] == '{"test_data": 123456789}' # Depends if using
or data['provider_data'] == '{"test_data":123456789}'
) # ujson or not
# depends on whether we're using ujson
return data['provider_data'] in ['{"test_data": 123456789}', '{"test_data":123456789}']
monkeypatch.setattr(bot.request, 'post', test)

View file

@ -606,26 +606,26 @@ class TestMessage:
def test_chat_id(self, message):
assert message.chat_id == message.chat.id
@pytest.mark.parametrize('type', argvalues=[Chat.SUPERGROUP, Chat.CHANNEL])
def test_link_with_username(self, message, type):
@pytest.mark.parametrize('_type', argvalues=[Chat.SUPERGROUP, Chat.CHANNEL])
def test_link_with_username(self, message, _type):
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}'
@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.id = id
message.chat.type = type
message.chat.id = _id
message.chat.type = _type
# 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}'
@pytest.mark.parametrize('id, username', argvalues=[(None, 'username'), (-3, None)])
def test_link_private_chats(self, message, id, username):
@pytest.mark.parametrize('_id, username', argvalues=[(None, 'username'), (-3, None)])
def test_link_private_chats(self, message, _id, username):
message.chat.type = Chat.PRIVATE
message.chat.id = id
message.chat.id = _id
message.chat.username = username
assert message.link is None
message.chat.type = Chat.GROUP

View file

@ -121,13 +121,13 @@ class TestMessageHandler:
def callback_context_regex1(self, update, context):
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
self.test_flag = types and num
def callback_context_regex2(self, update, context):
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
self.test_flag = types and num

View file

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

View file

@ -1195,9 +1195,6 @@ class TestPickelPersistence:
h2 = MessageHandler(None, second, pass_user_data=True, pass_chat_data=True)
dp.add_handler(h1)
dp.process_update(update)
del dp
del u
del pickle_persistence
pickle_persistence_2 = PicklePersistence(
filename='pickletest',
store_user_data=True,
@ -1218,10 +1215,7 @@ class TestPickelPersistence:
dp.user_data[4242424242]['my_test'] = 'Working!'
dp.chat_data[-4242424242]['my_test2'] = 'Working2!'
dp.bot_data['test'] = 'Working3!'
u.signal_handler(signal.SIGINT, None)
del dp
del u
del pickle_persistence
u._signal_handler(signal.SIGINT, None)
pickle_persistence_2 = PicklePersistence(
filename='pickletest',
store_user_data=True,
@ -1240,10 +1234,7 @@ class TestPickelPersistence:
dp.user_data[4242424242]['my_test'] = 'Working!'
dp.chat_data[-4242424242]['my_test2'] = 'Working2!'
dp.bot_data['my_test3'] = 'Working3!'
u.signal_handler(signal.SIGINT, None)
del dp
del u
del pickle_persistence_only_bot
u._signal_handler(signal.SIGINT, None)
pickle_persistence_2 = PicklePersistence(
filename='pickletest',
store_user_data=False,
@ -1262,10 +1253,7 @@ class TestPickelPersistence:
u.running = True
dp.user_data[4242424242]['my_test'] = 'Working!'
dp.chat_data[-4242424242]['my_test2'] = 'Working2!'
u.signal_handler(signal.SIGINT, None)
del dp
del u
del pickle_persistence_only_chat
u._signal_handler(signal.SIGINT, None)
pickle_persistence_2 = PicklePersistence(
filename='pickletest',
store_user_data=False,
@ -1284,10 +1272,7 @@ class TestPickelPersistence:
u.running = True
dp.user_data[4242424242]['my_test'] = 'Working!'
dp.chat_data[-4242424242]['my_test2'] = 'Working2!'
u.signal_handler(signal.SIGINT, None)
del dp
del u
del pickle_persistence_only_user
u._signal_handler(signal.SIGINT, None)
pickle_persistence_2 = PicklePersistence(
filename='pickletest',
store_user_data=True,
@ -1310,10 +1295,10 @@ class TestPickelPersistence:
start = CommandHandler('start', start)
def next(update, context):
def next_callback(update, context):
return NEXT2
next = MessageHandler(None, next)
next_handler = MessageHandler(None, next_callback)
def next2(update, context):
return ConversationHandler.END
@ -1321,7 +1306,7 @@ class TestPickelPersistence:
next2 = MessageHandler(None, next2)
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)
assert ch.conversations[ch._get_key(update)] == 1
@ -1345,10 +1330,10 @@ class TestPickelPersistence:
start = CommandHandler('start', start)
def next(update, context):
def next_callback(update, context):
return NEXT2
next = MessageHandler(None, next)
next_handler = MessageHandler(None, next_callback)
def next2(update, context):
return ConversationHandler.END
@ -1356,7 +1341,7 @@ class TestPickelPersistence:
next2 = MessageHandler(None, next2)
nested_ch = ConversationHandler(
[next],
[next_handler],
{NEXT2: [next2]},
[],
name='name3',
@ -1612,12 +1597,9 @@ class TestDictPersistence:
h2 = MessageHandler(None, second, pass_user_data=True, pass_chat_data=True)
dp.add_handler(h1)
dp.process_update(update)
del dp
del u
user_data = dict_persistence.user_data_json
chat_data = dict_persistence.chat_data_json
bot_data = dict_persistence.bot_data_json
del dict_persistence
dict_persistence_2 = DictPersistence(
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)
def next(update, context):
def next_callback(update, context):
return NEXT2
next = MessageHandler(None, next)
next_handler = MessageHandler(None, next_callback)
def next2(update, context):
return ConversationHandler.END
@ -1649,7 +1631,7 @@ class TestDictPersistence:
next2 = MessageHandler(None, next2)
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)
assert ch.conversations[ch._get_key(update)] == 1
@ -1672,10 +1654,10 @@ class TestDictPersistence:
start = CommandHandler('start', start)
def next(update, context):
def next_callback(update, context):
return NEXT2
next = MessageHandler(None, next)
next_handler = MessageHandler(None, next_callback)
def next2(update, context):
return ConversationHandler.END
@ -1683,7 +1665,7 @@ class TestDictPersistence:
next2 = MessageHandler(None, next2)
nested_ch = ConversationHandler(
[next],
[next_handler],
{NEXT2: [next2]},
[],
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
i = 0
for type in all_types:
if getattr(update, type) is not None:
for _type in all_types:
if getattr(update, _type) is not None:
i += 1
assert getattr(update, type) == paramdict[type]
assert getattr(update, _type) == paramdict[_type]
assert i == 1
def test_update_de_json_empty(self, bot):
@ -118,9 +118,9 @@ class TestUpdate:
assert isinstance(update_dict, dict)
assert update_dict['update_id'] == update.update_id
for type in all_types:
if getattr(update, type) is not None:
assert update_dict[type] == getattr(update, type).to_dict()
for _type in all_types:
if getattr(update, _type) is not None:
assert update_dict[_type] == getattr(update, _type).to_dict()
def test_effective_chat(self, update):
# Test that it's sometimes None per docstring