diff --git a/telegram/_bot.py b/telegram/_bot.py index 4bb85e1f5..799c5238e 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -524,7 +524,10 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): @classmethod def _warn( - cls, message: str, category: Type[Warning] = PTBUserWarning, stacklevel: int = 0 + cls, + message: Union[str, PTBUserWarning], + 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. @@ -837,7 +840,6 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): f"Please use 'Bot.{endpoint}' instead of " f"'Bot.do_api_request(\"{endpoint}\", ...)'" ), - PTBDeprecationWarning, stacklevel=2, ) @@ -4209,10 +4211,12 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): except NotImplementedError: arg_read_timeout = 2 self._warn( - f"The class {self._request[0].__class__.__name__} does not override " - "the property `read_timeout`. Overriding this property will be mandatory in " - "future versions. Using 2 seconds as fallback.", - PTBDeprecationWarning, + PTBDeprecationWarning( + "20.7", + f"The class {self._request[0].__class__.__name__} does not override " + "the property `read_timeout`. Overriding this property will be mandatory " + "in future versions. Using 2 seconds as fallback.", + ), stacklevel=2, ) diff --git a/telegram/_chat.py b/telegram/_chat.py index cfb564e48..f19a8e21e 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -910,10 +910,12 @@ class Chat(TelegramObject): for arg in _deprecated_attrs: if (val := object.__getattribute__(self, arg)) is not None and val != (): warn( - f"The argument `{arg}` is deprecated and will only be available via " - "`ChatFullInfo` in the future.", + PTBDeprecationWarning( + "NEXT.VERSION", + f"The argument `{arg}` is deprecated and will only be available via " + "`ChatFullInfo` in the future.", + ), stacklevel=2, - category=PTBDeprecationWarning, ) self._id_attrs = (self.id,) @@ -923,10 +925,12 @@ class Chat(TelegramObject): def __getattribute__(self, name: str) -> Any: if name in _deprecated_attrs and self.__class__ is Chat: warn( - f"The attribute `{name}` is deprecated and will only be accessible via " - "`ChatFullInfo` in the future.", + PTBDeprecationWarning( + "NEXT.VERSION", + f"The attribute `{name}` is deprecated and will only be accessible via " + "`ChatFullInfo` in the future.", + ), stacklevel=2, - category=PTBDeprecationWarning, ) return super().__getattribute__(name) diff --git a/telegram/_message.py b/telegram/_message.py index 61b538c03..a546436f1 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -1579,9 +1579,11 @@ class Message(MaybeInaccessibleMessage): if quote is not None: warn( - "The `quote` parameter is deprecated in favor of the `do_quote` parameter. Please " - "update your code to use `do_quote` instead.", - PTBDeprecationWarning, + PTBDeprecationWarning( + "20.8", + "The `quote` parameter is deprecated in favor of the `do_quote` parameter. " + "Please update your code to use `do_quote` instead.", + ), stacklevel=2, ) diff --git a/telegram/_passport/passportelementerrors.py b/telegram/_passport/passportelementerrors.py index 0692c98f3..8d6911439 100644 --- a/telegram/_passport/passportelementerrors.py +++ b/telegram/_passport/passportelementerrors.py @@ -210,9 +210,11 @@ class PassportElementErrorFiles(PassportElementError): This attribute will return a tuple instead of a list in future major versions. """ warn( - "The attribute `file_hashes` will return a tuple instead of a list in future major" - " versions.", - PTBDeprecationWarning, + PTBDeprecationWarning( + "20.6", + "The attribute `file_hashes` will return a tuple instead of a list in future major" + " versions.", + ), stacklevel=2, ) return self._file_hashes @@ -427,10 +429,12 @@ class PassportElementErrorTranslationFiles(PassportElementError): This attribute will return a tuple instead of a list in future major versions. """ warn( - "The attribute `file_hashes` will return a tuple instead of a list in future major" - " versions. See the stability policy:" - " https://docs.python-telegram-bot.org/en/stable/stability_policy.html", - PTBDeprecationWarning, + PTBDeprecationWarning( + "20.6", + "The attribute `file_hashes` will return a tuple instead of a list in future major" + " versions. See the stability policy:" + " https://docs.python-telegram-bot.org/en/stable/stability_policy.html", + ), stacklevel=2, ) return self._file_hashes diff --git a/telegram/_passport/passportfile.py b/telegram/_passport/passportfile.py index 12c0f6f04..3c69e9eb5 100644 --- a/telegram/_passport/passportfile.py +++ b/telegram/_passport/passportfile.py @@ -107,9 +107,11 @@ class PassportFile(TelegramObject): This attribute will return a datetime instead of a integer in future major versions. """ warn( - "The attribute `file_date` will return a datetime instead of an integer in future" - " major versions.", - PTBDeprecationWarning, + PTBDeprecationWarning( + "20.6", + "The attribute `file_date` will return a datetime instead of an integer in future" + " major versions.", + ), stacklevel=2, ) return self._file_date diff --git a/telegram/_utils/warnings.py b/telegram/_utils/warnings.py index d81f4e792..f11b6f3cf 100644 --- a/telegram/_utils/warnings.py +++ b/telegram/_utils/warnings.py @@ -26,19 +26,28 @@ Warning: the changelog. """ import warnings -from typing import Type +from typing import Type, Union from telegram.warnings import PTBUserWarning -def warn(message: str, category: Type[Warning] = PTBUserWarning, stacklevel: int = 0) -> None: +def warn( + message: Union[str, PTBUserWarning], + category: Type[Warning] = PTBUserWarning, + stacklevel: int = 0, +) -> None: """ Helper function used as a shortcut for warning with default values. .. versionadded:: 20.0 Args: - message (:obj:`str`): Specify the warnings message to pass to ``warnings.warn()``. + message (:obj:`str` | :obj:`PTBUserWarning`): Specify the warnings message to pass to + ``warnings.warn()``. + + .. versionchanged:: NEXT.VERSION + Now also accepts a :obj:`PTBUserWarning` instance. + category (:obj:`Type[Warning]`, optional): Specify the Warning class to pass to ``warnings.warn()``. Defaults to :class:`telegram.warnings.PTBUserWarning`. stacklevel (:obj:`int`, optional): Specify the stacklevel to pass to ``warnings.warn()``. diff --git a/telegram/_utils/warnings_transition.py b/telegram/_utils/warnings_transition.py index 655450d15..a135ee5e6 100644 --- a/telegram/_utils/warnings_transition.py +++ b/telegram/_utils/warnings_transition.py @@ -23,10 +23,10 @@ inside warnings.py. .. versionadded:: 20.2 """ -from typing import Any, Callable, Type +from typing import Any, Callable, Type, Union from telegram._utils.warnings import warn -from telegram.warnings import PTBDeprecationWarning +from telegram.warnings import PTBDeprecationWarning, PTBUserWarning def build_deprecation_warning_message( @@ -54,8 +54,9 @@ def warn_about_deprecated_arg_return_new_arg( deprecated_arg_name: str, new_arg_name: str, bot_api_version: str, + ptb_version: str, stacklevel: int = 2, - warn_callback: Callable[[str, Type[Warning], int], None] = warn, + warn_callback: Callable[[Union[str, PTBUserWarning], Type[Warning], int], None] = warn, ) -> Any: """A helper function for the transition in API when argument is renamed. @@ -80,10 +81,12 @@ def warn_about_deprecated_arg_return_new_arg( if deprecated_arg: warn_callback( - f"Bot API {bot_api_version} renamed the argument '{deprecated_arg_name}' to " - f"'{new_arg_name}'.", - PTBDeprecationWarning, - stacklevel + 1, + PTBDeprecationWarning( + ptb_version, + f"Bot API {bot_api_version} renamed the argument '{deprecated_arg_name}' to " + f"'{new_arg_name}'.", + ), + stacklevel=stacklevel + 1, # type: ignore[call-arg] ) return deprecated_arg @@ -94,6 +97,7 @@ def warn_about_deprecated_attr_in_property( deprecated_attr_name: str, new_attr_name: str, bot_api_version: str, + ptb_version: str, stacklevel: int = 2, ) -> None: """A helper function for the transition in API when attribute is renamed. Call from properties. @@ -101,8 +105,10 @@ def warn_about_deprecated_attr_in_property( The properties replace deprecated attributes in classes and issue these deprecation warnings. """ warn( - f"Bot API {bot_api_version} renamed the attribute '{deprecated_attr_name}' to " - f"'{new_attr_name}'.", - PTBDeprecationWarning, + PTBDeprecationWarning( + ptb_version, + f"Bot API {bot_api_version} renamed the attribute '{deprecated_attr_name}' to " + f"'{new_attr_name}'.", + ), stacklevel=stacklevel + 1, ) diff --git a/telegram/ext/_application.py b/telegram/ext/_application.py index b18877bc0..68c058f6d 100644 --- a/telegram/ext/_application.py +++ b/telegram/ext/_application.py @@ -857,9 +857,11 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica if (read_timeout, write_timeout, connect_timeout, pool_timeout) != ((DEFAULT_NONE,) * 4): warn( - "Setting timeouts via `Application.run_polling` is deprecated. " - "Please use `ApplicationBuilder.get_updates_*_timeout` instead.", - PTBDeprecationWarning, + PTBDeprecationWarning( + "20.6", + "Setting timeouts via `Application.run_polling` is deprecated. " + "Please use `ApplicationBuilder.get_updates_*_timeout` instead.", + ), stacklevel=2, ) @@ -1183,9 +1185,11 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica # Generator-based coroutines are not supported in Python 3.12+ if sys.version_info < (3, 12) and isinstance(coroutine, Generator): warn( - "Generator-based coroutines are deprecated in create_task and will not work" - " in Python 3.12+", - category=PTBDeprecationWarning, + PTBDeprecationWarning( + "20.4", + "Generator-based coroutines are deprecated in create_task and will not" + " work in Python 3.12+", + ), ) return await asyncio.create_task(coroutine) # If user uses generator in python 3.12+, Exception will happen and we cannot do diff --git a/telegram/ext/_applicationbuilder.py b/telegram/ext/_applicationbuilder.py index 9fa1fe654..2da562799 100644 --- a/telegram/ext/_applicationbuilder.py +++ b/telegram/ext/_applicationbuilder.py @@ -528,9 +528,11 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]): :class:`ApplicationBuilder`: The same builder with the updated argument. """ warn( - "`ApplicationBuilder.proxy_url` is deprecated since version " - "20.7. Use `ApplicationBuilder.proxy` instead.", - PTBDeprecationWarning, + PTBDeprecationWarning( + "20.7", + "`ApplicationBuilder.proxy_url` is deprecated. Use `ApplicationBuilder.proxy` " + "instead.", + ), stacklevel=2, ) return self.proxy(proxy_url) @@ -760,9 +762,11 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]): :class:`ApplicationBuilder`: The same builder with the updated argument. """ warn( - "`ApplicationBuilder.get_updates_proxy_url` is deprecated since version " - "20.7. Use `ApplicationBuilder.get_updates_proxy` instead.", - PTBDeprecationWarning, + PTBDeprecationWarning( + "20.7", + "`ApplicationBuilder.get_updates_proxy_url` is deprecated. Use " + "`ApplicationBuilder.get_updates_proxy` instead.", + ), stacklevel=2, ) return self.get_updates_proxy(get_updates_proxy_url) diff --git a/telegram/ext/_defaults.py b/telegram/ext/_defaults.py index da27fb6eb..82ba75bf9 100644 --- a/telegram/ext/_defaults.py +++ b/telegram/ext/_defaults.py @@ -156,9 +156,11 @@ class Defaults: raise ValueError("`quote` and `do_quote` are mutually exclusive") if disable_web_page_preview is not None: warn( - "`Defaults.disable_web_page_preview` is deprecated. Use " - "`Defaults.link_preview_options` instead.", - category=PTBDeprecationWarning, + PTBDeprecationWarning( + "20.8", + "`Defaults.disable_web_page_preview` is deprecated. Use " + "`Defaults.link_preview_options` instead.", + ), stacklevel=2, ) self._link_preview_options: Optional[LinkPreviewOptions] = LinkPreviewOptions( @@ -169,8 +171,9 @@ class Defaults: if quote is not None: warn( - "`Defaults.quote` is deprecated. Use `Defaults.do_quote` instead.", - category=PTBDeprecationWarning, + PTBDeprecationWarning( + "20.8", "`Defaults.quote` is deprecated. Use `Defaults.do_quote` instead." + ), stacklevel=2, ) self._do_quote: Optional[bool] = quote diff --git a/telegram/ext/_extbot.py b/telegram/ext/_extbot.py index afb4400b0..6cefee43c 100644 --- a/telegram/ext/_extbot.py +++ b/telegram/ext/_extbot.py @@ -263,7 +263,10 @@ class ExtBot(Bot, Generic[RLARGS]): @classmethod def _warn( - cls, message: str, category: Type[Warning] = PTBUserWarning, stacklevel: int = 0 + cls, + message: Union[str, PTBUserWarning], + 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. diff --git a/telegram/request/_baserequest.py b/telegram/request/_baserequest.py index cc8b73706..93024d6c4 100644 --- a/telegram/request/_baserequest.py +++ b/telegram/request/_baserequest.py @@ -318,10 +318,12 @@ class BaseRequest( and isinstance(write_timeout, DefaultValue) ): warn( - f"The `write_timeout` parameter passed to {self.__class__.__name__}.do_request " - "will default to `BaseRequest.DEFAULT_NONE` instead of 20 in future versions " - "for *all* methods of the `Bot` class, including methods sending media.", - PTBDeprecationWarning, + PTBDeprecationWarning( + "20.7", + f"The `write_timeout` parameter passed to {self.__class__.__name__}.do_request" + " will default to `BaseRequest.DEFAULT_NONE` instead of 20 in future versions " + "for *all* methods of the `Bot` class, including methods sending media.", + ), stacklevel=3, ) write_timeout = 20 diff --git a/telegram/request/_httpxrequest.py b/telegram/request/_httpxrequest.py index 626cce830..e98615392 100644 --- a/telegram/request/_httpxrequest.py +++ b/telegram/request/_httpxrequest.py @@ -146,9 +146,9 @@ class HTTPXRequest(BaseRequest): if proxy_url is not None: proxy = proxy_url warn( - "The parameter `proxy_url` is deprecated since version 20.7. Use `proxy` " - "instead.", - PTBDeprecationWarning, + PTBDeprecationWarning( + "20.7", "The parameter `proxy_url` is deprecated. Use `proxy` instead." + ), stacklevel=2, ) diff --git a/telegram/warnings.py b/telegram/warnings.py index 5ff74191a..9eda53954 100644 --- a/telegram/warnings.py +++ b/telegram/warnings.py @@ -54,6 +54,34 @@ class PTBDeprecationWarning(PTBUserWarning, DeprecationWarning): .. versionchanged:: 20.0 Renamed TelegramDeprecationWarning to PTBDeprecationWarning. + + Args: + version (:obj:`str`): The version in which the feature was deprecated. + + .. versionadded:: NEXT.VERSION + message (:obj:`str`): The message to display. + + .. versionadded:: NEXT.VERSION + + Attributes: + version (:obj:`str`): The version in which the feature was deprecated. + + .. versionadded:: NEXT.VERSION + message (:obj:`str`): The message to display. + + .. versionadded:: NEXT.VERSION """ - __slots__ = () + __slots__ = ("message", "version") + + def __init__(self, version: str, message: str) -> None: + self.version: str = version + self.message: str = message + + def __str__(self) -> str: + """Returns a string representation of the warning, using :attr:`message` and + :attr:`version`. + + .. versionadded:: NEXT.VERSION + """ + return f"Deprecated since version {self.version}: {self.message}" diff --git a/tests/test_bot.py b/tests/test_bot.py index 4f1cfeff4..047238907 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -2105,6 +2105,7 @@ class TestBotWithoutRequest: monkeypatch.setattr(bot.request, "post", make_assertion) assert await bot.do_api_request("camel_case") + @pytest.mark.filterwarnings("ignore::telegram.warnings.PTBUserWarning") async def test_do_api_request_media_write_timeout(self, bot, chat_id, monkeypatch): test_flag = None @@ -2143,6 +2144,7 @@ class TestBotWithoutRequest: DEFAULT_NONE, ) + @pytest.mark.filterwarnings("ignore::telegram.warnings.PTBUserWarning") async def test_do_api_request_default_timezone(self, tz_bot, monkeypatch): until = dtm.datetime(2020, 1, 11, 16, 13) until_timestamp = to_timestamp(until, tzinfo=tz_bot.defaults.tzinfo) @@ -4090,7 +4092,7 @@ class TestBotWithRequest: @pytest.mark.parametrize("bot_class", [Bot, ExtBot]) async def test_do_api_request_warning_known_method(self, bot, bot_class): - with pytest.warns(PTBDeprecationWarning, match="Please use 'Bot.get_me'") as record: + with pytest.warns(PTBUserWarning, match="Please use 'Bot.get_me'") as record: await bot_class(bot.token).do_api_request("get_me") assert record[0].filename == __file__, "Wrong stack level!" @@ -4099,6 +4101,7 @@ class TestBotWithRequest: with pytest.raises(EndPointNotFound, match="'unknownEndpoint' not found"): await bot.do_api_request("unknown_endpoint") + @pytest.mark.filterwarnings("ignore::telegram.warnings.PTBUserWarning") async def test_do_api_request_invalid_token(self, bot): # we do not initialize the bot here on purpose b/c that's the case were we actually # do not know for sure if the token is invalid or the method was not found @@ -4113,6 +4116,7 @@ class TestBotWithRequest: ): await Bot(bot.token).do_api_request("unknown_endpoint") + @pytest.mark.filterwarnings("ignore::telegram.warnings.PTBUserWarning") @pytest.mark.parametrize("return_type", [Message, None]) async def test_do_api_request_basic_and_files(self, bot, chat_id, return_type): result = await bot.do_api_request( @@ -4137,6 +4141,7 @@ class TestBotWithRequest: assert out.read() == data_file("telegram.png").open("rb").read() assert result.document.file_name == "telegram.png" + @pytest.mark.filterwarnings("ignore::telegram.warnings.PTBUserWarning") @pytest.mark.parametrize("return_type", [Message, None]) async def test_do_api_request_list_return_type(self, bot, chat_id, return_type): result = await bot.do_api_request( @@ -4175,6 +4180,7 @@ class TestBotWithRequest: assert out.read() == data_file(file_name).open("rb").read() assert message.document.file_name == file_name + @pytest.mark.filterwarnings("ignore::telegram.warnings.PTBUserWarning") @pytest.mark.parametrize("return_type", [Message, None]) async def test_do_api_request_bool_return_type(self, bot, chat_id, return_type): assert await bot.do_api_request("delete_my_commands", return_type=return_type) is True diff --git a/tests/test_warnings.py b/tests/test_warnings.py index 06161d59f..3e3beb48f 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -33,7 +33,7 @@ class TestWarnings: [ (PTBUserWarning("test message")), (PTBRuntimeWarning("test message")), - (PTBDeprecationWarning()), + (PTBDeprecationWarning("20.6", "test message")), ], ) def test_slots_behavior(self, inst): @@ -80,9 +80,8 @@ class TestWarnings: assert str(recwarn[1].message) == "test message 2" assert Path(recwarn[1].filename) == expected_file, "incorrect stacklevel!" - warn("test message 3", stacklevel=1, category=PTBDeprecationWarning) - expected_file = Path(__file__) + warn(PTBDeprecationWarning("20.6", "test message 3"), stacklevel=1) assert len(recwarn) == 3 assert recwarn[2].category is PTBDeprecationWarning - assert str(recwarn[2].message) == "test message 3" - assert Path(recwarn[2].filename) == expected_file, "incorrect stacklevel!" + assert str(recwarn[2].message) == "Deprecated since version 20.6: test message 3" + assert Path(recwarn[2].filename) == Path(__file__), "incorrect stacklevel!"