Warnings Overhaul (#2662)

This commit is contained in:
Harshil 2021-09-20 10:45:42 +04:00 committed by Hinrich Mahler
parent 7528503794
commit f6497093ce
16 changed files with 207 additions and 124 deletions

View file

@ -182,3 +182,4 @@ utils
telegram.utils.promise telegram.utils.promise
telegram.utils.request telegram.utils.request
telegram.utils.types telegram.utils.types
telegram.utils.warnings

View file

@ -0,0 +1,8 @@
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/utils/warnings.py
telegram.utils.warnings Module
===============================
.. automodule:: telegram.utils.warnings
:members:
:show-inheritance:

View file

@ -22,10 +22,10 @@ try:
except ImportError: except ImportError:
import json # type: ignore[no-redef] import json # type: ignore[no-redef]
import warnings
from typing import TYPE_CHECKING, List, Optional, Type, TypeVar, Tuple from typing import TYPE_CHECKING, List, Optional, Type, TypeVar, Tuple
from telegram.utils.types import JSONDict from telegram.utils.types import JSONDict
from telegram.utils.warnings import warn
if TYPE_CHECKING: if TYPE_CHECKING:
from telegram import Bot from telegram import Bot
@ -140,14 +140,16 @@ class TelegramObject:
# pylint: disable=no-member # pylint: disable=no-member
if isinstance(other, self.__class__): if isinstance(other, self.__class__):
if self._id_attrs == (): if self._id_attrs == ():
warnings.warn( warn(
f"Objects of type {self.__class__.__name__} can not be meaningfully tested for" f"Objects of type {self.__class__.__name__} can not be meaningfully tested for"
" equivalence." " equivalence.",
stacklevel=2,
) )
if other._id_attrs == (): if other._id_attrs == ():
warnings.warn( warn(
f"Objects of type {other.__class__.__name__} can not be meaningfully tested" f"Objects of type {other.__class__.__name__} can not be meaningfully tested"
" for equivalence." " for equivalence.",
stacklevel=2,
) )
return self._id_attrs == other._id_attrs return self._id_attrs == other._id_attrs
return super().__eq__(other) return super().__eq__(other)

View file

@ -21,7 +21,6 @@
import functools import functools
import logging import logging
import warnings
from datetime import datetime from datetime import datetime
from typing import ( from typing import (
@ -91,7 +90,7 @@ from telegram import (
) )
from telegram.constants import MAX_INLINE_QUERY_RESULTS from telegram.constants import MAX_INLINE_QUERY_RESULTS
from telegram.error import InvalidToken, TelegramError from telegram.error import InvalidToken, TelegramError
from telegram.utils.deprecate import TelegramDeprecationWarning from telegram.utils.warnings import PTBDeprecationWarning, warn
from telegram.utils.helpers import ( from telegram.utils.helpers import (
DEFAULT_NONE, DEFAULT_NONE,
DefaultValue, DefaultValue,
@ -198,10 +197,10 @@ class Bot(TelegramObject):
self.defaults = defaults self.defaults = defaults
if self.defaults: if self.defaults:
warnings.warn( warn(
'Passing Defaults to telegram.Bot is deprecated. Use telegram.ext.ExtBot instead.', 'Passing Defaults to telegram.Bot is deprecated. Use telegram.ext.ExtBot instead.',
TelegramDeprecationWarning, PTBDeprecationWarning,
stacklevel=3, stacklevel=4,
) )
if base_url is None: if base_url is None:

View file

@ -17,7 +17,6 @@
# You should have received a copy of the GNU Lesser Public License # You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the BasePersistence class.""" """This module contains the BasePersistence class."""
import warnings
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from copy import copy from copy import copy
from typing import Dict, Optional, Tuple, cast, ClassVar, Generic, DefaultDict, NamedTuple from typing import Dict, Optional, Tuple, cast, ClassVar, Generic, DefaultDict, NamedTuple
@ -26,6 +25,7 @@ from telegram import Bot
import telegram.ext.extbot import telegram.ext.extbot
from telegram.ext.utils.types import UD, CD, BD, ConversationDict, CDCData from telegram.ext.utils.types import UD, CD, BD, ConversationDict, CDCData
from telegram.utils.warnings import warn, PTBRuntimeWarning
class PersistenceInput(NamedTuple): class PersistenceInput(NamedTuple):
@ -230,10 +230,10 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
return new_immutable return new_immutable
if isinstance(obj, type): if isinstance(obj, type):
# classes usually do have a __dict__, but it's not writable # classes usually do have a __dict__, but it's not writable
warnings.warn( warn(
'BasePersistence.replace_bot does not handle classes. See ' f'BasePersistence.replace_bot does not handle classes such as {obj.__name__!r}. '
'the docs of BasePersistence.replace_bot for more information.', 'See the docs of BasePersistence.replace_bot for more information.',
RuntimeWarning, PTBRuntimeWarning,
) )
return obj return obj
@ -241,10 +241,10 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
new_obj = copy(obj) new_obj = copy(obj)
memo[obj_id] = new_obj memo[obj_id] = new_obj
except Exception: except Exception:
warnings.warn( warn(
'BasePersistence.replace_bot does not handle objects that can not be copied. See ' 'BasePersistence.replace_bot does not handle objects that can not be copied. See '
'the docs of BasePersistence.replace_bot for more information.', 'the docs of BasePersistence.replace_bot for more information.',
RuntimeWarning, PTBRuntimeWarning,
) )
memo[obj_id] = obj memo[obj_id] = obj
return obj return obj
@ -282,10 +282,10 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
memo[obj_id] = new_obj memo[obj_id] = new_obj
return new_obj return new_obj
except Exception as exception: except Exception as exception:
warnings.warn( warn(
f'Parsing of an object failed with the following exception: {exception}. ' f'Parsing of an object failed with the following exception: {exception}. '
f'See the docs of BasePersistence.replace_bot for more information.', f'See the docs of BasePersistence.replace_bot for more information.',
RuntimeWarning, PTBRuntimeWarning,
) )
memo[obj_id] = obj memo[obj_id] = obj
@ -333,20 +333,20 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
return new_immutable return new_immutable
if isinstance(obj, type): if isinstance(obj, type):
# classes usually do have a __dict__, but it's not writable # classes usually do have a __dict__, but it's not writable
warnings.warn( warn(
'BasePersistence.insert_bot does not handle classes. See ' f'BasePersistence.insert_bot does not handle classes such as {obj.__name__!r}. '
'the docs of BasePersistence.insert_bot for more information.', 'See the docs of BasePersistence.insert_bot for more information.',
RuntimeWarning, PTBRuntimeWarning,
) )
return obj return obj
try: try:
new_obj = copy(obj) new_obj = copy(obj)
except Exception: except Exception:
warnings.warn( warn(
'BasePersistence.insert_bot does not handle objects that can not be copied. See ' 'BasePersistence.insert_bot does not handle objects that can not be copied. See '
'the docs of BasePersistence.insert_bot for more information.', 'the docs of BasePersistence.insert_bot for more information.',
RuntimeWarning, PTBRuntimeWarning,
) )
memo[obj_id] = obj memo[obj_id] = obj
return obj return obj
@ -384,10 +384,10 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
memo[obj_id] = new_obj memo[obj_id] = new_obj
return new_obj return new_obj
except Exception as exception: except Exception as exception:
warnings.warn( warn(
f'Parsing of an object failed with the following exception: {exception}. ' f'Parsing of an object failed with the following exception: {exception}. '
f'See the docs of BasePersistence.insert_bot for more information.', f'See the docs of BasePersistence.insert_bot for more information.',
RuntimeWarning, PTBRuntimeWarning,
) )
memo[obj_id] = obj memo[obj_id] = obj

View file

@ -20,7 +20,6 @@
"""This module contains the ConversationHandler.""" """This module contains the ConversationHandler."""
import logging import logging
import warnings
import functools import functools
import datetime import datetime
from threading import Lock from threading import Lock
@ -39,6 +38,7 @@ from telegram.ext import (
from telegram.ext.utils.promise import Promise from telegram.ext.utils.promise import Promise
from telegram.ext.utils.types import ConversationDict from telegram.ext.utils.types import ConversationDict
from telegram.ext.utils.types import CCT from telegram.ext.utils.types import CCT
from telegram.utils.warnings import warn
if TYPE_CHECKING: if TYPE_CHECKING:
from telegram.ext import Dispatcher, Job from telegram.ext import Dispatcher, Job
@ -259,9 +259,10 @@ class ConversationHandler(Handler[Update, CCT]):
raise ValueError("'per_user', 'per_chat' and 'per_message' can't all be 'False'") raise ValueError("'per_user', 'per_chat' and 'per_message' can't all be 'False'")
if self.per_message and not self.per_chat: if self.per_message and not self.per_chat:
warnings.warn( warn(
"If 'per_message=True' is used, 'per_chat=True' should also be used, " "If 'per_message=True' is used, 'per_chat=True' should also be used, "
"since message IDs are not globally unique." "since message IDs are not globally unique.",
stacklevel=2,
) )
all_handlers: List[Handler] = [] all_handlers: List[Handler] = []
@ -274,37 +275,41 @@ class ConversationHandler(Handler[Update, CCT]):
if self.per_message: if self.per_message:
for handler in all_handlers: for handler in all_handlers:
if not isinstance(handler, CallbackQueryHandler): if not isinstance(handler, CallbackQueryHandler):
warnings.warn( warn(
"If 'per_message=True', all entry points and state handlers" "If 'per_message=True', all entry points, state handlers, and fallbacks"
" must be 'CallbackQueryHandler', since no other handlers " " must be 'CallbackQueryHandler', since no other handlers "
"have a message context." "have a message context.",
stacklevel=2,
) )
break break
else: else:
for handler in all_handlers: for handler in all_handlers:
if isinstance(handler, CallbackQueryHandler): if isinstance(handler, CallbackQueryHandler):
warnings.warn( warn(
"If 'per_message=False', 'CallbackQueryHandler' will not be " "If 'per_message=False', 'CallbackQueryHandler' will not be "
"tracked for every message." "tracked for every message.",
stacklevel=2,
) )
break break
if self.per_chat: if self.per_chat:
for handler in all_handlers: for handler in all_handlers:
if isinstance(handler, (InlineQueryHandler, ChosenInlineResultHandler)): if isinstance(handler, (InlineQueryHandler, ChosenInlineResultHandler)):
warnings.warn( warn(
"If 'per_chat=True', 'InlineQueryHandler' can not be used, " "If 'per_chat=True', 'InlineQueryHandler' can not be used, "
"since inline queries have no chat context." "since inline queries have no chat context.",
stacklevel=2,
) )
break break
if self.conversation_timeout: if self.conversation_timeout:
for handler in all_handlers: for handler in all_handlers:
if isinstance(handler, self.__class__): if isinstance(handler, self.__class__):
warnings.warn( warn(
"Using `conversation_timeout` with nested conversations is currently not " "Using `conversation_timeout` with nested conversations is currently not "
"supported. You can still try to use it, but it will likely behave " "supported. You can still try to use it, but it will likely behave "
"differently from what you expect." "differently from what you expect.",
stacklevel=2,
) )
break break
@ -644,8 +649,8 @@ class ConversationHandler(Handler[Update, CCT]):
new_state, dispatcher, update, context, conversation_key new_state, dispatcher, update, context, conversation_key
) )
else: else:
self.logger.warning( warn(
"Ignoring `conversation_timeout` because the Dispatcher has no JobQueue." "Ignoring `conversation_timeout` because the Dispatcher has no JobQueue.",
) )
if isinstance(self.map_to_parent, dict) and new_state in self.map_to_parent: if isinstance(self.map_to_parent, dict) and new_state in self.map_to_parent:
@ -680,9 +685,9 @@ class ConversationHandler(Handler[Update, CCT]):
elif new_state is not None: elif new_state is not None:
if new_state not in self.states: if new_state not in self.states:
warnings.warn( warn(
f"Handler returned state {new_state} which is unknown to the " f"Handler returned state {new_state} which is unknown to the "
f"ConversationHandler{' ' + self.name if self.name is not None else ''}." f"ConversationHandler{' ' + self.name if self.name is not None else ''}.",
) )
with self._conversations_lock: with self._conversations_lock:
self.conversations[key] = new_state self.conversations[key] = new_state
@ -711,9 +716,9 @@ class ConversationHandler(Handler[Update, CCT]):
try: try:
handler.handle_update(ctxt.update, ctxt.dispatcher, check, callback_context) handler.handle_update(ctxt.update, ctxt.dispatcher, check, callback_context)
except DispatcherHandlerStop: except DispatcherHandlerStop:
self.logger.warning( warn(
'DispatcherHandlerStop in TIMEOUT state of ' 'DispatcherHandlerStop in TIMEOUT state of '
'ConversationHandler has no effect. Ignoring.' 'ConversationHandler has no effect. Ignoring.',
) )
self._update_state(self.END, ctxt.conversation_key) self._update_state(self.END, ctxt.conversation_key)

View file

@ -19,7 +19,6 @@
"""This module contains the Dispatcher class.""" """This module contains the Dispatcher class."""
import logging import logging
import warnings
import weakref import weakref
from collections import defaultdict from collections import defaultdict
from queue import Empty, Queue from queue import Empty, Queue
@ -47,6 +46,7 @@ from telegram.ext.handler import Handler
import telegram.ext.extbot import telegram.ext.extbot
from telegram.ext.callbackdatacache import CallbackDataCache from telegram.ext.callbackdatacache import CallbackDataCache
from telegram.ext.utils.promise import Promise from telegram.ext.utils.promise import Promise
from telegram.utils.warnings import warn
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
from telegram.ext.utils.types import CCT, UD, CD, BD from telegram.ext.utils.types import CCT, UD, CD, BD
@ -199,8 +199,9 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
self.context_types = cast(ContextTypes[CCT, UD, CD, BD], context_types or ContextTypes()) self.context_types = cast(ContextTypes[CCT, UD, CD, BD], context_types or ContextTypes())
if self.workers < 1: if self.workers < 1:
warnings.warn( warn(
'Asynchronous callbacks can not be processed without at least one worker thread.' 'Asynchronous callbacks can not be processed without at least one worker thread.',
stacklevel=2,
) )
self.user_data: DefaultDict[int, UD] = defaultdict(self.context_types.user_data) self.user_data: DefaultDict[int, UD] = defaultdict(self.context_types.user_data)
@ -315,9 +316,9 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
continue continue
if isinstance(promise.exception, DispatcherHandlerStop): if isinstance(promise.exception, DispatcherHandlerStop):
self.logger.warning( warn(
'DispatcherHandlerStop is not supported with async functions; func: %s', 'DispatcherHandlerStop is not supported with async functions; '
promise.pooled_function.__name__, f'func: {promise.pooled_function.__name__}',
) )
continue continue

View file

@ -20,7 +20,6 @@
import logging import logging
import ssl import ssl
import warnings
from queue import Queue from queue import Queue
from signal import SIGABRT, SIGINT, SIGTERM, signal from signal import SIGABRT, SIGINT, SIGTERM, signal
from threading import Event, Lock, Thread, current_thread from threading import Event, Lock, Thread, current_thread
@ -42,7 +41,7 @@ from typing import (
from telegram import Bot, TelegramError from telegram import Bot, TelegramError
from telegram.error import InvalidToken, RetryAfter, TimedOut, Unauthorized from telegram.error import InvalidToken, RetryAfter, TimedOut, Unauthorized
from telegram.ext import Dispatcher, JobQueue, ContextTypes, ExtBot from telegram.ext import Dispatcher, JobQueue, ContextTypes, ExtBot
from telegram.utils.deprecate import TelegramDeprecationWarning from telegram.utils.warnings import PTBDeprecationWarning, warn
from telegram.utils.helpers import get_signal_name, DEFAULT_FALSE, DefaultValue from telegram.utils.helpers import get_signal_name, DEFAULT_FALSE, DefaultValue
from telegram.utils.request import Request from telegram.utils.request import Request
from telegram.ext.utils.types import CCT, UD, CD, BD from telegram.ext.utils.types import CCT, UD, CD, BD
@ -211,14 +210,14 @@ class Updater(Generic[CCT, UD, CD, BD]):
): ):
if defaults and bot: if defaults and bot:
warnings.warn( warn(
'Passing defaults to an Updater has no effect when a Bot is passed ' 'Passing defaults to an Updater has no effect when a Bot is passed '
'as well. Pass them to the Bot instead.', 'as well. Pass them to the Bot instead.',
TelegramDeprecationWarning, PTBDeprecationWarning,
stacklevel=2, stacklevel=2,
) )
if arbitrary_callback_data is not DEFAULT_FALSE and bot: if arbitrary_callback_data is not DEFAULT_FALSE and bot:
warnings.warn( warn(
'Passing arbitrary_callback_data to an Updater has no ' 'Passing arbitrary_callback_data to an Updater has no '
'effect when a Bot is passed as well. Pass them to the Bot instead.', 'effect when a Bot is passed as well. Pass them to the Bot instead.',
stacklevel=2, stacklevel=2,
@ -250,9 +249,10 @@ class Updater(Generic[CCT, UD, CD, BD]):
if bot is not None: if bot is not None:
self.bot = bot self.bot = bot
if bot.request.con_pool_size < con_pool_size: if bot.request.con_pool_size < con_pool_size:
self.logger.warning( warn(
'Connection pool of Request object is smaller than optimal value (%s)', f'Connection pool of Request object is smaller than optimal value '
con_pool_size, f'{con_pool_size}',
stacklevel=2,
) )
else: else:
# we need a connection pool the size of: # we need a connection pool the size of:
@ -299,9 +299,10 @@ class Updater(Generic[CCT, UD, CD, BD]):
self.bot = dispatcher.bot self.bot = dispatcher.bot
if self.bot.request.con_pool_size < con_pool_size: if self.bot.request.con_pool_size < con_pool_size:
self.logger.warning( warn(
'Connection pool of Request object is smaller than optimal value (%s)', f'Connection pool of Request object is smaller than optimal value '
con_pool_size, f'{con_pool_size}',
stacklevel=2,
) )
self.update_queue = dispatcher.update_queue self.update_queue = dispatcher.update_queue
self.__exception_event = dispatcher.exception_event self.__exception_event = dispatcher.exception_event

View file

@ -1,28 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# 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 a class which is used for deprecation warnings."""
# 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):
"""Custom warning class for deprecations in this library."""
__slots__ = ()

View file

@ -0,0 +1,68 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# 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 classes used for warnings."""
import warnings
from typing import Type
class PTBUserWarning(UserWarning):
"""
Custom user warning class used for warnings in this library.
.. versionadded:: 14.0
"""
__slots__ = ()
class PTBRuntimeWarning(PTBUserWarning, RuntimeWarning):
"""
Custom runtime warning class used for warnings in this library.
.. versionadded:: 14.0
"""
__slots__ = ()
# https://www.python.org/dev/peps/pep-0565/ recommends to use a custom warning class derived from
# DeprecationWarning. We also subclass from TGUserWarning so users can easily 'switch off' warnings
class PTBDeprecationWarning(PTBUserWarning, DeprecationWarning):
"""
Custom warning class for deprecations in this library.
.. versionchanged:: 14.0
Renamed TelegramDeprecationWarning to PTBDeprecationWarning.
"""
__slots__ = ()
def warn(message: str, category: Type[Warning] = PTBUserWarning, stacklevel: int = 0) -> None:
"""
Helper function used as a shortcut for warning with default values.
.. versionadded:: 14.0
Args:
category (:obj:`Type[Warning]`): Specify the Warning class to pass to ``warnings.warn()``.
stacklevel (:obj:`int`): Specify the stacklevel to pass to ``warnings.warn()``. Pass the
same value as you'd pass directly to ``warnings.warn()``.
"""
warnings.warn(message, category=category, stacklevel=stacklevel + 1)

View file

@ -64,7 +64,7 @@ from tests.bots import get_bot
# This is here instead of in setup.cfg due to https://github.com/pytest-dev/pytest/issues/8343 # This is here instead of in setup.cfg due to https://github.com/pytest-dev/pytest/issues/8343
def pytest_runtestloop(session): def pytest_runtestloop(session):
session.add_marker( session.add_marker(
pytest.mark.filterwarnings('ignore::telegram.utils.deprecate.TelegramDeprecationWarning') pytest.mark.filterwarnings('ignore::telegram.utils.warnings.PTBDeprecationWarning')
) )
@ -110,7 +110,7 @@ class DictDispatcher(Dispatcher):
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def bot(bot_info): def bot(bot_info):
return DictExtBot(bot_info['token'], private_key=PRIVATE_KEY, request=DictRequest()) return DictExtBot(bot_info['token'], private_key=PRIVATE_KEY, request=DictRequest(8))
DEFAULT_BOTS = {} DEFAULT_BOTS = {}

View file

@ -788,7 +788,7 @@ class TestConversationHandler:
assert not handler.check_update(Update(0, pre_checkout_query=pre_checkout_query)) assert not handler.check_update(Update(0, pre_checkout_query=pre_checkout_query))
assert not handler.check_update(Update(0, shipping_query=shipping_query)) assert not handler.check_update(Update(0, shipping_query=shipping_query))
def test_no_jobqueue_warning(self, dp, bot, user1, caplog): def test_no_jobqueue_warning(self, dp, bot, user1, recwarn):
handler = ConversationHandler( handler = ConversationHandler(
entry_points=self.entry_points, entry_points=self.entry_points,
states=self.states, states=self.states,
@ -813,12 +813,11 @@ class TestConversationHandler:
bot=bot, bot=bot,
) )
with caplog.at_level(logging.WARNING): dp.process_update(Update(update_id=0, message=message))
dp.process_update(Update(update_id=0, message=message)) sleep(0.5)
sleep(0.5) assert len(recwarn) == 1
assert len(caplog.records) == 1
assert ( assert (
caplog.records[0].message str(recwarn[0].message)
== "Ignoring `conversation_timeout` because the Dispatcher has no JobQueue." == "Ignoring `conversation_timeout` because the Dispatcher has no JobQueue."
) )
# now set dp.job_queue back to it's original value # now set dp.job_queue back to it's original value
@ -990,7 +989,7 @@ class TestConversationHandler:
# assert timeout handler didn't got called # assert timeout handler didn't got called
assert self.test_flag is False assert self.test_flag is False
def test_conversation_timeout_dispatcher_handler_stop(self, dp, bot, user1, caplog): def test_conversation_timeout_dispatcher_handler_stop(self, dp, bot, user1, recwarn):
handler = ConversationHandler( handler = ConversationHandler(
entry_points=self.entry_points, entry_points=self.entry_points,
states=self.states, states=self.states,
@ -1017,14 +1016,12 @@ class TestConversationHandler:
bot=bot, bot=bot,
) )
with caplog.at_level(logging.WARNING): dp.process_update(Update(update_id=0, message=message))
dp.process_update(Update(update_id=0, message=message)) assert handler.conversations.get((self.group.id, user1.id)) == self.THIRSTY
assert handler.conversations.get((self.group.id, user1.id)) == self.THIRSTY sleep(0.9)
sleep(0.9) assert handler.conversations.get((self.group.id, user1.id)) is None
assert handler.conversations.get((self.group.id, user1.id)) is None assert len(recwarn) == 1
assert len(caplog.records) == 1 assert str(recwarn[0].message).startswith('DispatcherHandlerStop in TIMEOUT')
rec = caplog.records[-1]
assert rec.getMessage().startswith('DispatcherHandlerStop in TIMEOUT')
def test_conversation_handler_timeout_update_and_context(self, dp, bot, user1): def test_conversation_handler_timeout_update_and_context(self, dp, bot, user1):
context = None context = None
@ -1360,6 +1357,7 @@ class TestConversationHandler:
"supported. You can still try to use it, but it will likely behave " "supported. You can still try to use it, but it will likely behave "
"differently from what you expect." "differently from what you expect."
) )
assert recwarn[0].filename == __file__, "incorrect stacklevel!"
def test_per_message_warning_is_only_shown_once(self, recwarn): def test_per_message_warning_is_only_shown_once(self, recwarn):
ConversationHandler( ConversationHandler(
@ -1373,10 +1371,28 @@ class TestConversationHandler:
) )
assert len(recwarn) == 1 assert len(recwarn) == 1
assert str(recwarn[0].message) == ( assert str(recwarn[0].message) == (
"If 'per_message=True', all entry points and state handlers" "If 'per_message=True', all entry points, state handlers, and fallbacks"
" must be 'CallbackQueryHandler', since no other handlers" " must be 'CallbackQueryHandler', since no other handlers"
" have a message context." " have a message context."
) )
assert recwarn[0].filename == __file__, "incorrect stacklevel!"
def test_per_message_but_not_per_chat_warning(self, recwarn):
ConversationHandler(
entry_points=[CallbackQueryHandler(self.code, "code")],
states={
self.BREWING: [CallbackQueryHandler(self.code, "code")],
},
fallbacks=[CallbackQueryHandler(self.code, "code")],
per_message=True,
per_chat=False,
)
assert len(recwarn) == 1
assert str(recwarn[0].message) == (
"If 'per_message=True' is used, 'per_chat=True' should also be used, "
"since message IDs are not globally unique."
)
assert recwarn[0].filename == __file__, "incorrect stacklevel!"
def test_per_message_false_warning_is_only_shown_once(self, recwarn): def test_per_message_false_warning_is_only_shown_once(self, recwarn):
ConversationHandler( ConversationHandler(
@ -1393,6 +1409,7 @@ class TestConversationHandler:
"If 'per_message=False', 'CallbackQueryHandler' will not be " "If 'per_message=False', 'CallbackQueryHandler' will not be "
"tracked for every message." "tracked for every message."
) )
assert recwarn[0].filename == __file__, "incorrect stacklevel!"
def test_warnings_per_chat_is_only_shown_once(self, recwarn): def test_warnings_per_chat_is_only_shown_once(self, recwarn):
def hello(update, context): def hello(update, context):
@ -1415,6 +1432,7 @@ class TestConversationHandler:
"If 'per_chat=True', 'InlineQueryHandler' can not be used," "If 'per_chat=True', 'InlineQueryHandler' can not be used,"
" since inline queries have no chat context." " since inline queries have no chat context."
) )
assert recwarn[0].filename == __file__, "incorrect stacklevel!"
def test_nested_conversation_handler(self, dp, bot, user1, user2): def test_nested_conversation_handler(self, dp, bot, user1, user2):
self.nested_states[self.DRINKING] = [ self.nested_states[self.DRINKING] = [

View file

@ -110,6 +110,7 @@ class TestDispatcher:
str(recwarn[0].message) str(recwarn[0].message)
== 'Asynchronous callbacks can not be processed without at least one worker thread.' == 'Asynchronous callbacks can not be processed without at least one worker thread.'
) )
assert recwarn[0].filename == __file__, "stacklevel is incorrect!"
def test_one_context_per_update(self, dp): def test_one_context_per_update(self, dp):
def one(update, context): def one(update, context):
@ -243,21 +244,18 @@ class TestDispatcher:
assert name1 != name2 assert name1 != name2
def test_async_raises_dispatcher_handler_stop(self, dp, caplog): def test_async_raises_dispatcher_handler_stop(self, dp, recwarn):
def callback(update, context): def callback(update, context):
raise DispatcherHandlerStop() raise DispatcherHandlerStop()
dp.add_handler(MessageHandler(Filters.all, callback, run_async=True)) dp.add_handler(MessageHandler(Filters.all, callback, run_async=True))
with caplog.at_level(logging.WARNING): dp.update_queue.put(self.message_update)
dp.update_queue.put(self.message_update) sleep(0.1)
sleep(0.1) assert len(recwarn) == 1
assert len(caplog.records) == 1 assert str(recwarn[-1].message).startswith(
assert ( 'DispatcherHandlerStop is not supported with async functions'
caplog.records[-1] )
.getMessage()
.startswith('DispatcherHandlerStop is not supported with async functions')
)
def test_add_async_handler(self, dp): def test_add_async_handler(self, dp):
dp.add_handler( dp.add_handler(

View file

@ -774,10 +774,10 @@ class TestBasePersistence:
assert len(recwarn) == 2 assert len(recwarn) == 2
assert str(recwarn[0].message).startswith( assert str(recwarn[0].message).startswith(
"BasePersistence.replace_bot does not handle classes." "BasePersistence.replace_bot does not handle classes such as 'CustomClass'"
) )
assert str(recwarn[1].message).startswith( assert str(recwarn[1].message).startswith(
"BasePersistence.insert_bot does not handle classes." "BasePersistence.insert_bot does not handle classes such as 'CustomClass'"
) )
def test_bot_replace_insert_bot_objects_with_faulty_equality(self, bot, bot_persistence): def test_bot_replace_insert_bot_objects_with_faulty_equality(self, bot, bot_persistence):

View file

@ -101,9 +101,9 @@ class TestTelegramObject:
a = TGO() a = TGO()
b = TGO() b = TGO()
assert a == b assert a == b
assert len(recwarn) == 2 assert len(recwarn) == 1
assert str(recwarn[0].message) == expected_warning assert str(recwarn[0].message) == expected_warning
assert str(recwarn[1].message) == expected_warning assert recwarn[0].filename == __file__, "wrong stacklevel"
def test_meaningful_comparison(self, recwarn): def test_meaningful_comparison(self, recwarn):
class TGO(TelegramObject): class TGO(TelegramObject):

View file

@ -56,7 +56,7 @@ from telegram.ext import (
InvalidCallbackData, InvalidCallbackData,
ExtBot, ExtBot,
) )
from telegram.utils.deprecate import TelegramDeprecationWarning from telegram.utils.warnings import PTBDeprecationWarning
from telegram.ext.utils.webhookhandler import WebhookServer from telegram.ext.utils.webhookhandler import WebhookServer
signalskip = pytest.mark.skipif( signalskip = pytest.mark.skipif(
@ -119,6 +119,16 @@ class TestUpdater:
assert len(recwarn) == 1 assert len(recwarn) == 1
assert 'Passing arbitrary_callback_data to an Updater' in str(recwarn[0].message) assert 'Passing arbitrary_callback_data to an Updater' in str(recwarn[0].message)
def test_warn_con_pool(self, bot, recwarn, dp):
dp = Dispatcher(bot, Queue(), workers=5)
Updater(bot=bot, workers=8)
Updater(dispatcher=dp, workers=None)
assert len(recwarn) == 2
for idx, value in enumerate((12, 9)):
warning = f'Connection pool of Request object is smaller than optimal value {value}'
assert str(recwarn[idx].message) == warning
assert recwarn[idx].filename == __file__, "wrong stacklevel!"
@pytest.mark.parametrize( @pytest.mark.parametrize(
('error',), ('error',),
argvalues=[(TelegramError('Test Error 2'),), (Unauthorized('Test Unauthorized'),)], argvalues=[(TelegramError('Test Error 2'),), (Unauthorized('Test Unauthorized'),)],
@ -647,5 +657,5 @@ class TestUpdater:
Updater(dispatcher=dispatcher, context_types=True) Updater(dispatcher=dispatcher, context_types=True)
def test_defaults_warning(self, bot): def test_defaults_warning(self, bot):
with pytest.warns(TelegramDeprecationWarning, match='no effect when a Bot is passed'): with pytest.warns(PTBDeprecationWarning, match='no effect when a Bot is passed'):
Updater(bot=bot, defaults=Defaults()) Updater(bot=bot, defaults=Defaults())