Source code for fury.deprecator

"""Function for recording and reporting deprecations.

Note
-----
this file is copied (with minor modifications) from the Nibabel.
https://github.com/nipy/nibabel. See COPYING file distributed along with
the Nibabel package for the copyright and license terms.

"""

import functools
import warnings
import re
from fury import __version__
from fury.optpkg import optional_package
# packaging.version.parse is a third-party utility but is used by setuptools
# (so probably already installed) and is conformant to the current PEP 440.
# But just if it is not the case, we use distutils
packaging, have_pkg, _ = optional_package('setuptools.extern.packaging')

_LEADING_WHITE = re.compile(r'^(\s*)')


[docs]class ExpiredDeprecationError(RuntimeError): """Error for expired deprecation. Error raised when a called function or method has passed out of its deprecation period. """ pass
def _ensure_cr(text): """Remove trailing whitespace and add carriage return. Ensures that `text` always ends with a carriage return """ return text.rstrip() + '\n' def _add_dep_doc(old_doc, dep_doc): """Add deprecation message `dep_doc` to docstring in `old_doc`. Parameters ---------- old_doc : str Docstring from some object. dep_doc : str Deprecation warning to add to top of docstring, after initial line. Returns ------- new_doc : str `old_doc` with `dep_doc` inserted after any first lines of docstring. """ dep_doc = _ensure_cr(dep_doc) if not old_doc: return dep_doc old_doc = _ensure_cr(old_doc) old_lines = old_doc.splitlines() new_lines = [] for line_no, line in enumerate(old_lines): if line.strip(): new_lines.append(line) else: break next_line = line_no + 1 if next_line >= len(old_lines): # nothing following first paragraph, just append message return old_doc + '\n' + dep_doc indent = _LEADING_WHITE.match(old_lines[next_line]).group() dep_lines = [indent + L for L in [''] + dep_doc.splitlines() + ['']] return '\n'.join(new_lines + dep_lines + old_lines[next_line:]) + '\n'
[docs]def cmp_pkg_version(version_str, pkg_version_str=__version__): """Compare `version_str` to current package version. Parameters ---------- version_str : str Version string to compare to current package version pkg_version_str : str, optional Version of our package. Optional, set fom ``__version__`` by default. Returns ------- version_cmp : int 1 if `version_str` is a later version than `pkg_version_str`, 0 if same, -1 if earlier. Examples -------- >>> cmp_pkg_version('1.2.1', '1.2.0') 1 >>> cmp_pkg_version('1.2.0dev', '1.2.0') -1 """ version_cmp = packaging.version.parse if have_pkg else None if any([re.match(r'^[a-z, A-Z]', v)for v in [version_str, pkg_version_str]]): msg = 'Invalid version {0} or {1}'.format(version_str, pkg_version_str) raise ValueError(msg) elif version_cmp(version_str) > version_cmp(pkg_version_str): return 1 elif version_cmp(version_str) == version_cmp(pkg_version_str): return 0 else: return -1
[docs]def deprecate_with_version(message, since='', until='', version_comparator=cmp_pkg_version, warn_class=DeprecationWarning, error_class=ExpiredDeprecationError): """Return decorator function function for deprecation warning / error. The decorated function / method will: * Raise the given `warning_class` warning when the function / method gets called, up to (and including) version `until` (if specified); * Raise the given `error_class` error when the function / method gets called, when the package version is greater than version `until` (if specified). Parameters ---------- message : str Message explaining deprecation, giving possible alternatives. since : str, optional Released version at which object was first deprecated. until : str, optional Last released version at which this function will still raise a deprecation warning. Versions higher than this will raise an error. version_comparator : callable Callable accepting string as argument, and return 1 if string represents a higher version than encoded in the `version_comparator`, 0 if the version is equal, and -1 if the version is lower. For example, the `version_comparator` may compare the input version string to the current package version string. warn_class : class, optional Class of warning to generate for deprecation. error_class : class, optional Class of error to generate when `version_comparator` returns 1 for a given argument of ``until``. Returns ------- deprecator : func Function returning a decorator. """ def is_bad_version(version_str): """Return True if `version_str` is too high.""" return version_comparator(version_str) == -1 messages = [message] if (since, until) != ('', ''): messages.append('') if since: messages.append('* deprecated from version: ' + since) if until: messages.append('* {0} {1} as of version: {2}'.format( "Raises" if is_bad_version(until) else "Will raise", error_class, until)) message = '\n'.join(messages) def deprecator(func): @functools.wraps(func) def deprecated_func(*args, **kwargs): if until and is_bad_version(until): raise error_class(message) warnings.warn(message, warn_class, stacklevel=2) return func(*args, **kwargs) deprecated_func.__doc__ = _add_dep_doc(deprecated_func.__doc__, message) return deprecated_func return deprecator