from __future__ import annotations
import functools
import textwrap
from typing import Any
from typing import TYPE_CHECKING
from typing import TypeVar
import warnings
from packaging import version
from optuna._experimental import _get_docstring_indent
from optuna._experimental import _validate_version
if TYPE_CHECKING:
from collections.abc import Callable
from typing_extensions import ParamSpec
FT = TypeVar("FT")
FP = ParamSpec("FP")
CT = TypeVar("CT")
_DEPRECATION_NOTE_TEMPLATE = """
.. warning::
Deprecated in v{d_ver}. This feature will be removed in the future. The removal of this
feature is currently scheduled for v{r_ver}, but this schedule is subject to change.
See https://github.com/optuna/optuna/releases/tag/v{d_ver}.
"""
_DEPRECATION_WARNING_TEMPLATE = (
"{name} has been deprecated in v{d_ver}. "
"This feature will be removed in v{r_ver}. "
"See https://github.com/optuna/optuna/releases/tag/v{d_ver}."
)
def _validate_two_version(old_version: str, new_version: str) -> None:
if version.parse(old_version) > version.parse(new_version):
raise ValueError(
"Invalid version relationship. The deprecated version must be smaller than "
"the removed version, but (deprecated version, removed version) = ({}, {}) are "
"specified.".format(old_version, new_version)
)
def _format_text(text: str) -> str:
return "\n\n" + textwrap.indent(text.strip(), " ") + "\n"
def deprecated_func(
deprecated_version: str,
removed_version: str,
name: str | None = None,
text: str | None = None,
) -> "Callable[[Callable[FP, FT]], Callable[FP, FT]]":
"""Decorate function as deprecated.
Args:
deprecated_version:
The version in which the target feature is deprecated.
removed_version:
The version in which the target feature will be removed.
name:
The name of the feature. Defaults to the function name. Optional.
text:
The additional text for the deprecation note. The default note is build using specified
``deprecated_version`` and ``removed_version``. If you want to provide additional
information, please specify this argument yourself.
.. note::
The default deprecation note is as follows: "Deprecated in v{d_ver}. This feature
will be removed in the future. The removal of this feature is currently scheduled
for v{r_ver}, but this schedule is subject to change. See
https://github.com/optuna/optuna/releases/tag/v{d_ver}."
.. note::
The specified text is concatenated after the default deprecation note.
"""
_validate_version(deprecated_version)
_validate_version(removed_version)
_validate_two_version(deprecated_version, removed_version)
def decorator(func: "Callable[FP, FT]") -> "Callable[FP, FT]":
if func.__doc__ is None:
func.__doc__ = ""
note = _DEPRECATION_NOTE_TEMPLATE.format(d_ver=deprecated_version, r_ver=removed_version)
if text is not None:
note += _format_text(text)
indent = _get_docstring_indent(func.__doc__)
func.__doc__ = func.__doc__.strip() + textwrap.indent(note, indent) + indent
@functools.wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> "FT":
"""Decorates a function as deprecated.
This decorator is supposed to be applied to the deprecated function.
"""
message = _DEPRECATION_WARNING_TEMPLATE.format(
name=(name if name is not None else func.__name__),
d_ver=deprecated_version,
r_ver=removed_version,
)
if text is not None:
message += " " + text
warnings.warn(message, FutureWarning, stacklevel=2)
return func(*args, **kwargs)
return wrapper
return decorator
def deprecated_class(
deprecated_version: str,
removed_version: str,
name: str | None = None,
text: str | None = None,
) -> "Callable[[CT], CT]":
"""Decorate class as deprecated.
Args:
deprecated_version:
The version in which the target feature is deprecated.
removed_version:
The version in which the target feature will be removed.
name:
The name of the feature. Defaults to the class name. Optional.
text:
The additional text for the deprecation note. The default note is build using specified
``deprecated_version`` and ``removed_version``. If you want to provide additional
information, please specify this argument yourself.
.. note::
The default deprecation note is as follows: "Deprecated in v{d_ver}. This feature
will be removed in the future. The removal of this feature is currently scheduled
for v{r_ver}, but this schedule is subject to change. See
https://github.com/optuna/optuna/releases/tag/v{d_ver}."
.. note::
The specified text is concatenated after the default deprecation note.
"""
_validate_version(deprecated_version)
_validate_version(removed_version)
_validate_two_version(deprecated_version, removed_version)
def decorator(cls: "CT") -> "CT":
def wrapper(cls: "CT") -> "CT":
"""Decorates a class as deprecated.
This decorator is supposed to be applied to the deprecated class.
"""
_original_init = getattr(cls, "__init__")
_original_name = getattr(cls, "__name__")
@functools.wraps(_original_init)
def wrapped_init(self: Any, *args: Any, **kwargs: Any) -> None:
message = _DEPRECATION_WARNING_TEMPLATE.format(
name=(name if name is not None else _original_name),
d_ver=deprecated_version,
r_ver=removed_version,
)
if text is not None:
message += " " + text
warnings.warn(
message,
FutureWarning,
stacklevel=2,
)
_original_init(self, *args, **kwargs)
setattr(cls, "__init__", wrapped_init)
if cls.__doc__ is None:
cls.__doc__ = ""
note = _DEPRECATION_NOTE_TEMPLATE.format(
d_ver=deprecated_version, r_ver=removed_version
)
if text is not None:
note += _format_text(text)
indent = _get_docstring_indent(cls.__doc__)
cls.__doc__ = cls.__doc__.strip() + textwrap.indent(note, indent) + indent
return cls
return wrapper(cls)
return decorator