deprecation.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import inspect
  2. import logging
  3. from functools import wraps
  4. from typing import Any, Callable, Optional, TypeVar, Union, cast, overload
  5. from ray.util import log_once
  6. from ray.util.annotations import _mark_annotated
  7. # TypeVar for preserving function/class signatures through decorators.
  8. # Note: These decorators also accept properties, but we use Callable for the
  9. # common case. Properties work at runtime but won't get full type inference.
  10. F = TypeVar("F", bound=Callable[..., Any])
  11. logger = logging.getLogger(__name__)
  12. # A constant to use for any configuration that should be deprecated
  13. # (to check, whether this config has actually been assigned a proper value or
  14. # not).
  15. DEPRECATED_VALUE = -1
  16. def deprecation_warning(
  17. old: str,
  18. new: Optional[str] = None,
  19. *,
  20. help: Optional[str] = None,
  21. error: Optional[Union[bool, type[Exception]]] = None,
  22. stacklevel: int = 2,
  23. ) -> None:
  24. """Warns (via the `logger` object) or throws a deprecation warning/error.
  25. Args:
  26. old: A description of the "thing" that is to be deprecated.
  27. new: A description of the new "thing" that replaces it.
  28. help: An optional help text to tell the user, what to
  29. do instead of using `old`.
  30. error: Whether or which exception to raise. If True, raise ValueError.
  31. If False, just warn. If `error` is-a subclass of Exception,
  32. raise that Exception.
  33. stacklevel: The stacklevel to use for the warning message.
  34. Use 2 to point to where this function is called, 3+ to point
  35. further up the stack.
  36. Raises:
  37. ValueError: If `error=True`.
  38. Exception: Of type `error`, iff `error` is a sub-class of `Exception`.
  39. """
  40. msg = "`{}` has been deprecated.{}".format(
  41. old, (" Use `{}` instead.".format(new) if new else f" {help}" if help else "")
  42. )
  43. if error:
  44. if not isinstance(error, bool) and issubclass(error, Exception):
  45. # error is an Exception
  46. raise error(msg)
  47. else:
  48. # error is a boolean, construct ValueError ourselves
  49. raise ValueError(msg)
  50. else:
  51. logger.warning(
  52. "DeprecationWarning: " + msg + " This will raise an error in the future!",
  53. stacklevel=stacklevel,
  54. )
  55. @overload
  56. def Deprecated(
  57. old: None = None,
  58. *,
  59. new: Optional[str] = None,
  60. help: Optional[str] = None,
  61. error: Union[bool, type[Exception]],
  62. ) -> Callable[[F], F]:
  63. ...
  64. @overload
  65. def Deprecated(
  66. old: str,
  67. *,
  68. new: Optional[str] = None,
  69. help: Optional[str] = None,
  70. error: Union[bool, type[Exception]],
  71. ) -> Callable[[F], F]:
  72. ...
  73. def Deprecated(
  74. old: Optional[str] = None,
  75. *,
  76. new: Optional[str] = None,
  77. help: Optional[str] = None,
  78. error: Union[bool, type[Exception]],
  79. ) -> Callable[[F], F]:
  80. """Decorator for documenting a deprecated class, method, or function.
  81. Automatically adds a `deprecation.deprecation_warning(old=...,
  82. error=False)` to not break existing code at this point to the decorated
  83. class' constructor, method, or function.
  84. In a next major release, this warning should then be made an error
  85. (by setting error=True), which means at this point that the
  86. class/method/function is no longer supported, but will still inform
  87. the user about the deprecation event.
  88. In a further major release, the class, method, function should be erased
  89. entirely from the codebase.
  90. .. testcode::
  91. :skipif: True
  92. from ray._common.deprecation import Deprecated
  93. # Deprecated class: Patches the constructor to warn if the class is
  94. # used.
  95. @Deprecated(new="NewAndMuchCoolerClass", error=False)
  96. class OldAndUncoolClass:
  97. ...
  98. # Deprecated class method: Patches the method to warn if called.
  99. class StillCoolClass:
  100. ...
  101. @Deprecated(new="StillCoolClass.new_and_much_cooler_method()",
  102. error=False)
  103. def old_and_uncool_method(self, uncool_arg):
  104. ...
  105. # Deprecated function: Patches the function to warn if called.
  106. @Deprecated(new="new_and_much_cooler_function", error=False)
  107. def old_and_uncool_function(*uncool_args):
  108. ...
  109. """
  110. def _inner(obj: F) -> F:
  111. # A deprecated class.
  112. if inspect.isclass(obj):
  113. # Patch the class' init method to raise the warning/error.
  114. obj_init = obj.__init__
  115. def patched_init(*args, **kwargs):
  116. if log_once(old or obj.__name__):
  117. deprecation_warning(
  118. old=old or obj.__name__,
  119. new=new,
  120. help=help,
  121. error=error,
  122. stacklevel=3,
  123. )
  124. return obj_init(*args, **kwargs)
  125. obj.__init__ = patched_init
  126. _mark_annotated(obj)
  127. # Return the patched class (with the warning/error when
  128. # instantiated).
  129. return obj
  130. # A deprecated class method or function.
  131. # Patch with the warning/error at the beginning.
  132. def _ctor(*args, **kwargs):
  133. if log_once(old or obj.__name__):
  134. deprecation_warning(
  135. old=old or obj.__name__,
  136. new=new,
  137. help=help,
  138. error=error,
  139. stacklevel=3,
  140. )
  141. # Call the deprecated method/function.
  142. return obj(*args, **kwargs)
  143. # Only apply @wraps for actual callables, not properties/descriptors.
  144. # Setting __wrapped__ on a property causes inspect.unwrap() to return
  145. # the property, which breaks inspect.signature() in the tracing helper.
  146. if callable(obj):
  147. _ctor = wraps(obj)(_ctor)
  148. # Return the patched class method/function.
  149. return cast(F, _ctor)
  150. # Return the prepared decorator.
  151. return _inner