Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion nibabel/arraywriters.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ def _check_nan2zero(self, nan2zero):
raise WriterError('Deprecated `nan2zero` argument to `to_fileobj` '
'must be same as class value set in __init__')
warnings.warn('Please remove `nan2zero` from call to ' '`to_fileobj` '
'and use in instance __init__ instead',
'and use in instance __init__ instead.\n'
'* deprecated in version: 2.0\n'
'* will raise error in version: 4.0\n',
DeprecationWarning, stacklevel=3)

def _needs_nan2zero(self):
Expand Down
6 changes: 0 additions & 6 deletions nibabel/cifti2/tests/test_cifti2.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,9 @@
import numpy as np

from nibabel import cifti2 as ci
from nibabel.nifti1 import data_type_codes, intent_codes
from nibabel.nifti2 import Nifti2Header

from nibabel import cifti2 as ci
from nibabel.cifti2.cifti2 import _float_01

from numpy.testing import (assert_array_almost_equal,
assert_array_equal)

from nose.tools import assert_true, assert_equal, assert_raises, assert_is_none

from nibabel.tests.test_dataobj_images import TestDataobjAPI as _TDA
Expand Down
19 changes: 8 additions & 11 deletions nibabel/dataobj_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@
* has an attribute or property ``shape``.
"""

import warnings

import numpy as np

from .filebasedimages import FileBasedImage
from .deprecated import deprecate_with_version


class DataobjImage(FileBasedImage):
Expand Down Expand Up @@ -49,11 +48,10 @@ def dataobj(self):
return self._dataobj

@property
@deprecate_with_version('_data attribute not part of public API. '
'please use "dataobj" property instead.',
'2.0', '4.0')
def _data(self):
warnings.warn('Please use ``dataobj`` instead of ``_data``; '
'We will remove this wrapper for ``_data`` soon',
FutureWarning,
stacklevel=2)
return self._dataobj

def get_data(self, caching='fill'):
Expand Down Expand Up @@ -231,12 +229,11 @@ def uncache(self):
def shape(self):
return self._dataobj.shape

@deprecate_with_version('get_shape method is deprecated.\n'
'Please use the ``img.shape`` property '
'instead.',
'1.2', '3.0')
def get_shape(self):
""" Return shape for image

This function deprecated; please use the ``shape`` property instead
"""
warnings.warn('Please use the shape property instead of get_shape',
DeprecationWarning,
stacklevel=2)
return self.shape
20 changes: 19 additions & 1 deletion nibabel/deprecated.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
""" Module to help with deprecating classes and modules
""" Module to help with deprecating objects and classes
"""

import warnings

from .deprecator import Deprecator
from .info import cmp_pkg_version


class ModuleProxy(object):
""" Proxy for module that may not yet have been imported
Expand Down Expand Up @@ -63,3 +66,18 @@ def __init__(self, *args, **kwargs):
FutureWarning,
stacklevel=2)
super(FutureWarningMixin, self).__init__(*args, **kwargs)


class VisibleDeprecationWarning(UserWarning):
""" Deprecation warning that will be shown by default

Python >= 2.7 does not show standard DeprecationWarnings by default:

http://docs.python.org/dev/whatsnew/2.7.html#the-future-for-python-2-x

Use this class for cases where we do want to show deprecations by default.
"""
pass


deprecate_with_version = Deprecator(cmp_pkg_version)
168 changes: 168 additions & 0 deletions nibabel/deprecator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
""" Class for recording and reporting deprecations
"""

import functools
import warnings
import re

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


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'


class Deprecator(object):
""" Class to make decorator marking function or method as deprecated

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
----------
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`` in the ``__call__`` method (see below).
"""

def __init__(self,
version_comparator,
warn_class=DeprecationWarning,
error_class=ExpiredDeprecationError):
self.version_comparator = version_comparator
self.warn_class = warn_class
self.error_class = error_class

def is_bad_version(self, version_str):
""" Return True if `version_str` is too high

Tests `version_str` with ``self.version_comparator``

Parameters
----------
version_str : str
String giving version to test

Returns
-------
is_bad : bool
True if `version_str` is for version below that expected by
``self.version_comparator``, False otherwise.
"""
return self.version_comparator(version_str) == -1

def __call__(self, message, since='', until='',
warn_class=None, error_class=None):
""" Return decorator function function for deprecation warning / error

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.
warn_class : None or class, optional
Class of warning to generate for deprecation (overrides instance
default).
error_class : None or class, optional
Class of error to generate when `version_comparator` returns 1 for a
given argument of ``until`` (overrides class default).

Returns
-------
deprecator : func
Function returning a decorator.
"""
warn_class = warn_class if warn_class else self.warn_class
error_class = error_class if error_class else self.error_class
messages = [message]
if (since, until) != ('', ''):
messages.append('')
if since:
messages.append('* deprecated from version: ' + since)
if until:
messages.append('* {} {} as of version: {}'.format(
"Raises" if self.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 self.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
5 changes: 5 additions & 0 deletions nibabel/ecat.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
from .arraywriters import make_array_writer
from .wrapstruct import WrapStruct
from .fileslice import canonical_slicers, predict_shape, slice2outax
from .deprecated import deprecate_with_version

BLOCK_SIZE = 512

Expand Down Expand Up @@ -846,6 +847,10 @@ def get_subheaders(self):
return self._subheader

@classmethod
@deprecate_with_version('from_filespec class method is deprecated.\n'
'Please use the ``from_file_map`` class method '
'instead.',
'2.1', '4.0')
def from_filespec(klass, filespec):
return klass.from_filename(filespec)

Expand Down
Loading