| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- from inspect import Parameter, signature
- import functools
- import warnings
- from importlib import import_module
- from scipy._lib._docscrape import FunctionDoc
- __all__ = ["_deprecated"]
- # Object to use as default value for arguments to be deprecated. This should
- # be used over 'None' as the user could parse 'None' as a positional argument
- _NoValue = object()
- def _sub_module_deprecation(*, sub_package, module, private_modules, all,
- attribute, correct_module=None, dep_version="1.16.0"):
- """Helper function for deprecating modules that are public but were
- intended to be private.
- Parameters
- ----------
- sub_package : str
- Subpackage the module belongs to eg. stats
- module : str
- Public but intended private module to deprecate
- private_modules : list
- Private replacement(s) for `module`; should contain the
- content of ``all``, possibly spread over several modules.
- all : list
- ``__all__`` belonging to `module`
- attribute : str
- The attribute in `module` being accessed
- correct_module : str, optional
- Module in `sub_package` that `attribute` should be imported from.
- Default is that `attribute` should be imported from ``scipy.sub_package``.
- dep_version : str, optional
- Version in which deprecated attributes will be removed.
- """
- if correct_module is not None:
- correct_import = f"scipy.{sub_package}.{correct_module}"
- else:
- correct_import = f"scipy.{sub_package}"
- if attribute not in all:
- raise AttributeError(
- f"`scipy.{sub_package}.{module}` has no attribute `{attribute}`; "
- f"furthermore, `scipy.{sub_package}.{module}` is deprecated "
- f"and will be removed in SciPy 2.0.0."
- )
- attr = getattr(import_module(correct_import), attribute, None)
- if attr is not None:
- message = (
- f"Please import `{attribute}` from the `{correct_import}` namespace; "
- f"the `scipy.{sub_package}.{module}` namespace is deprecated "
- f"and will be removed in SciPy 2.0.0."
- )
- else:
- message = (
- f"`scipy.{sub_package}.{module}.{attribute}` is deprecated along with "
- f"the `scipy.{sub_package}.{module}` namespace. "
- f"`scipy.{sub_package}.{module}.{attribute}` will be removed "
- f"in SciPy {dep_version}, and the `scipy.{sub_package}.{module}` namespace "
- f"will be removed in SciPy 2.0.0."
- )
- warnings.warn(message, category=DeprecationWarning, stacklevel=3)
- for module in private_modules:
- try:
- return getattr(import_module(f"scipy.{sub_package}.{module}"), attribute)
- except AttributeError as e:
- # still raise an error if the attribute isn't in any of the expected
- # private modules
- if module == private_modules[-1]:
- raise e
- continue
-
- def _deprecated(msg, stacklevel=2):
- """Deprecate a function by emitting a warning on use."""
- def wrap(fun):
- if isinstance(fun, type):
- warnings.warn(
- f"Trying to deprecate class {fun!r}",
- category=RuntimeWarning, stacklevel=2)
- return fun
- @functools.wraps(fun)
- def call(*args, **kwargs):
- warnings.warn(msg, category=DeprecationWarning,
- stacklevel=stacklevel)
- return fun(*args, **kwargs)
- call.__doc__ = fun.__doc__
- return call
- return wrap
- class _DeprecationHelperStr:
- """
- Helper class used by deprecate_cython_api
- """
- def __init__(self, content, message):
- self._content = content
- self._message = message
- def __hash__(self):
- return hash(self._content)
- def __eq__(self, other):
- res = (self._content == other)
- if res:
- warnings.warn(self._message, category=DeprecationWarning,
- stacklevel=2)
- return res
- def deprecate_cython_api(module, routine_name, new_name=None, message=None):
- """
- Deprecate an exported cdef function in a public Cython API module.
- Only functions can be deprecated; typedefs etc. cannot.
- Parameters
- ----------
- module : module
- Public Cython API module (e.g. scipy.linalg.cython_blas).
- routine_name : str
- Name of the routine to deprecate. May also be a fused-type
- routine (in which case its all specializations are deprecated).
- new_name : str
- New name to include in the deprecation warning message
- message : str
- Additional text in the deprecation warning message
- Examples
- --------
- Usually, this function would be used in the top-level of the
- module ``.pyx`` file:
- >>> from scipy._lib.deprecation import deprecate_cython_api
- >>> import scipy.linalg.cython_blas as mod
- >>> deprecate_cython_api(mod, "dgemm", "dgemm_new",
- ... message="Deprecated in Scipy 1.5.0")
- >>> del deprecate_cython_api, mod
- After this, Cython modules that use the deprecated function emit a
- deprecation warning when they are imported.
- """
- old_name = f"{module.__name__}.{routine_name}"
- if new_name is None:
- depdoc = f"`{old_name}` is deprecated!"
- else:
- depdoc = f"`{old_name}` is deprecated, use `{new_name}` instead!"
- if message is not None:
- depdoc += "\n" + message
- d = module.__pyx_capi__
- # Check if the function is a fused-type function with a mangled name
- j = 0
- has_fused = False
- while True:
- fused_name = f"__pyx_fuse_{j}{routine_name}"
- if fused_name in d:
- has_fused = True
- d[_DeprecationHelperStr(fused_name, depdoc)] = d.pop(fused_name)
- j += 1
- else:
- break
- # If not, apply deprecation to the named routine
- if not has_fused:
- d[_DeprecationHelperStr(routine_name, depdoc)] = d.pop(routine_name)
- # taken from scikit-learn, see
- # https://github.com/scikit-learn/scikit-learn/blob/1.3.0/sklearn/utils/validation.py#L38
- def _deprecate_positional_args(func=None, *, version=None,
- deprecated_args=None, custom_message=""):
- """Decorator for methods that issues warnings for positional arguments.
- Using the keyword-only argument syntax in pep 3102, arguments after the
- * will issue a warning when passed as a positional argument.
- Parameters
- ----------
- func : callable, default=None
- Function to check arguments on.
- version : callable, default=None
- The version when positional arguments will result in error.
- deprecated_args : set of str, optional
- Arguments to deprecate - whether passed by position or keyword.
- custom_message : str, optional
- Custom message to add to deprecation warning and documentation.
- """
- if version is None:
- msg = "Need to specify a version where signature will be changed"
- raise ValueError(msg)
- deprecated_args = set() if deprecated_args is None else set(deprecated_args)
- def _inner_deprecate_positional_args(f):
- sig = signature(f)
- kwonly_args = []
- all_args = []
- for name, param in sig.parameters.items():
- if param.kind == Parameter.POSITIONAL_OR_KEYWORD:
- all_args.append(name)
- elif param.kind == Parameter.KEYWORD_ONLY:
- kwonly_args.append(name)
- def warn_deprecated_args(kwargs):
- intersection = deprecated_args.intersection(kwargs)
- if intersection:
- message = (f"Arguments {intersection} are deprecated, whether passed "
- "by position or keyword. They will be removed in SciPy "
- f"{version}. ")
- message += custom_message
- warnings.warn(message, category=DeprecationWarning, stacklevel=3)
- @functools.wraps(f)
- def inner_f(*args, **kwargs):
- extra_args = len(args) - len(all_args)
- if extra_args <= 0:
- warn_deprecated_args(kwargs)
- return f(*args, **kwargs)
- # extra_args > 0
- kwonly_extra_args = set(kwonly_args[:extra_args]) - deprecated_args
- args_msg = ", ".join(kwonly_extra_args)
- warnings.warn(
- (
- f"You are passing as positional arguments: {args_msg}. "
- "Please change your invocation to use keyword arguments. "
- f"From SciPy {version}, passing these as positional "
- "arguments will result in an error."
- ),
- DeprecationWarning,
- stacklevel=2,
- )
- kwargs.update(zip(sig.parameters, args))
- warn_deprecated_args(kwargs)
- return f(**kwargs)
- doc = FunctionDoc(inner_f)
- kwonly_extra_args = set(kwonly_args) - deprecated_args
- admonition = f"""
- .. deprecated:: {version}
- Use of argument(s) ``{kwonly_extra_args}`` by position is deprecated; beginning in
- SciPy {version}, these will be keyword-only. """
- if deprecated_args:
- admonition += (f"Argument(s) ``{deprecated_args}`` are deprecated, whether "
- "passed by position or keyword; they will be removed in "
- f"SciPy {version}. ")
- admonition += custom_message
- doc['Extended Summary'] += [admonition]
- doc = str(doc).split("\n", 1)[1].lstrip(" \n") # remove signature
- inner_f.__doc__ = str(doc)
- return inner_f
- if func is not None:
- return _inner_deprecate_positional_args(func)
- return _inner_deprecate_positional_args
|