Skip to content

Commit 3ec5eeb

Browse files
authored
Merge pull request #3273 from nipy/fix-unavailable-traits
handle unavailable traits due to version differences
2 parents 7d538fa + 35ea635 commit 3ec5eeb

File tree

3 files changed

+73
-16
lines changed

3 files changed

+73
-16
lines changed

nipype/interfaces/base/core.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
from ...external.due import due
3333

34-
from .traits_extension import traits, isdefined
34+
from .traits_extension import traits, isdefined, Undefined
3535
from .specs import (
3636
BaseInterfaceInputSpec,
3737
CommandLineInputSpec,
@@ -180,7 +180,16 @@ def __init__(
180180
if not self.input_spec:
181181
raise Exception("No input_spec in class: %s" % self.__class__.__name__)
182182

183-
self.inputs = self.input_spec(**inputs)
183+
# Create input spec, disable any defaults that are unavailable due to
184+
# version, and then apply the inputs that were passed.
185+
self.inputs = self.input_spec()
186+
unavailable_traits = self._check_version_requirements(
187+
self.inputs, permissive=True
188+
)
189+
if unavailable_traits:
190+
self.inputs.trait_set(**{k: Undefined for k in unavailable_traits})
191+
self.inputs.trait_set(**inputs)
192+
184193
self.ignore_exception = ignore_exception
185194

186195
if resource_monitor is not None:
@@ -264,8 +273,12 @@ def _check_mandatory_inputs(self):
264273
):
265274
self._check_requires(spec, name, getattr(self.inputs, name))
266275

267-
def _check_version_requirements(self, trait_object, raise_exception=True):
276+
def _check_version_requirements(self, trait_object, permissive=False):
268277
""" Raises an exception on version mismatch
278+
279+
Set the ``permissive`` attribute to True to suppress warnings and exceptions.
280+
This is currently only used in __init__ to silently identify unavailable
281+
traits.
269282
"""
270283
unavailable_traits = []
271284
# check minimum version
@@ -283,15 +296,16 @@ def _check_version_requirements(self, trait_object, raise_exception=True):
283296
f"Nipype cannot validate the package version {version!r} for "
284297
f"{self.__class__.__name__}. Trait {name} requires version >={min_ver}."
285298
)
286-
iflogger.warning(f"{msg}. Please verify validity.")
299+
if not permissive:
300+
iflogger.warning(f"{msg}. Please verify validity.")
287301
if config.getboolean("execution", "stop_on_unknown_version"):
288302
raise ValueError(msg) from err
289303
continue
290304
if too_old:
291305
unavailable_traits.append(name)
292306
if not isdefined(getattr(trait_object, name)):
293307
continue
294-
if raise_exception:
308+
if not permissive:
295309
raise Exception(
296310
"Trait %s (%s) (version %s < required %s)"
297311
% (name, self.__class__.__name__, version, min_ver)
@@ -311,15 +325,16 @@ def _check_version_requirements(self, trait_object, raise_exception=True):
311325
f"Nipype cannot validate the package version {version!r} for "
312326
f"{self.__class__.__name__}. Trait {name} requires version <={max_ver}."
313327
)
314-
iflogger.warning(f"{msg}. Please verify validity.")
328+
if not permissive:
329+
iflogger.warning(f"{msg}. Please verify validity.")
315330
if config.getboolean("execution", "stop_on_unknown_version"):
316331
raise ValueError(msg) from err
317332
continue
318333
if too_new:
319334
unavailable_traits.append(name)
320335
if not isdefined(getattr(trait_object, name)):
321336
continue
322-
if raise_exception:
337+
if not permissive:
323338
raise Exception(
324339
"Trait %s (%s) (version %s > required %s)"
325340
% (name, self.__class__.__name__, version, max_ver)

nipype/interfaces/base/tests/test_core.py

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ class input_spec(nib.TraitedSpec):
253253
assert len(caplog.records) == 2
254254

255255

256-
def test_input_version_missing_error():
256+
def test_input_version_missing_error(caplog):
257257
from nipype import config
258258

259259
class DerivedInterface(nib.BaseInterface):
@@ -263,13 +263,55 @@ class input_spec(nib.TraitedSpec):
263263

264264
_version = "misparsed-garbage"
265265

266-
with mock.patch.object(config, "getboolean", return_value=True):
267-
obj = DerivedInterface(foo=1)
268-
with pytest.raises(ValueError):
269-
obj._check_version_requirements(obj.inputs)
270-
obj = DerivedInterface(bar=1)
271-
with pytest.raises(ValueError):
272-
obj._check_version_requirements(obj.inputs)
266+
obj1 = DerivedInterface(foo=1)
267+
obj2 = DerivedInterface(bar=1)
268+
with caplog.at_level(logging.WARNING, logger="nipype.interface"):
269+
with mock.patch.object(config, "getboolean", return_value=True):
270+
with pytest.raises(ValueError):
271+
obj1._check_version_requirements(obj1.inputs)
272+
with pytest.raises(ValueError):
273+
obj2._check_version_requirements(obj2.inputs)
274+
assert len(caplog.records) == 2
275+
276+
277+
def test_unavailable_input():
278+
class WithInput(nib.BaseInterface):
279+
class input_spec(nib.TraitedSpec):
280+
foo = nib.traits.Int(3, usedefault=True, max_ver="0.5")
281+
282+
_version = "0.4"
283+
284+
def _run_interface(self, runtime):
285+
return runtime
286+
287+
class WithoutInput(WithInput):
288+
_version = "0.6"
289+
290+
has = WithInput()
291+
hasnt = WithoutInput()
292+
trying_anyway = WithoutInput(foo=3)
293+
assert has.inputs.foo == 3
294+
assert not nib.isdefined(hasnt.inputs.foo)
295+
assert trying_anyway.inputs.foo == 3
296+
297+
has.run()
298+
hasnt.run()
299+
with pytest.raises(Exception):
300+
trying_anyway.run()
301+
302+
# Still settable
303+
has.inputs.foo = 4
304+
hasnt.inputs.foo = 4
305+
trying_anyway.inputs.foo = 4
306+
assert has.inputs.foo == 4
307+
assert hasnt.inputs.foo == 4
308+
assert trying_anyway.inputs.foo == 4
309+
310+
has.run()
311+
with pytest.raises(Exception):
312+
hasnt.run()
313+
with pytest.raises(Exception):
314+
trying_anyway.run()
273315

274316

275317
def test_output_version():

nipype/interfaces/tests/test_io.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ def test_datasink_substitutions(tmpdir):
465465
files.append(f)
466466
open(f, "w")
467467
ds = nio.DataSink(
468-
parametrization=False,
468+
parameterization=False,
469469
base_directory=str(outdir),
470470
substitutions=[("ababab", "ABABAB")],
471471
# end archoring ($) is used to assure operation on the filename

0 commit comments

Comments
 (0)