|
from inspect import Parameter, signature |
|
import functools |
|
import warnings |
|
from importlib import import_module |
|
from scipy._lib._docscrape import FunctionDoc |
|
|
|
|
|
__all__ = ["_deprecated"] |
|
|
|
|
|
|
|
|
|
_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: |
|
|
|
|
|
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__ |
|
|
|
|
|
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 has_fused: |
|
d[_DeprecationHelperStr(routine_name, depdoc)] = d.pop(routine_name) |
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
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] |
|
inner_f.__doc__ = str(doc) |
|
|
|
return inner_f |
|
|
|
if func is not None: |
|
return _inner_deprecate_positional_args(func) |
|
|
|
return _inner_deprecate_positional_args |
|
|