From 34269f4e32a758ff5be58e42ae6d9363da38fd2a Mon Sep 17 00:00:00 2001 From: Paillat Date: Sun, 13 Jul 2025 18:15:11 +0200 Subject: [PATCH 1/5] :recycle: update deprecated usage in versioning and interactions to use `typing.deprecated` --- discord/_version.py | 14 ++++++----- discord/interactions.py | 38 +++++++++++++++++++++++++---- discord/utils.py | 53 ++++++++++++++++++++++++++++++++--------- 3 files changed, 83 insertions(+), 22 deletions(-) diff --git a/discord/_version.py b/discord/_version.py index ba68799dfe..dbbebd63ee 100644 --- a/discord/_version.py +++ b/discord/_version.py @@ -36,7 +36,9 @@ from typing import Literal, NamedTuple -from .utils import deprecated +from typing_extensions import deprecated + +from .utils import deprecated_message try: __version__ = version("py-cord") @@ -84,27 +86,27 @@ def advanced(self, value: object) -> None: _advanced = value @property - @deprecated("releaselevel", "2.4") + @deprecated(deprecated_message("release_level", "releaselevel", "2.4")) def release_level(self) -> Literal["alpha", "beta", "candidate", "final"]: return self.releaselevel @property - @deprecated('.advanced["serial"]', "2.4") + @deprecated(deprecated_message("serial", '.advanced["serial"]', "2.4")) def serial(self) -> int: return self.advanced["serial"] @property - @deprecated('.advanced["build"]', "2.4") + @deprecated(deprecated_message("build", '.advanced["build"]', "2.4")) def build(self) -> int | None: return self.advanced["build"] @property - @deprecated('.advanced["commit"]', "2.4") + @deprecated(deprecated_message("commit", '.advanced["commit"]', "2.4")) def commit(self) -> str | None: return self.advanced["commit"] @property - @deprecated('.advanced["date"]', "2.4") + @deprecated(deprecated_message("date", '.advanced["date"]', "2.4")) def date(self) -> datetime.date | None: return self.advanced["date"] diff --git a/discord/interactions.py b/discord/interactions.py index 0834e9bb75..73d58c1e82 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -29,6 +29,8 @@ import datetime from typing import TYPE_CHECKING, Any, Coroutine, Union +from typing_extensions import deprecated + from . import utils from .channel import ChannelType, PartialMessageable, _threaded_channel_factory from .enums import ( @@ -47,6 +49,7 @@ from .object import Object from .permissions import Permissions from .user import User +from .utils import deprecated_message from .webhook.async_ import ( Webhook, WebhookMessage, @@ -315,7 +318,10 @@ def is_component(self) -> bool: return self.type == InteractionType.component @utils.cached_slot_property("_cs_channel") - @utils.deprecated("Interaction.channel", "2.7", stacklevel=4) + @deprecated( + deprecated_message("Interaction.cached_channel", "Interaction.channel", "2.7"), + stacklevel=2, + ) def cached_channel(self) -> InteractionChannel | None: """The cached channel from which the interaction was sent. DM channels are not resolved. These are :class:`PartialMessageable` instead. @@ -457,7 +463,11 @@ async def original_response(self) -> InteractionMessage: self._original_response = message return message - @utils.deprecated("Interaction.original_response", "2.2") + @deprecated( + deprecated_message( + "Interaction.original_message", "Interaction.original_response", "2.2" + ) + ) async def original_message(self): """An alias for :meth:`original_response`. @@ -584,7 +594,13 @@ async def edit_original_response( return message - @utils.deprecated("Interaction.edit_original_response", "2.2") + @deprecated( + deprecated_message( + "Interaction.edit_original_message", + "Interaction.edit_original_response", + "2.2", + ) + ) async def edit_original_message(self, **kwargs): """An alias for :meth:`edit_original_response`. @@ -642,7 +658,13 @@ async def delete_original_response(self, *, delay: float | None = None) -> None: else: await func - @utils.deprecated("Interaction.delete_original_response", "2.2") + @deprecated( + deprecated_message( + "Interaction.delete_original_message", + "Interaction.delete_original_response", + "2.2", + ) + ) async def delete_original_message(self, **kwargs): """An alias for :meth:`delete_original_response`. @@ -1288,7 +1310,13 @@ async def send_modal(self, modal: Modal) -> Interaction: self._parent._state.store_modal(modal, self._parent.user.id) return self._parent - @utils.deprecated("a button with type ButtonType.premium", "2.6") + @deprecated( + deprecated_message( + "InteractionResponse.premium_required", + "a button with type ButtonType.premium", + "2.6", + ) + ) async def premium_required(self) -> Interaction: """|coro| diff --git a/discord/utils.py b/discord/utils.py index b509162cf0..0520b05003 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -75,7 +75,7 @@ __all__ = ( "parse_time", "warn_deprecated", - "deprecated", + "deprecated_message", "oauth_url", "snowflake_time", "time_snowflake", @@ -283,6 +283,40 @@ def decorator(overridden: T) -> T: return decorator +def deprecated_message( + name: str, + instead: str | None = None, + since: str | None = None, + removed: str | None = None, + reference: str | None = None, +) -> str: + """ + Generates a deprecation message, with the ability to specify details about the deprecation. + + Parameters + ---------- + name + instead + since + removed + reference + + Returns + ------- + """ + message = f"{name} is deprecated" + if since: + message += f" since version {since}" + if removed: + message += f" and will be removed in version {removed}" + if instead: + message += f", consider using {instead} instead" + message += "." + if reference: + message += f" See {reference} for more information." + return message + + def warn_deprecated( name: str, instead: str | None = None, @@ -313,16 +347,13 @@ def warn_deprecated( The stacklevel kwarg passed to :func:`warnings.warn`. Defaults to 3. """ warnings.simplefilter("always", DeprecationWarning) # turn off filter - message = f"{name} is deprecated" - if since: - message += f" since version {since}" - if removed: - message += f" and will be removed in version {removed}" - if instead: - message += f", consider using {instead} instead" - message += "." - if reference: - message += f" See {reference} for more information." + message = deprecated_message( + name=name, + instead=instead, + since=since, + removed=removed, + reference=reference, + ) warnings.warn(message, stacklevel=stacklevel, category=DeprecationWarning) warnings.simplefilter("default", DeprecationWarning) # reset filter From 37f5a62d611582b9540fd23ba9e025c17fae9fc7 Mon Sep 17 00:00:00 2001 From: Paillat Date: Sun, 13 Jul 2025 18:25:23 +0200 Subject: [PATCH 2/5] :coffin: remove `utils.deprecated` decorator implementation --- discord/utils.py | 52 ------------------------------------------------ 1 file changed, 52 deletions(-) diff --git a/discord/utils.py b/discord/utils.py index 0520b05003..b73788f8c7 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -29,7 +29,6 @@ import asyncio import collections.abc import datetime -import functools import itertools import json import re @@ -359,57 +358,6 @@ def warn_deprecated( warnings.simplefilter("default", DeprecationWarning) # reset filter -def deprecated( - instead: str | None = None, - since: str | None = None, - removed: str | None = None, - reference: str | None = None, - stacklevel: int = 3, - *, - use_qualname: bool = True, -) -> Callable[[Callable[[P], T]], Callable[[P], T]]: - """A decorator implementation of :func:`warn_deprecated`. This will automatically call :func:`warn_deprecated` when - the decorated function is called. - - Parameters - ---------- - instead: Optional[:class:`str`] - A recommended alternative to the function. - since: Optional[:class:`str`] - The version in which the function was deprecated. This should be in the format ``major.minor(.patch)``, where - the patch version is optional. - removed: Optional[:class:`str`] - The version in which the function is planned to be removed. This should be in the format - ``major.minor(.patch)``, where the patch version is optional. - reference: Optional[:class:`str`] - A reference that explains the deprecation, typically a URL to a page such as a changelog entry or a GitHub - issue/PR. - stacklevel: :class:`int` - The stacklevel kwarg passed to :func:`warnings.warn`. Defaults to 3. - use_qualname: :class:`bool` - Whether to use the qualified name of the function in the deprecation warning. If ``False``, the short name of - the function will be used instead. For example, __qualname__ will display as ``Client.login`` while __name__ - will display as ``login``. Defaults to ``True``. - """ - - def actual_decorator(func: Callable[[P], T]) -> Callable[[P], T]: - @functools.wraps(func) - def decorated(*args: P.args, **kwargs: P.kwargs) -> T: - warn_deprecated( - name=func.__qualname__ if use_qualname else func.__name__, - instead=instead, - since=since, - removed=removed, - reference=reference, - stacklevel=stacklevel, - ) - return func(*args, **kwargs) - - return decorated - - return actual_decorator - - def oauth_url( client_id: int | str, *, From c3a26f7d2f62672400ec74e04c4aef0e5df60381 Mon Sep 17 00:00:00 2001 From: Paillat Date: Sun, 13 Jul 2025 18:25:50 +0200 Subject: [PATCH 3/5] :memo: improve type hints and documentation for deprecation parameters --- discord/utils.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/discord/utils.py b/discord/utils.py index b73788f8c7..c388843e5e 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -294,14 +294,24 @@ def deprecated_message( Parameters ---------- - name - instead - since - removed - reference + name: str + The name of the deprecated function. + instead: Optional[:class:`str`] + A recommended alternative to the function. + since: Optional[:class:`str`] + The version in which the function was deprecated. This should be in the format ``major.minor(.patch)``, where + the patch version is optional. + removed: Optional[:class:`str`] + The version in which the function is planned to be removed. This should be in the format + ``major.minor(.patch)``, where the patch version is optional. + reference: Optional[:class:`str`] + A reference that explains the deprecation, typically a URL to a page such as a changelog entry or a GitHub + issue/PR. Returns ------- + :class:`str` + The deprecation message. """ message = f"{name} is deprecated" if since: From 24a304088e0fc17cde758f21a544b5d3477dc4e0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 08:38:39 +0000 Subject: [PATCH 4/5] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/discord/utils.py b/discord/utils.py index bd3e746aac..c406f54856 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -29,7 +29,6 @@ import asyncio import collections.abc import datetime -import functools import importlib.resources import itertools import json From 04aa2f641f04dd1b75b99358218ad1f8e4babc5c Mon Sep 17 00:00:00 2001 From: Paillat Date: Wed, 6 Aug 2025 10:44:59 +0200 Subject: [PATCH 5/5] Stuff broke in merge commit --- discord/role.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/discord/role.py b/discord/role.py index 60016f1aec..9bed278068 100644 --- a/discord/role.py +++ b/discord/role.py @@ -27,7 +27,7 @@ from typing import TYPE_CHECKING, Any, TypeVar -from typing_extensions import Self +from typing_extensions import Self, deprecated from .asset import Asset from .colour import Colour @@ -39,7 +39,7 @@ MISSING, _bytes_to_base64_data, _get_as_snowflake, - deprecated, + deprecated_message, snowflake_time, warn_deprecated, ) @@ -478,7 +478,7 @@ def permissions(self) -> Permissions: return Permissions(self._permissions) @property - @deprecated("colours.primary", "2.7") + @deprecated(deprecated_message("colours.primary", "2.7")) def colour(self) -> Colour: """Returns the role colour. Equivalent to :attr:`colours.primary`. An alias exists under ``color``. @@ -488,7 +488,7 @@ def colour(self) -> Colour: return self.colours.primary @property - @deprecated("colors.primary", "2.7") + @deprecated(deprecated_message("colors.primary", "2.7")) def color(self) -> Colour: """Returns the role's primary color. Equivalent to :attr:`colors.primary`. An alias exists under ``colour``.