Skip to content

Commit b2fc79c

Browse files
committed
Merge pull request #480 from satra/enh/deprecation
enh: added deprecation related metadata to traitedspec
2 parents c0d2404 + 5d346b8 commit b2fc79c

File tree

4 files changed

+91
-3
lines changed

4 files changed

+91
-3
lines changed

CHANGES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Next release
66
* ENH: New examples: how to use ANTS template building workflows (smri_ants_build_tmeplate),
77
how to set SGE specific options (smri_ants_build_template_new)
88
* ENH: added no_flatten option to Merge
9+
* ENH: added deprecation metadata to traits
910

1011
Release 0.6.0 (Jun 30, 2012)
1112
============================

doc/devel/interface_specs.rst

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,37 @@ Common
262262
can be set to either `True` or `False`. `False` indicates that contents
263263
should be symlinked, while `True` indicates that the contents should be
264264
copied over.
265-
265+
266+
``deprecated``
267+
This is metadata for removing or renaming an input field from a spec.::
268+
269+
class RealignInputSpec(BaseInterfaceInputSpec):
270+
jobtype = traits.Enum('estwrite', 'estimate', 'write',
271+
deprecated='0.8',
272+
desc='one of: estimate, write, estwrite',
273+
usedefault=True)
274+
275+
In the above example this means that the `jobtype` input is deprecated and
276+
will be removed in version 0.8. Deprecation should be set to two versions
277+
from current release. Raises `TraitError` after package version crosses the
278+
deprecation version.
279+
280+
``new_name``
281+
For inputs that are being renamed, one can specify the new name of the field
282+
283+
class RealignInputSpec(BaseInterfaceInputSpec):
284+
jobtype = traits.Enum('estwrite', 'estimate', 'write',
285+
deprecated='0.8', new_name='job_type',
286+
desc='one of: estimate, write, estwrite',
287+
usedefault=True)
288+
job_type = traits.Enum('estwrite', 'estimate', 'write',
289+
desc='one of: estimate, write, estwrite',
290+
usedefault=True)
291+
292+
In the above example, the `jobtype` field is being renamed to `job_type`.
293+
When `new_name` is provided it must exist as a trait, otherwise an exception
294+
will be raised.
295+
266296
CommandLine
267297
^^^^^^^^^^^
268298

nipype/interfaces/base.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@
2828
from ..utils.filemanip import (md5, hash_infile, FileNotFoundError,
2929
hash_timestamp)
3030
from ..utils.misc import is_container, trim
31-
from .. import config, logging
31+
from .. import config, logging, LooseVersion
32+
from .. import __version__
33+
34+
nipype_version = LooseVersion(__version__)
3235

3336
iflogger = logging.getLogger('interface')
3437

@@ -326,6 +329,10 @@ def _generate_handlers(self):
326329
requires = self.trait_names(**has_requires)
327330
for elem in requires:
328331
self.on_trait_change(self._requires_warn, elem)
332+
has_deprecation = dict(deprecated=lambda t: t is not None)
333+
deprecated = self.trait_names(**has_deprecation)
334+
for elem in deprecated:
335+
self.on_trait_change(self._deprecated_warn, elem)
329336

330337
def _xor_warn(self, obj, name, old, new):
331338
""" Generates warnings for xor traits
@@ -347,7 +354,7 @@ def _xor_warn(self, obj, name, old, new):
347354
def _requires_warn(self, obj, name, old, new):
348355
"""Part of the xor behavior
349356
"""
350-
if new:
357+
if isdefined(new):
351358
trait_spec = self.traits()[name]
352359
msg = None
353360
for trait_name in trait_spec.requires:
@@ -358,6 +365,30 @@ def _requires_warn(self, obj, name, old, new):
358365
if msg:
359366
warn(msg)
360367

368+
def _deprecated_warn(self, obj, name, old, new):
369+
"""Checks if a user assigns a value to a deprecated trait
370+
"""
371+
if isdefined(new):
372+
trait_spec = self.traits()[name]
373+
msg1 = ('Input %s in interface %s is deprecated.') % (name,
374+
self.__class__.__name__.split('InputSpec')[0])
375+
msg2 = ('Will be removed or raise an error as of release %s') % \
376+
trait_spec.deprecated
377+
self.trait_set(trait_change_notify=False, **{'%s' % name: Undefined})
378+
if trait_spec.new_name:
379+
if trait_spec.new_name not in self.copyable_trait_names():
380+
raise TraitError(msg1 + ' Replacement trait %s not found' %
381+
trait_spec.new_name)
382+
msg3 = 'It has been replaced by %s.' % trait_spec.new_name
383+
else:
384+
msg3 = ''
385+
msg = ' '.join((msg1, msg2, msg3))
386+
if LooseVersion(str(trait_spec.deprecated)) < nipype_version:
387+
raise TraitError(msg)
388+
else:
389+
warn(msg)
390+
391+
361392
def _hash_infile(self, adict, key):
362393
""" Inject file hashes into adict[key]"""
363394
stuff = adict[key]

nipype/interfaces/tests/test_base.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,32 @@ class MyInterface(nib.BaseInterface):
132132
myif.inputs.kung = 2
133133
yield assert_equal, myif.inputs.kung, 2.0
134134

135+
def test_deprecation():
136+
class DeprecationSpec1(nib.TraitedSpec):
137+
foo = nib.traits.Int(deprecated='0.1')
138+
spec_instance = DeprecationSpec1()
139+
set_foo = lambda : setattr(spec_instance, 'foo', 1)
140+
yield assert_raises, nib.TraitError, set_foo
141+
class DeprecationSpec1numeric(nib.TraitedSpec):
142+
foo = nib.traits.Int(deprecated=0.1)
143+
spec_instance = DeprecationSpec1numeric()
144+
set_foo = lambda : setattr(spec_instance, 'foo', 1)
145+
yield assert_raises, nib.TraitError, set_foo
146+
class DeprecationSpec2(nib.TraitedSpec):
147+
foo = nib.traits.Int(deprecated='100', new_name='bar')
148+
spec_instance = DeprecationSpec2()
149+
set_foo = lambda : setattr(spec_instance, 'foo', 1)
150+
yield assert_raises, nib.TraitError, set_foo
151+
class DeprecationSpec3(nib.TraitedSpec):
152+
foo = nib.traits.Int(deprecated='1000', new_name='bar')
153+
bar = nib.traits.Int()
154+
spec_instance = DeprecationSpec3()
155+
not_raised = True
156+
try:
157+
spec_instance.foo = 1
158+
except nib.TraitError:
159+
not_raised = False
160+
yield assert_true, not_raised
135161

136162
def checknose():
137163
"""check version of nose for known incompatability"""

0 commit comments

Comments
 (0)