| 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 |
|
|