_deprecation.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import warnings
  2. from collections.abc import Iterable
  3. from functools import wraps
  4. from inspect import Parameter, signature
  5. def _deprecate_positional_args(*, version: str):
  6. """Decorator for methods that issues warnings for positional arguments.
  7. Using the keyword-only argument syntax in pep 3102, arguments after the
  8. * will issue a warning when passed as a positional argument.
  9. Args:
  10. version (`str`):
  11. The version when positional arguments will result in error.
  12. """
  13. def _inner_deprecate_positional_args(f):
  14. sig = signature(f)
  15. kwonly_args = []
  16. all_args = []
  17. for name, param in sig.parameters.items():
  18. if param.kind == Parameter.POSITIONAL_OR_KEYWORD:
  19. all_args.append(name)
  20. elif param.kind == Parameter.KEYWORD_ONLY:
  21. kwonly_args.append(name)
  22. @wraps(f)
  23. def inner_f(*args, **kwargs):
  24. extra_args = len(args) - len(all_args)
  25. if extra_args <= 0:
  26. return f(*args, **kwargs)
  27. # extra_args > 0
  28. args_msg = [
  29. f"{name}='{arg}'" if isinstance(arg, str) else f"{name}={arg}"
  30. for name, arg in zip(kwonly_args[:extra_args], args[-extra_args:])
  31. ]
  32. args_msg = ", ".join(args_msg)
  33. warnings.warn(
  34. f"Deprecated positional argument(s) used in '{f.__name__}': pass"
  35. f" {args_msg} as keyword args. From version {version} passing these"
  36. " as positional arguments will result in an error,",
  37. FutureWarning,
  38. )
  39. kwargs.update(zip(sig.parameters, args))
  40. return f(**kwargs)
  41. return inner_f
  42. return _inner_deprecate_positional_args
  43. def _deprecate_arguments(
  44. *,
  45. version: str,
  46. deprecated_args: Iterable[str],
  47. custom_message: str | None = None,
  48. ):
  49. """Decorator to issue warnings when using deprecated arguments.
  50. TODO: could be useful to be able to set a custom error message.
  51. Args:
  52. version (`str`):
  53. The version when deprecated arguments will result in error.
  54. deprecated_args (`list[str]`):
  55. List of the arguments to be deprecated.
  56. custom_message (`str`, *optional*):
  57. Warning message that is raised. If not passed, a default warning message
  58. will be created.
  59. """
  60. def _inner_deprecate_positional_args(f):
  61. sig = signature(f)
  62. @wraps(f)
  63. def inner_f(*args, **kwargs):
  64. # Check for used deprecated arguments
  65. used_deprecated_args = []
  66. for _, parameter in zip(args, sig.parameters.values()):
  67. if parameter.name in deprecated_args:
  68. used_deprecated_args.append(parameter.name)
  69. for kwarg_name, kwarg_value in kwargs.items():
  70. if (
  71. # If argument is deprecated but still used
  72. kwarg_name in deprecated_args
  73. # And then the value is not the default value
  74. and kwarg_value != sig.parameters[kwarg_name].default
  75. ):
  76. used_deprecated_args.append(kwarg_name)
  77. # Warn and proceed
  78. if len(used_deprecated_args) > 0:
  79. message = (
  80. f"Deprecated argument(s) used in '{f.__name__}':"
  81. f" {', '.join(used_deprecated_args)}. Will not be supported from"
  82. f" version '{version}'."
  83. )
  84. if custom_message is not None:
  85. message += "\n\n" + custom_message
  86. warnings.warn(message, FutureWarning)
  87. return f(*args, **kwargs)
  88. return inner_f
  89. return _inner_deprecate_positional_args
  90. def _deprecate_method(*, version: str, message: str | None = None):
  91. """Decorator to issue warnings when using a deprecated method.
  92. Args:
  93. version (`str`):
  94. The version when deprecated arguments will result in error.
  95. message (`str`, *optional*):
  96. Warning message that is raised. If not passed, a default warning message
  97. will be created.
  98. """
  99. def _inner_deprecate_method(f):
  100. name = f.__name__
  101. if name == "__init__":
  102. name = f.__qualname__.split(".")[0] # class name instead of method name
  103. @wraps(f)
  104. def inner_f(*args, **kwargs):
  105. warning_message = (
  106. f"'{name}' (from '{f.__module__}') is deprecated and will be removed from version '{version}'."
  107. )
  108. if message is not None:
  109. warning_message += " " + message
  110. warnings.warn(warning_message, FutureWarning)
  111. return f(*args, **kwargs)
  112. return inner_f
  113. return _inner_deprecate_method