diff --git a/aiogram/utils/deprecated.py b/aiogram/utils/deprecated.py index 1ea2561d..8a527420 100644 --- a/aiogram/utils/deprecated.py +++ b/aiogram/utils/deprecated.py @@ -1,10 +1,7 @@ -""" -Source: https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically -""" - import functools import inspect import warnings +import asyncio def deprecated(reason): @@ -12,6 +9,8 @@ def deprecated(reason): This is a decorator which can be used to mark functions as deprecated. It will result in a warning being emitted when the function is used. + + Source: https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically """ if isinstance(reason, str): @@ -41,7 +40,7 @@ def deprecated(reason): return decorator - elif inspect.isclass(reason) or inspect.isfunction(reason): + if inspect.isclass(reason) or inspect.isfunction(reason): # The @deprecated is used without any 'reason'. # @@ -65,11 +64,71 @@ def deprecated(reason): return wrapper1 - else: - raise TypeError(repr(type(reason))) + raise TypeError(repr(type(reason))) def warn_deprecated(message, warning=DeprecationWarning, stacklevel=2): warnings.simplefilter('always', warning) warnings.warn(message, category=warning, stacklevel=stacklevel) warnings.simplefilter('default', warning) + + +def renamed_argument(old_name: str, new_name: str, until_version: str, stacklevel: int = 3): + """ + A meta-decorator to mark an argument as deprecated. + + .. code-block:: python3 + + @renamed_argument("chat", "chat_id", "3.0") # stacklevel=3 by default + @renamed_argument("user", "user_id", "3.0", stacklevel=4) + def some_function(user_id, chat_id=None): + print(f"user_id={user_id}, chat_id={chat_id}") + + some_function(user=123) # prints 'user_id=123, chat_id=None' with warning + some_function(123) # prints 'user_id=123, chat_id=None' without warning + some_function(user_id=123) # prints 'user_id=123, chat_id=None' without warning + + + :param old_name: + :param new_name: + :param until_version: the version in which the argument is scheduled to be removed + :param stacklevel: leave it to default if it's the first decorator used. + Increment with any new decorator used. + :return: decorator + """ + + def decorator(func): + if asyncio.iscoroutinefunction(func): + @functools.wraps(func) + async def wrapped(*args, **kwargs): + if old_name in kwargs: + warn_deprecated(f"In coroutine '{func.__name__}' argument '{old_name}' " + f"is renamed to '{new_name}' " + f"and will be removed in aiogram {until_version}", + stacklevel=stacklevel) + kwargs.update( + { + new_name: kwargs[old_name], + } + ) + kwargs.pop(old_name) + await func(*args, **kwargs) + else: + @functools.wraps(func) + def wrapped(*args, **kwargs): + if old_name in kwargs: + warn_deprecated(f"In function `{func.__name__}` argument `{old_name}` " + f"is renamed to `{new_name}` " + f"and will be removed in aiogram {until_version}", + stacklevel=stacklevel) + kwargs.update( + { + new_name: kwargs[old_name], + } + ) + kwargs.pop(old_name) + func(*args, **kwargs) + + return wrapped + + return decorator diff --git a/docs/source/utils/deprecated.rst b/docs/source/utils/deprecated.rst index 0a7b4089..7f2f07cc 100644 --- a/docs/source/utils/deprecated.rst +++ b/docs/source/utils/deprecated.rst @@ -1,4 +1,5 @@ ========== Deprecated ========== -Coming soon... +.. automodule:: aiogram.utils.deprecated + :members: