diff --git a/telegram/_bot.py b/telegram/_bot.py index 79afc1a11d7..e7b060f4a06 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -273,7 +273,7 @@ def __init__( warning_string = "request" if warning_string: - warn( + self._warn( f"You set the HTTP version for the {warning_string} HTTPXRequest instance to " f"HTTP/2. The self hosted bot api instances only support HTTP/1.1. You should " f"either run a HTTP proxy in front of it which supports HTTP/2 or use HTTP/1.1.", @@ -338,6 +338,15 @@ def private_key(self) -> Optional[Any]: """ return self._private_key + @classmethod + def _warn( + cls, message: str, category: Type[Warning] = PTBUserWarning, stacklevel: int = 0 + ) -> None: + """Convenience method to issue a warning. This method is here mostly to make it easier + for ExtBot to add 1 level to all warning calls. + """ + warn(message=message, category=category, stacklevel=stacklevel + 1) + def __reduce__(self) -> NoReturn: """Customizes how :func:`copy.deepcopy` processes objects of this type. Bots can not be pickled and this method will always raise an exception. @@ -1157,7 +1166,10 @@ async def send_audio( """ thumbnail_or_thumb: FileInput = warn_about_thumb_return_thumbnail( - deprecated_arg=thumb, new_arg=thumbnail + deprecated_arg=thumb, + new_arg=thumbnail, + warn_callback=self._warn, + stacklevel=3, ) data: JSONDict = { "chat_id": chat_id, @@ -1294,7 +1306,10 @@ async def send_document( """ thumbnail_or_thumb: FileInput = warn_about_thumb_return_thumbnail( - deprecated_arg=thumb, new_arg=thumbnail + deprecated_arg=thumb, + new_arg=thumbnail, + warn_callback=self._warn, + stacklevel=3, ) data: JSONDict = { @@ -1531,7 +1546,10 @@ async def send_video( """ thumbnail_or_thumb: FileInput = warn_about_thumb_return_thumbnail( - deprecated_arg=thumb, new_arg=thumbnail + deprecated_arg=thumb, + new_arg=thumbnail, + warn_callback=self._warn, + stacklevel=3, ) data: JSONDict = { "chat_id": chat_id, @@ -1664,7 +1682,10 @@ async def send_video_note( """ thumbnail_or_thumb: FileInput = warn_about_thumb_return_thumbnail( - deprecated_arg=thumb, new_arg=thumbnail + deprecated_arg=thumb, + new_arg=thumbnail, + warn_callback=self._warn, + stacklevel=3, ) data: JSONDict = { "chat_id": chat_id, @@ -1807,6 +1828,8 @@ async def send_animation( thumbnail_or_thumb: FileInput = warn_about_thumb_return_thumbnail( deprecated_arg=thumb, new_arg=thumbnail, + warn_callback=self._warn, + stacklevel=3, ) data: JSONDict = { "chat_id": chat_id, @@ -5614,11 +5637,12 @@ async def upload_sticker_file( # only, which would have been confusing. if png_sticker: - warn( + self._warn( "Since Bot API 6.6, the parameter `png_sticker` for " "`upload_sticker_file` is deprecated. Please use the new parameters " "`sticker` and `sticker_format` instead.", - stacklevel=4, + stacklevel=3, + category=PTBDeprecationWarning, ) data: JSONDict = { @@ -5813,11 +5837,12 @@ async def create_new_sticker_set( # only, which would have been confusing. if any(pre_api_6_6_params.values()): - warn( + self._warn( f"Since Bot API 6.6, the parameters {set(pre_api_6_6_params)} for " "`create_new_sticker_set` are deprecated. Please use the new parameter " "`stickers` and `sticker_format` instead.", - stacklevel=4, + stacklevel=3, + category=PTBDeprecationWarning, ) data: JSONDict = { @@ -5984,11 +6009,12 @@ async def add_sticker_to_set( ) if any(pre_api_6_6_params.values()): - warn( + self._warn( f"Since Bot API 6.6, the parameters {set(pre_api_6_6_params)} for " "`add_sticker_to_set` are deprecated. Please use the new parameter `sticker` " "instead.", - stacklevel=4, + stacklevel=3, + category=PTBDeprecationWarning, ) data: JSONDict = { @@ -6224,7 +6250,7 @@ async def set_sticker_set_thumb( :class:`telegram.error.TelegramError` """ - warn( + self._warn( message=( "Bot API 6.6 renamed the method 'setStickerSetThumb' to 'setStickerSetThumbnail', " "hence method 'set_sticker_set_thumb' was renamed to 'set_sticker_set_thumbnail' " diff --git a/telegram/_files/_basethumbedmedium.py b/telegram/_files/_basethumbedmedium.py index 08ca07e5863..a23e8b8d42e 100644 --- a/telegram/_files/_basethumbedmedium.py +++ b/telegram/_files/_basethumbedmedium.py @@ -93,7 +93,7 @@ def __init__( deprecated_arg_name="thumb", new_arg_name="thumbnail", bot_api_version="6.6", - stacklevel=4, + stacklevel=3, ) @property diff --git a/telegram/_utils/warnings_transition.py b/telegram/_utils/warnings_transition.py index 06bf7eaa8c2..2e3b0ebad36 100644 --- a/telegram/_utils/warnings_transition.py +++ b/telegram/_utils/warnings_transition.py @@ -23,8 +23,7 @@ .. versionadded:: 20.2 """ -import functools -from typing import Any +from typing import Any, Callable, Type from telegram._utils.warnings import warn from telegram.warnings import PTBDeprecationWarning @@ -38,7 +37,8 @@ def warn_about_deprecated_arg_return_new_arg( deprecated_arg_name: str, new_arg_name: str, bot_api_version: str, - stacklevel: int = 3, + stacklevel: int = 2, + warn_callback: Callable[[str, Type[Warning], int], None] = warn, ) -> Any: """A helper function for the transition in API when argument is renamed. @@ -58,11 +58,11 @@ def warn_about_deprecated_arg_return_new_arg( ) if deprecated_arg: - warn( + warn_callback( f"Bot API {bot_api_version} renamed the argument '{deprecated_arg_name}' to " f"'{new_arg_name}'.", PTBDeprecationWarning, - stacklevel=stacklevel, + stacklevel + 1, ) return deprecated_arg @@ -73,7 +73,7 @@ def warn_about_deprecated_attr_in_property( deprecated_attr_name: str, new_attr_name: str, bot_api_version: str, - stacklevel: int = 3, + stacklevel: int = 2, ) -> None: """A helper function for the transition in API when attribute is renamed. Call from properties. @@ -83,16 +83,25 @@ def warn_about_deprecated_attr_in_property( f"Bot API {bot_api_version} renamed the attribute '{deprecated_attr_name}' to " f"'{new_attr_name}'.", PTBDeprecationWarning, - stacklevel=stacklevel, + stacklevel=stacklevel + 1, ) -warn_about_thumb_return_thumbnail = functools.partial( - warn_about_deprecated_arg_return_new_arg, - deprecated_arg_name="thumb", - new_arg_name="thumbnail", - bot_api_version="6.6", -) -"""A helper function to warn about using a deprecated 'thumb' argument and return it or the new -'thumbnail' argument, introduced in API 6.6. -""" +def warn_about_thumb_return_thumbnail( + deprecated_arg: Any, + new_arg: Any, + stacklevel: int = 2, + warn_callback: Callable[[str, Type[Warning], int], None] = warn, +) -> Any: + """A helper function to warn about using a deprecated 'thumb' argument and return it or the + new 'thumbnail' argument, introduced in API 6.6. + """ + return warn_about_deprecated_arg_return_new_arg( + deprecated_arg=deprecated_arg, + new_arg=new_arg, + warn_callback=warn_callback, + deprecated_arg_name="thumb", + new_arg_name="thumbnail", + bot_api_version="6.6", + stacklevel=stacklevel + 1, + ) diff --git a/telegram/ext/_conversationhandler.py b/telegram/ext/_conversationhandler.py index ce979fe58d9..5d7281d0cb2 100644 --- a/telegram/ext/_conversationhandler.py +++ b/telegram/ext/_conversationhandler.py @@ -840,11 +840,13 @@ async def handle_update( # type: ignore[override] if application.job_queue is None: warn( "Ignoring `conversation_timeout` because the Application has no JobQueue.", + stacklevel=1, ) elif not application.job_queue.scheduler.running: warn( "Ignoring `conversation_timeout` because the Applications JobQueue is " "not running.", + stacklevel=1, ) elif isinstance(new_state, asyncio.Task): # Add the new timeout job @@ -931,6 +933,7 @@ async def _trigger_timeout(self, context: CCT) -> None: warn( "ApplicationHandlerStop in TIMEOUT state of " "ConversationHandler has no effect. Ignoring.", + stacklevel=2, ) self._update_state(self.END, ctxt.conversation_key) diff --git a/telegram/ext/_extbot.py b/telegram/ext/_extbot.py index e8f02cb3461..a2995a46783 100644 --- a/telegram/ext/_extbot.py +++ b/telegram/ext/_extbot.py @@ -18,7 +18,6 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram Bot with convenience extensions.""" -import warnings from copy import copy from datetime import datetime from typing import ( @@ -31,6 +30,7 @@ Optional, Sequence, Tuple, + Type, TypeVar, Union, cast, @@ -87,11 +87,10 @@ from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue from telegram._utils.logging import get_logger from telegram._utils.types import DVInput, FileInput, JSONDict, ODVInput, ReplyMarkup -from telegram._utils.warnings import warn from telegram.ext._callbackdatacache import CallbackDataCache from telegram.ext._utils.types import RLARGS from telegram.request import BaseRequest -from telegram.warnings import PTBDeprecationWarning +from telegram.warnings import PTBUserWarning if TYPE_CHECKING: from telegram import ( @@ -235,6 +234,15 @@ def __init__( self._callback_data_cache = CallbackDataCache(bot=self, maxsize=maxsize) + @classmethod + def _warn( + cls, message: str, category: Type[Warning] = PTBUserWarning, stacklevel: int = 0 + ) -> None: + """We override this method to add one more level to the stacklevel, so that the warning + points to the user's code, not to the PTB code. + """ + super()._warn(message=message, category=category, stacklevel=stacklevel + 2) + @property def callback_data_cache(self) -> Optional[CallbackDataCache]: """:class:`telegram.ext.CallbackDataCache`: Optional. The cache for @@ -3284,30 +3292,16 @@ async def set_sticker_set_thumb( api_kwargs: JSONDict = None, rate_limit_args: RLARGS = None, ) -> bool: - # Manually issue deprecation here to get the stacklevel right - # Suppress the warning issued by super().set_sticker_set_thumb just in case - # the user explicitly enables deprecation warnings - # Unfortunately this is not entirely reliable (see tests), but it's a best effort solution - warn( - message=( - "Bot API 6.6 renamed the method 'setStickerSetThumb' to 'setStickerSetThumbnail', " - "hence method 'set_sticker_set_thumb' was renamed to 'set_sticker_set_thumbnail' " - "in PTB." - ), - category=PTBDeprecationWarning, - stacklevel=2, - ) - with warnings.catch_warnings(): - return await super().set_sticker_set_thumb( - name=name, - user_id=user_id, - thumb=thumb, - read_timeout=read_timeout, - write_timeout=write_timeout, - connect_timeout=connect_timeout, - pool_timeout=pool_timeout, - api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), - ) + return await super().set_sticker_set_thumb( + name=name, + user_id=user_id, + thumb=thumb, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) async def set_webhook( self, diff --git a/telegram/ext/_picklepersistence.py b/telegram/ext/_picklepersistence.py index 691e849705a..ee058ec5c17 100644 --- a/telegram/ext/_picklepersistence.py +++ b/telegram/ext/_picklepersistence.py @@ -98,7 +98,10 @@ def persistent_id(self, obj: object) -> Optional[str]: if obj is self._bot: return _REPLACED_KNOWN_BOT if isinstance(obj, Bot): - warn("Unknown bot instance found. Will be replaced by `None` during unpickling") + warn( + "Unknown bot instance found. Will be replaced by `None` during unpickling", + stacklevel=2, + ) return _REPLACED_UNKNOWN_BOT return None # pickles as usual diff --git a/tests/_files/test_animation.py b/tests/_files/test_animation.py index 85c5d9c92a3..2761f58002d 100644 --- a/tests/_files/test_animation.py +++ b/tests/_files/test_animation.py @@ -31,7 +31,10 @@ check_shortcut_call, check_shortcut_signature, ) -from tests.auxil.deprecations import check_thumb_deprecation_warnings_for_args_and_attrs +from tests.auxil.deprecations import ( + check_thumb_deprecation_warning_for_method_args, + check_thumb_deprecation_warnings_for_args_and_attrs, +) from tests.auxil.files import data_file from tests.auxil.slots import mro_slots @@ -191,6 +194,19 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): monkeypatch.setattr(bot.request, "post", make_assertion) assert await bot.send_animation(animation=animation, chat_id=chat_id) + @pytest.mark.parametrize("bot_class", ["Bot", "ExtBot"]) + async def test_send_animation_thumb_deprecation_warning( + self, recwarn, monkeypatch, bot_class, bot, raw_bot, chat_id, animation + ): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return True + + bot = raw_bot if bot_class == "Bot" else bot + + monkeypatch.setattr(bot.request, "post", make_assertion) + await bot.send_animation(chat_id, animation, thumb="thumb") + check_thumb_deprecation_warning_for_method_args(recwarn, __file__) + async def test_send_animation_with_local_files_throws_error_with_different_thumb_and_thumbnail( self, bot, chat_id ): diff --git a/tests/_files/test_audio.py b/tests/_files/test_audio.py index b8a4c44a1b2..2817710ee9d 100644 --- a/tests/_files/test_audio.py +++ b/tests/_files/test_audio.py @@ -31,7 +31,10 @@ check_shortcut_call, check_shortcut_signature, ) -from tests.auxil.deprecations import check_thumb_deprecation_warnings_for_args_and_attrs +from tests.auxil.deprecations import ( + check_thumb_deprecation_warning_for_method_args, + check_thumb_deprecation_warnings_for_args_and_attrs, +) from tests.auxil.files import data_file from tests.auxil.slots import mro_slots @@ -158,6 +161,19 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): monkeypatch.setattr(bot.request, "post", make_assertion) assert await bot.send_audio(audio=audio, chat_id=chat_id) + @pytest.mark.parametrize("bot_class", ["Bot", "ExtBot"]) + async def test_send_audio_thumb_deprecation_warning( + self, recwarn, monkeypatch, bot_class, bot, raw_bot, chat_id, audio + ): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return True + + bot = raw_bot if bot_class == "Bot" else bot + + monkeypatch.setattr(bot.request, "post", make_assertion) + await bot.send_audio(chat_id, audio, thumb="thumb") + check_thumb_deprecation_warning_for_method_args(recwarn, __file__) + async def test_send_audio_custom_filename(self, bot, chat_id, audio_file, monkeypatch): async def make_assertion(url, request_data: RequestData, *args, **kwargs): return list(request_data.multipart_data.values())[0][0] == "custom_filename" diff --git a/tests/_files/test_document.py b/tests/_files/test_document.py index a447aeb81b6..85cfaabab42 100644 --- a/tests/_files/test_document.py +++ b/tests/_files/test_document.py @@ -31,7 +31,10 @@ check_shortcut_call, check_shortcut_signature, ) -from tests.auxil.deprecations import check_thumb_deprecation_warnings_for_args_and_attrs +from tests.auxil.deprecations import ( + check_thumb_deprecation_warning_for_method_args, + check_thumb_deprecation_warnings_for_args_and_attrs, +) from tests.auxil.files import data_file from tests.auxil.slots import mro_slots @@ -157,6 +160,19 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): assert message + @pytest.mark.parametrize("bot_class", ["Bot", "ExtBot"]) + async def test_send_document_thumb_deprecation_warning( + self, recwarn, monkeypatch, bot_class, bot, raw_bot, chat_id, document + ): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return True + + bot = raw_bot if bot_class == "Bot" else bot + + monkeypatch.setattr(bot.request, "post", make_assertion) + await bot.send_document(chat_id, document, thumb="thumb") + check_thumb_deprecation_warning_for_method_args(recwarn, __file__) + @pytest.mark.parametrize("local_mode", [True, False]) async def test_send_document_local_files(self, monkeypatch, bot, chat_id, local_mode): try: diff --git a/tests/_files/test_sticker.py b/tests/_files/test_sticker.py index 433563a43d0..e8e9c653bd5 100644 --- a/tests/_files/test_sticker.py +++ b/tests/_files/test_sticker.py @@ -35,7 +35,6 @@ ) from telegram.constants import StickerFormat from telegram.error import BadRequest, TelegramError -from telegram.ext import ExtBot from telegram.request import RequestData from telegram.warnings import PTBDeprecationWarning from tests.auxil.bot_method_checks import ( @@ -640,15 +639,20 @@ def test_equality(self): assert a != e assert hash(a) != hash(e) - async def test_upload_sticker_file_warning(self, bot, monkeypatch, chat_id, recwarn): + @pytest.mark.parametrize("bot_class", ["Bot", "ExtBot"]) + async def test_upload_sticker_file_warning( + self, bot, raw_bot, monkeypatch, chat_id, recwarn, bot_class + ): async def make_assertion(*args, **kwargs): return {"file_id": "file_id", "file_unique_id": "file_unique_id"} + bot = raw_bot if bot_class == "Bot" else bot monkeypatch.setattr(bot, "_post", make_assertion) await bot.upload_sticker_file(chat_id, "png_sticker_file_id") assert len(recwarn) == 1 assert "Since Bot API 6.6, the parameter" in str(recwarn[0].message) + assert recwarn[0].category is PTBDeprecationWarning assert recwarn[0].filename == __file__ async def test_upload_sticker_file_missing_required_args(self, bot, chat_id): @@ -692,19 +696,31 @@ async def make_assertion(_, data, *args, **kwargs): test_flag = False await bot.upload_sticker_file(chat_id, file) assert test_flag - assert len(recwarn) in (1, 2) # second warning is for unclosed file + + warnings = [w for w in recwarn if w.category is not ResourceWarning] + assert len(warnings) == 1 + assert warnings[0].category is PTBDeprecationWarning + assert warnings[0].filename == __file__ + assert str(warnings[0].message).startswith( + "Since Bot API 6.6, the parameter `png_sticker` for " + ) finally: bot._local_mode = False - async def test_create_new_sticker_set_warning(self, bot, monkeypatch, chat_id, recwarn): + @pytest.mark.parametrize("bot_class", ["Bot", "ExtBot"]) + async def test_create_new_sticker_set_warning( + self, bot, raw_bot, bot_class, monkeypatch, chat_id, recwarn + ): async def make_assertion(*args, **kwargs): return True + bot = raw_bot if bot_class == "Bot" else bot monkeypatch.setattr(bot, "_post", make_assertion) await bot.create_new_sticker_set(chat_id, "name", "title", "some_str_emoji") assert len(recwarn) == 1 assert "Since Bot API 6.6, the parameters" in str(recwarn[0].message) + assert recwarn[0].category is PTBDeprecationWarning assert recwarn[0].filename == __file__ async def test_create_new_sticker_set_missing_required_args(self, bot, chat_id): @@ -767,7 +783,13 @@ async def make_assertion(_, data, *args, **kwargs): sticker_format=StickerFormat.STATIC, ) assert test_flag - assert len(recwarn) in (1, 2) + + warnings = [w for w in recwarn if w.category is not ResourceWarning] + assert len(warnings) == 1 + assert warnings[0].category is PTBDeprecationWarning + assert warnings[0].filename == __file__ + assert str(warnings[0].message).startswith("Since Bot API 6.6, the parameters") + assert "for `create_new_sticker_set` are deprecated" in str(warnings[0].message) async def test_create_new_sticker_all_params( self, monkeypatch, bot, chat_id, mask_position, recwarn @@ -804,6 +826,12 @@ async def make_assertion_new_params(_, data, *args, **kwargs): sticker_type=Sticker.MASK, ) assert len(recwarn) == 1 + assert recwarn[0].filename == __file__, "wrong stacklevel" + assert recwarn[0].category is PTBDeprecationWarning + assert str(recwarn[0].message).startswith("Since Bot API 6.6, the parameters") + assert "for `create_new_sticker_set` are deprecated" in str(recwarn[0].message) + + recwarn.clear() monkeypatch.setattr(bot, "_post", make_assertion_new_params) await bot.create_new_sticker_set( chat_id, @@ -813,17 +841,22 @@ async def make_assertion_new_params(_, data, *args, **kwargs): sticker_format=StickerFormat.STATIC, needs_repainting=True, ) - assert len(recwarn) == 1 + assert len(recwarn) == 0 - async def test_add_sticker_to_set_warning(self, bot, monkeypatch, chat_id, recwarn): + @pytest.mark.parametrize("bot_class", ["Bot", "ExtBot"]) + async def test_add_sticker_to_set_warning( + self, bot, raw_bot, monkeypatch, bot_class, chat_id, recwarn + ): async def make_assertion(*args, **kwargs): return True + bot = raw_bot if bot_class == "Bot" else bot monkeypatch.setattr(bot, "_post", make_assertion) await bot.add_sticker_to_set(chat_id, "name", "emoji", "fake_file_id") assert len(recwarn) == 1 assert "Since Bot API 6.6, the parameters" in str(recwarn[0].message) + assert recwarn[0].category is PTBDeprecationWarning assert recwarn[0].filename == __file__ async def test_add_sticker_to_set_missing_required_arg(self, bot, chat_id): @@ -872,7 +905,13 @@ async def make_assertion(_, data, *args, **kwargs): chat_id, "name", sticker=InputSticker(sticker=file, emoji_list=["this"]) ) assert test_flag - assert len(recwarn) in (1, 2) + + warnings = [w for w in recwarn if w.category is not ResourceWarning] + assert len(warnings) == 1 + assert warnings[0].category is PTBDeprecationWarning + assert warnings[0].filename == __file__ + assert str(warnings[0].message).startswith("Since Bot API 6.6, the parameters") + assert "for `add_sticker_to_set` are deprecated" in str(warnings[0].message) @pytest.mark.parametrize("local_mode", [True, False]) async def test_set_sticker_set_thumbnail_local_files( @@ -898,34 +937,26 @@ async def make_assertion(_, data, *args, **kwargs): finally: bot._local_mode = False + @pytest.mark.parametrize("bot_class", ["Bot", "ExtBot"]) async def test_set_sticker_set_thumb_deprecation_warning( - self, monkeypatch, bot, raw_bot, recwarn + self, monkeypatch, bot, raw_bot, recwarn, bot_class ): - ext_bot = bot + bot = bot if bot_class == "ExtBot" else raw_bot async def _post(*args, **kwargs): return True - for bot in (ext_bot, raw_bot): - cls_name = bot.__class__.__name__ - monkeypatch.setattr(bot, "_post", _post) - await bot.set_sticker_set_thumb("name", "user_id", "thumb") - - if isinstance(bot, ExtBot): - # Unfortunately, warnings.catch_warnings doesn't play well with pytest apparently - assert len(recwarn) == 2, f"Wrong warning number for class {cls_name}!" - else: - assert len(recwarn) == 1, f"No warning for class {cls_name}!" + monkeypatch.setattr(bot, "_post", _post) + await bot.set_sticker_set_thumb("name", "user_id", "thumb") - assert ( - recwarn[0].category is PTBDeprecationWarning - ), f"Wrong warning for class {cls_name}!" - assert "renamed the method 'setStickerSetThumb' to 'setStickerSetThumbnail'" in str( - recwarn[0].message - ), f"Wrong message for class {cls_name}!" + assert len(recwarn) == 1 + assert recwarn[0].category is PTBDeprecationWarning + assert "renamed the method 'setStickerSetThumb' to 'setStickerSetThumbnail'" in str( + recwarn[0].message + ) - assert recwarn[0].filename == __file__, f"incorrect stacklevel for class {cls_name}!" - recwarn.clear() + assert recwarn[0].filename == __file__, "incorrect stacklevel!" + recwarn.clear() async def test_get_file_instance_method(self, monkeypatch, sticker): async def make_assertion(*_, **kwargs): diff --git a/tests/_files/test_video.py b/tests/_files/test_video.py index 5d10d81d88d..773f7e21006 100644 --- a/tests/_files/test_video.py +++ b/tests/_files/test_video.py @@ -31,7 +31,10 @@ check_shortcut_call, check_shortcut_signature, ) -from tests.auxil.deprecations import check_thumb_deprecation_warnings_for_args_and_attrs +from tests.auxil.deprecations import ( + check_thumb_deprecation_warning_for_method_args, + check_thumb_deprecation_warnings_for_args_and_attrs, +) from tests.auxil.files import data_file from tests.auxil.slots import mro_slots @@ -171,6 +174,19 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): monkeypatch.setattr(bot.request, "post", make_assertion) assert await bot.send_video(chat_id, video=video) + @pytest.mark.parametrize("bot_class", ["Bot", "ExtBot"]) + async def test_send_video_thumb_deprecation_warning( + self, recwarn, monkeypatch, bot_class, bot, raw_bot, chat_id, video + ): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return True + + bot = raw_bot if bot_class == "Bot" else bot + + monkeypatch.setattr(bot.request, "post", make_assertion) + await bot.send_video(chat_id, video, thumb="thumb") + check_thumb_deprecation_warning_for_method_args(recwarn, __file__) + async def test_send_video_custom_filename(self, bot, chat_id, video_file, monkeypatch): async def make_assertion(url, request_data: RequestData, *args, **kwargs): return list(request_data.multipart_data.values())[0][0] == "custom_filename" diff --git a/tests/_files/test_videonote.py b/tests/_files/test_videonote.py index 5ce780fe1dc..aee0a042d17 100644 --- a/tests/_files/test_videonote.py +++ b/tests/_files/test_videonote.py @@ -30,7 +30,10 @@ check_shortcut_call, check_shortcut_signature, ) -from tests.auxil.deprecations import check_thumb_deprecation_warnings_for_args_and_attrs +from tests.auxil.deprecations import ( + check_thumb_deprecation_warning_for_method_args, + check_thumb_deprecation_warnings_for_args_and_attrs, +) from tests.auxil.files import data_file from tests.auxil.slots import mro_slots @@ -149,6 +152,19 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): monkeypatch.setattr(bot.request, "post", make_assertion) assert await bot.send_video_note(chat_id, video_note=video_note) + @pytest.mark.parametrize("bot_class", ["Bot", "ExtBot"]) + async def test_send_video_note_thumb_deprecation_warning( + self, recwarn, monkeypatch, bot_class, bot, raw_bot, chat_id, video_note + ): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return True + + bot = raw_bot if bot_class == "Bot" else bot + + monkeypatch.setattr(bot.request, "post", make_assertion) + await bot.send_video_note(chat_id, video_note, thumb="thumb") + check_thumb_deprecation_warning_for_method_args(recwarn, __file__) + async def test_send_video_note_custom_filename( self, bot, chat_id, video_note_file, monkeypatch ): diff --git a/tests/_inline/test_inlinequeryresultarticle.py b/tests/_inline/test_inlinequeryresultarticle.py index 6b36adcaf3b..c74ffe457e3 100644 --- a/tests/_inline/test_inlinequeryresultarticle.py +++ b/tests/_inline/test_inlinequeryresultarticle.py @@ -61,7 +61,7 @@ class TestInlineQueryResultArticleBase: class TestInlineQueryResultArticleWithoutRequest(TestInlineQueryResultArticleBase): - def test_slot_behaviour(self, inline_query_result_article, recwarn): + def test_slot_behaviour(self, inline_query_result_article): inst = inline_query_result_article for attr in inst.__slots__: assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" diff --git a/tests/_inline/test_inlinequeryresultgif.py b/tests/_inline/test_inlinequeryresultgif.py index 1edd5c353d8..7d3545af705 100644 --- a/tests/_inline/test_inlinequeryresultgif.py +++ b/tests/_inline/test_inlinequeryresultgif.py @@ -170,7 +170,7 @@ def test_thumb_url_issues_warning_and_works_without_positional_arg(self, recwarn new_name="thumbnail_url", ) - def test_init_throws_error_without_thumbnail_url_and_thumb_url(self, recwarn): + def test_init_throws_error_without_thumbnail_url_and_thumb_url(self): with pytest.raises(ValueError, match="You must pass either"): InlineQueryResultGif( TestInlineQueryResultGifBase.id_, @@ -188,7 +188,7 @@ def test_init_throws_error_without_thumbnail_url_and_thumb_url(self, recwarn): thumbnail_mime_type=TestInlineQueryResultGifBase.thumbnail_mime_type, ) - def test_throws_value_error_with_different_deprecated_and_new_arg_thumb_url(self, recwarn): + def test_throws_value_error_with_different_deprecated_and_new_arg_thumb_url(self): with pytest.raises( ValueError, match="different entities as 'thumb_url' and 'thumbnail_url'" ): diff --git a/tests/_inline/test_inlinequeryresultmpeg4gif.py b/tests/_inline/test_inlinequeryresultmpeg4gif.py index eab02169481..7a340a0464d 100644 --- a/tests/_inline/test_inlinequeryresultmpeg4gif.py +++ b/tests/_inline/test_inlinequeryresultmpeg4gif.py @@ -175,7 +175,7 @@ def test_thumb_url_issues_warning_and_works_without_positional_arg(self, recwarn new_name="thumbnail_url", ) - def test_init_throws_error_without_thumbnail_url_and_thumb_url(self, recwarn): + def test_init_throws_error_without_thumbnail_url_and_thumb_url(self): with pytest.raises(ValueError, match="You must pass either"): InlineQueryResultMpeg4Gif( TestInlineQueryResultMpeg4GifBase.id_, diff --git a/tests/_inline/test_inlinequeryresultphoto.py b/tests/_inline/test_inlinequeryresultphoto.py index a0796838f54..d10b15fa374 100644 --- a/tests/_inline/test_inlinequeryresultphoto.py +++ b/tests/_inline/test_inlinequeryresultphoto.py @@ -142,7 +142,7 @@ def test_thumb_url_issues_warning_and_works_without_positional_arg(self, recwarn new_name="thumbnail_url", ) - def test_init_throws_error_without_thumbnail_url_and_thumb_url(self, recwarn): + def test_init_throws_error_without_thumbnail_url_and_thumb_url(self): with pytest.raises(ValueError, match="You must pass either"): InlineQueryResultPhoto( TestInlineQueryResultPhotoBase.id_, diff --git a/tests/_inline/test_inlinequeryresultvideo.py b/tests/_inline/test_inlinequeryresultvideo.py index a751316cecf..e34eb348c46 100644 --- a/tests/_inline/test_inlinequeryresultvideo.py +++ b/tests/_inline/test_inlinequeryresultvideo.py @@ -157,7 +157,7 @@ def test_thumb_url_issues_warning_and_works_without_positional_arg(self, recwarn new_name="thumbnail_url", ) - def test_init_throws_error_without_thumbnail_url_and_thumb_url(self, recwarn): + def test_init_throws_error_without_thumbnail_url_and_thumb_url(self): with pytest.raises(ValueError, match="You must pass either"): InlineQueryResultVideo( TestInlineQueryResultVideoBase.id_, diff --git a/tests/auxil/deprecations.py b/tests/auxil/deprecations.py index 90c7e057193..224ecdc4067 100644 --- a/tests/auxil/deprecations.py +++ b/tests/auxil/deprecations.py @@ -19,7 +19,6 @@ from _pytest.recwarn import WarningsRecorder -from telegram._utils import warnings_transition from telegram.warnings import PTBDeprecationWarning @@ -68,7 +67,20 @@ def check_thumb_deprecation_warnings_for_args_and_attrs( assert recwarn[i].filename == calling_file, ( f'Warning for {names[i]} ("{str(recwarn[i].message)}") was issued by file ' - f"{recwarn[i].filename}, expected {calling_file} or {warnings_transition.__file__}" + f"{recwarn[i].filename}, expected {calling_file}" ) return True + + +def check_thumb_deprecation_warning_for_method_args( + recwarn: WarningsRecorder, + calling_file: str, + deprecated_name: str = "thumb", + new_name: str = "thumbnail", +): + """Similar as `check_thumb_deprecation_warnings_for_args_and_attrs`, but for bot methods.""" + assert len(recwarn) == 1 + assert recwarn[0].category is PTBDeprecationWarning + assert recwarn[0].filename == calling_file + assert f"argument '{deprecated_name}' to '{new_name}'" in str(recwarn[0].message) diff --git a/tests/ext/test_application.py b/tests/ext/test_application.py index 8ee4d09ec0f..461bd8af7e6 100644 --- a/tests/ext/test_application.py +++ b/tests/ext/test_application.py @@ -143,6 +143,7 @@ def test_manual_init_warning(self, recwarn, updater): str(recwarn[-1].message) == "`Application` instances should be built via the `ApplicationBuilder`." ) + assert recwarn[0].category is PTBUserWarning assert recwarn[0].filename == __file__, "stacklevel is incorrect!" @pytest.mark.parametrize( @@ -223,6 +224,7 @@ def test_job_queue(self, one_time_bot, app, recwarn): assert application.job_queue is None assert len(recwarn) == 1 assert str(recwarn[0].message) == expected_warning + assert recwarn[0].category is PTBUserWarning assert recwarn[0].filename == __file__, "wrong stacklevel" def test_custom_context_init(self, one_time_bot): @@ -1207,6 +1209,7 @@ async def callback(): assert task.result() == 43 else: assert len(recwarn) == 1 + assert recwarn[0].category is PTBUserWarning assert "won't be automatically awaited" in str(recwarn[0].message) assert recwarn[0].filename == __file__, "wrong stacklevel!" assert not task.done() @@ -2074,6 +2077,7 @@ async def raise_method(*args, **kwargs): for record in recwarn: print(record) if str(record.message).startswith("Could not add signal handlers for the stop"): + assert record.category is PTBUserWarning assert record.filename == __file__, "stacklevel is incorrect!" found = True assert found diff --git a/tests/ext/test_callbackcontext.py b/tests/ext/test_callbackcontext.py index 01aea45d7de..32d518714b9 100644 --- a/tests/ext/test_callbackcontext.py +++ b/tests/ext/test_callbackcontext.py @@ -31,6 +31,7 @@ ) from telegram.error import TelegramError from telegram.ext import ApplicationBuilder, CallbackContext, Job +from telegram.warnings import PTBUserWarning from tests.auxil.slots import mro_slots """ @@ -72,6 +73,7 @@ def test_job_queue(self, bot, app, recwarn): assert callback_context.job_queue is None assert len(recwarn) == 1 assert str(recwarn[0].message) == expected_warning + assert recwarn[0].category is PTBUserWarning assert recwarn[0].filename == __file__, "wrong stacklevel" def test_from_update(self, app): diff --git a/tests/ext/test_conversationhandler.py b/tests/ext/test_conversationhandler.py index c6e3aadd71c..484e30ce743 100644 --- a/tests/ext/test_conversationhandler.py +++ b/tests/ext/test_conversationhandler.py @@ -19,6 +19,7 @@ """Persistence of conversations is tested in test_basepersistence.py""" import asyncio import logging +from pathlib import Path from warnings import filterwarnings import pytest @@ -58,6 +59,7 @@ ) from telegram.warnings import PTBUserWarning from tests.auxil.build_messages import make_command_message +from tests.auxil.files import PROJECT_ROOT_PATH from tests.auxil.pytest_classes import PytestBot, make_bot from tests.auxil.slots import mro_slots @@ -458,6 +460,7 @@ class NotUpdate: # this for loop checks if the correct stacklevel is used when generating the warning for warning in recwarn: + assert warning.category is PTBUserWarning assert warning.filename == __file__, "incorrect stacklevel!" @pytest.mark.parametrize( @@ -676,6 +679,11 @@ async def callback(_, __): print(exc) raise exc assert len(recwarn) == 1 + assert recwarn[0].category is PTBUserWarning + assert ( + Path(recwarn[0].filename) + == PROJECT_ROOT_PATH / "telegram" / "ext" / "_conversationhandler.py" + ), "wrong stacklevel!" assert str(recwarn[0].message) == ( "'callback' returned state 69 which is unknown to the ConversationHandler xyz." ) @@ -1044,10 +1052,17 @@ async def test_no_running_job_queue_warning(self, app, bot, user1, recwarn, jq): assert len(recwarn) == 1 else: assert len(recwarn) == 2 + assert str(recwarn[0].message if jq else recwarn[1].message).startswith( "Ignoring `conversation_timeout`" ) assert ("is not running" if jq else "No `JobQueue` set up.") in str(recwarn[0].message) + for warning in recwarn: + assert warning.category is PTBUserWarning + assert ( + Path(warning.filename) + == PROJECT_ROOT_PATH / "telegram" / "ext" / "_conversationhandler.py" + ), "wrong stacklevel!" # now set app.job_queue back to it's original value async def test_schedule_job_exception(self, app, bot, user1, monkeypatch, caplog): @@ -1363,6 +1378,11 @@ def timeout(*args, **kwargs): assert handler.check_update(Update(0, message=message)) assert len(recwarn) == 1 assert str(recwarn[0].message).startswith("ApplicationHandlerStop in TIMEOUT") + assert recwarn[0].category is PTBUserWarning + assert ( + Path(recwarn[0].filename) + == PROJECT_ROOT_PATH / "telegram" / "ext" / "_jobqueue.py" + ), "wrong stacklevel!" await app.stop() diff --git a/tests/ext/test_dictpersistence.py b/tests/ext/test_dictpersistence.py index ae9d71dfbe8..b74db2d55f0 100644 --- a/tests/ext/test_dictpersistence.py +++ b/tests/ext/test_dictpersistence.py @@ -91,7 +91,7 @@ class TestDictPersistence: """Just tests the DictPersistence interface. Integration of persistence into Applictation is tested in TestBasePersistence!""" - async def test_slot_behaviour(self, recwarn): + async def test_slot_behaviour(self): inst = DictPersistence() for attr in inst.__slots__: assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" diff --git a/tests/ext/test_jobqueue.py b/tests/ext/test_jobqueue.py index 355b32c6611..b77d8b087ab 100644 --- a/tests/ext/test_jobqueue.py +++ b/tests/ext/test_jobqueue.py @@ -26,6 +26,7 @@ import pytest from telegram.ext import ApplicationBuilder, CallbackContext, ContextTypes, Job, JobQueue +from telegram.warnings import PTBUserWarning from tests.auxil.envvars import GITHUB_ACTION, TEST_WITH_OPT_DEPS from tests.auxil.pytest_classes import make_bot from tests.auxil.slots import mro_slots @@ -359,6 +360,7 @@ async def test_run_daily_warning(self, job_queue, recwarn): job_queue.run_daily(self.job_run_once, time_of_day, days=(0, 1, 2, 3)) assert len(recwarn) == 1 assert str(recwarn[0].message) == self.expected_warning + assert recwarn[0].category is PTBUserWarning assert recwarn[0].filename == __file__, "wrong stacklevel" @pytest.mark.parametrize("weekday", [0, 1, 2, 3, 4, 5, 6]) @@ -376,6 +378,7 @@ async def test_run_daily_days_of_week(self, job_queue, recwarn, weekday): assert scheduled_time == pytest.approx(expected_reschedule_time) assert len(recwarn) == 1 assert str(recwarn[0].message) == self.expected_warning + assert recwarn[0].category is PTBUserWarning assert recwarn[0].filename == __file__, "wrong stacklevel" async def test_run_monthly(self, job_queue, timezone): diff --git a/tests/ext/test_picklepersistence.py b/tests/ext/test_picklepersistence.py index 2164e12c718..c557cb21636 100644 --- a/tests/ext/test_picklepersistence.py +++ b/tests/ext/test_picklepersistence.py @@ -27,6 +27,7 @@ from telegram import Chat, Message, TelegramObject, Update, User from telegram.ext import ContextTypes, PersistenceInput, PicklePersistence from telegram.warnings import PTBUserWarning +from tests.auxil.files import PROJECT_ROOT_PATH from tests.auxil.pytest_classes import make_bot from tests.auxil.slots import mro_slots @@ -892,6 +893,10 @@ async def test_custom_pickler_unpickler_simple( assert len(recwarn) == 1 assert recwarn[-1].category is PTBUserWarning assert str(recwarn[-1].message).startswith("Unknown bot instance found.") + assert ( + Path(recwarn[-1].filename) + == PROJECT_ROOT_PATH / "telegram" / "ext" / "_picklepersistence.py" + ), "wrong stacklevel!" pp = PicklePersistence("pickletest", single_file=False, on_flush=False) pp.set_bot(bot) assert (await pp.get_chat_data())[12345]["unknown_bot_in_user"]._bot is None diff --git a/tests/test_bot.py b/tests/test_bot.py index 05ec6731efb..063c8b93f2d 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -76,6 +76,7 @@ from telegram.ext import ExtBot, InvalidCallbackData from telegram.helpers import escape_markdown from telegram.request import BaseRequest, HTTPXRequest, RequestData +from telegram.warnings import PTBUserWarning from tests.auxil.bot_method_checks import check_defaults_handling from tests.auxil.ci_bots import FALLBACKS from tests.auxil.envvars import GITHUB_ACTION, TEST_WITH_OPT_DEPS @@ -1650,22 +1651,21 @@ async def post(*args, **kwargs): bot.callback_data_cache.clear_callback_data() bot.callback_data_cache.clear_callback_queries() - async def test_http2_runtime_error(self, recwarn): - Bot("12345:ABCDE", base_url="http://", request=HTTPXRequest(http_version="2")) - Bot( + @pytest.mark.parametrize("bot_class", [Bot, ExtBot]) + async def test_http2_runtime_error(self, recwarn, bot_class): + bot_class("12345:ABCDE", base_url="http://", request=HTTPXRequest(http_version="2")) + bot_class( "12345:ABCDE", base_url="http://", get_updates_request=HTTPXRequest(http_version="2"), ) - Bot( + bot_class( "12345:ABCDE", base_url="http://", request=HTTPXRequest(http_version="2"), get_updates_request=HTTPXRequest(http_version="2"), ) - # this exists to make sure the error is also raised by extbot - ExtBot("12345:ABCDE", base_url="http://", request=HTTPXRequest(http_version="2")) - assert len(recwarn) == 4 + assert len(recwarn) == 3 assert "You set the HTTP version for the request HTTPXRequest instance" in str( recwarn[0].message ) @@ -1676,6 +1676,9 @@ async def test_http2_runtime_error(self, recwarn): "You set the HTTP version for the get_updates_request and request HTTPXRequest " "instance" in str(recwarn[2].message) ) + for warning in recwarn: + assert warning.filename == __file__, "wrong stacklevel!" + assert warning.category is PTBUserWarning class TestBotWithRequest: diff --git a/tests/test_chatpermissions.py b/tests/test_chatpermissions.py index da984f784b0..9585710c108 100644 --- a/tests/test_chatpermissions.py +++ b/tests/test_chatpermissions.py @@ -20,6 +20,7 @@ import pytest from telegram import ChatPermissions, User +from telegram.warnings import PTBDeprecationWarning from tests.auxil.slots import mro_slots @@ -211,3 +212,5 @@ def test_equality_warning(self, recwarn, chat_permissions): "In v21, granular media settings will be considered as well when comparing" " ChatPermissions instances." ) + assert recwarn[0].category is PTBDeprecationWarning + assert recwarn[0].filename == __file__, "wrong stacklevel" diff --git a/tests/test_keyboardbutton.py b/tests/test_keyboardbutton.py index 63dd08f7735..f58af574c7f 100644 --- a/tests/test_keyboardbutton.py +++ b/tests/test_keyboardbutton.py @@ -26,6 +26,7 @@ KeyboardButtonRequestUser, WebAppInfo, ) +from telegram.warnings import PTBDeprecationWarning from tests.auxil.slots import mro_slots @@ -141,3 +142,5 @@ def test_equality_warning(self, recwarn, keyboard_button): "In v21, granular media settings will be considered as well when comparing" " ChatPermissions instances." ) + assert recwarn[0].category is PTBDeprecationWarning + assert recwarn[0].filename == __file__, "wrong stacklevel" diff --git a/tests/test_telegramobject.py b/tests/test_telegramobject.py index 50ec6c2406a..756f69bcfcf 100644 --- a/tests/test_telegramobject.py +++ b/tests/test_telegramobject.py @@ -28,6 +28,7 @@ from telegram import Bot, BotCommand, Chat, Message, PhotoSize, TelegramObject, User from telegram.ext import PicklePersistence +from telegram.warnings import PTBUserWarning from tests.auxil.files import data_file from tests.auxil.slots import mro_slots @@ -222,6 +223,7 @@ class TGO(TelegramObject): assert a == b assert len(recwarn) == 1 assert str(recwarn[0].message) == expected_warning + assert recwarn[0].category is PTBUserWarning assert recwarn[0].filename == __file__, "wrong stacklevel" def test_meaningful_comparison(self, recwarn):