From e8977681401bfd54a563fea08a912706d17299d5 Mon Sep 17 00:00:00 2001 From: Satrajit Ghosh Date: Sat, 21 Nov 2020 09:41:14 -0500 Subject: [PATCH 1/6] Update core.py Potential hack for #3138 --- nipype/interfaces/base/core.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nipype/interfaces/base/core.py b/nipype/interfaces/base/core.py index 54c4302c7f..dcdb636994 100644 --- a/nipype/interfaces/base/core.py +++ b/nipype/interfaces/base/core.py @@ -31,7 +31,7 @@ from ...external.due import due -from .traits_extension import traits, isdefined +from .traits_extension import traits, isdefined, Undefined from .specs import ( BaseInterfaceInputSpec, CommandLineInputSpec, @@ -371,8 +371,10 @@ def run(self, cwd=None, ignore_exception=None, **inputs): enable_rm = config.resource_monitor and self.resource_monitor self.inputs.trait_set(**inputs) + unavailable_traits = self._check_version_requirements(self.inputs) + if unavailable_traits: + self.inputs.traitset(**{k: Undefined for k in unavailable_traits}) self._check_mandatory_inputs() - self._check_version_requirements(self.inputs) interface = self.__class__ self._duecredit_cite() From 737e2202e4469d1a32ec9b9a55bc39776b558133 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Sat, 21 Nov 2020 10:13:20 -0500 Subject: [PATCH 2/6] RF: Unset default traits for out-of-version inputs at init --- nipype/interfaces/base/core.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/nipype/interfaces/base/core.py b/nipype/interfaces/base/core.py index dcdb636994..1063c6a0df 100644 --- a/nipype/interfaces/base/core.py +++ b/nipype/interfaces/base/core.py @@ -180,7 +180,14 @@ def __init__( if not self.input_spec: raise Exception("No input_spec in class: %s" % self.__class__.__name__) - self.inputs = self.input_spec(**inputs) + # Create input spec, disable any defaults that are unavailable due to + # version, and then apply the inputs that were passed. + self.inputs = self.input_spec() + unavailable_traits = self._check_version_requirements(self.inputs, raise_exception=False) + if unavailable_traits: + self.inputs.trait_set(**{k: Undefined for k in unavailable_traits}) + self.inputs.trait_set(**inputs) + self.ignore_exception = ignore_exception if resource_monitor is not None: @@ -371,10 +378,8 @@ def run(self, cwd=None, ignore_exception=None, **inputs): enable_rm = config.resource_monitor and self.resource_monitor self.inputs.trait_set(**inputs) - unavailable_traits = self._check_version_requirements(self.inputs) - if unavailable_traits: - self.inputs.traitset(**{k: Undefined for k in unavailable_traits}) self._check_mandatory_inputs() + self._check_version_requirements(self.inputs) interface = self.__class__ self._duecredit_cite() From eef368740a61f26545c868438f94ed25a1e1b171 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Sat, 21 Nov 2020 12:13:30 -0500 Subject: [PATCH 3/6] TEST: Verify behavior when an input has a default and max_ver --- nipype/interfaces/base/tests/test_core.py | 40 +++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/nipype/interfaces/base/tests/test_core.py b/nipype/interfaces/base/tests/test_core.py index 165b3532ab..e8da1f16b2 100644 --- a/nipype/interfaces/base/tests/test_core.py +++ b/nipype/interfaces/base/tests/test_core.py @@ -272,6 +272,46 @@ class input_spec(nib.TraitedSpec): obj._check_version_requirements(obj.inputs) +def test_unavailable_input(): + class WithInput(nib.BaseInterface): + class input_spec(nib.TraitedSpec): + foo = nib.traits.Int(3, usedefault=True, max_ver="0.5") + + _version = "0.4" + + def _run_interface(self, runtime): + return runtime + + class WithoutInput(WithInput): + _version = "0.6" + + has = WithInput() + hasnt = WithoutInput() + trying_anyway = WithoutInput(foo=3) + assert has.inputs.foo == 3 + assert not nib.isdefined(hasnt.inputs.foo) + assert trying_anyway.inputs.foo == 3 + + has.run() + hasnt.run() + with pytest.raises(Exception): + trying_anyway.run() + + # Still settable + has.inputs.foo = 4 + hasnt.inputs.foo = 4 + trying_anyway.inputs.foo = 4 + assert has.inputs.foo == 4 + assert hasnt.inputs.foo == 4 + assert trying_anyway.inputs.foo == 4 + + has.run() + with pytest.raises(Exception): + hasnt.run() + with pytest.raises(Exception): + trying_anyway.run() + + def test_output_version(): class InputSpec(nib.TraitedSpec): foo = nib.traits.Int(desc="a random int") From a29a041851b051d6b8e04628adb95310aa45cee2 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Sat, 21 Nov 2020 13:20:14 -0500 Subject: [PATCH 4/6] TEST: Expect more warnings --- nipype/interfaces/base/tests/test_core.py | 26 ++++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/nipype/interfaces/base/tests/test_core.py b/nipype/interfaces/base/tests/test_core.py index e8da1f16b2..233635a972 100644 --- a/nipype/interfaces/base/tests/test_core.py +++ b/nipype/interfaces/base/tests/test_core.py @@ -245,15 +245,17 @@ class input_spec(nib.TraitedSpec): _version = "misparsed-garbage" - obj = DerivedInterface() + with caplog.at_level(logging.WARNING, logger="nipype.interface"): + obj = DerivedInterface() + assert len(caplog.records) == 2 obj.inputs.foo = 1 obj.inputs.bar = 1 with caplog.at_level(logging.WARNING, logger="nipype.interface"): obj._check_version_requirements(obj.inputs) - assert len(caplog.records) == 2 + assert len(caplog.records) == 4 -def test_input_version_missing_error(): +def test_input_version_missing_error(caplog): from nipype import config class DerivedInterface(nib.BaseInterface): @@ -263,13 +265,17 @@ class input_spec(nib.TraitedSpec): _version = "misparsed-garbage" - with mock.patch.object(config, "getboolean", return_value=True): - obj = DerivedInterface(foo=1) - with pytest.raises(ValueError): - obj._check_version_requirements(obj.inputs) - obj = DerivedInterface(bar=1) - with pytest.raises(ValueError): - obj._check_version_requirements(obj.inputs) + with caplog.at_level(logging.WARNING, logger="nipype.interface"): + obj1 = DerivedInterface(foo=1) + obj2 = DerivedInterface(bar=1) + assert len(caplog.records) == 4 + with caplog.at_level(logging.WARNING, logger="nipype.interface"): + with mock.patch.object(config, "getboolean", return_value=True): + with pytest.raises(ValueError): + obj1._check_version_requirements(obj1.inputs) + with pytest.raises(ValueError): + obj2._check_version_requirements(obj2.inputs) + assert len(caplog.records) == 6 def test_unavailable_input(): From 8db5b25c7e952ccbcae926fa85f0a95234db8556 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Sat, 21 Nov 2020 19:07:23 -0500 Subject: [PATCH 5/6] RF: Change raise_exception to permissive; suppress warnings and exceptions with same mechanism --- nipype/interfaces/base/core.py | 20 ++++++++++++++------ nipype/interfaces/base/tests/test_core.py | 14 +++++--------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/nipype/interfaces/base/core.py b/nipype/interfaces/base/core.py index 1063c6a0df..afafbebf84 100644 --- a/nipype/interfaces/base/core.py +++ b/nipype/interfaces/base/core.py @@ -183,7 +183,9 @@ def __init__( # Create input spec, disable any defaults that are unavailable due to # version, and then apply the inputs that were passed. self.inputs = self.input_spec() - unavailable_traits = self._check_version_requirements(self.inputs, raise_exception=False) + unavailable_traits = self._check_version_requirements( + self.inputs, permissive=True + ) if unavailable_traits: self.inputs.trait_set(**{k: Undefined for k in unavailable_traits}) self.inputs.trait_set(**inputs) @@ -271,8 +273,12 @@ def _check_mandatory_inputs(self): ): self._check_requires(spec, name, getattr(self.inputs, name)) - def _check_version_requirements(self, trait_object, raise_exception=True): + def _check_version_requirements(self, trait_object, permissive=False): """ Raises an exception on version mismatch + + Set the ``permissive`` attribute to True to suppress warnings and exceptions. + This is currently only used in __init__ to silently identify unavailable + traits. """ unavailable_traits = [] # check minimum version @@ -290,7 +296,8 @@ def _check_version_requirements(self, trait_object, raise_exception=True): f"Nipype cannot validate the package version {version!r} for " f"{self.__class__.__name__}. Trait {name} requires version >={min_ver}." ) - iflogger.warning(f"{msg}. Please verify validity.") + if not permissive: + iflogger.warning(f"{msg}. Please verify validity.") if config.getboolean("execution", "stop_on_unknown_version"): raise ValueError(msg) from err continue @@ -298,7 +305,7 @@ def _check_version_requirements(self, trait_object, raise_exception=True): unavailable_traits.append(name) if not isdefined(getattr(trait_object, name)): continue - if raise_exception: + if not permissive: raise Exception( "Trait %s (%s) (version %s < required %s)" % (name, self.__class__.__name__, version, min_ver) @@ -318,7 +325,8 @@ def _check_version_requirements(self, trait_object, raise_exception=True): f"Nipype cannot validate the package version {version!r} for " f"{self.__class__.__name__}. Trait {name} requires version <={max_ver}." ) - iflogger.warning(f"{msg}. Please verify validity.") + if not permissive: + iflogger.warning(f"{msg}. Please verify validity.") if config.getboolean("execution", "stop_on_unknown_version"): raise ValueError(msg) from err continue @@ -326,7 +334,7 @@ def _check_version_requirements(self, trait_object, raise_exception=True): unavailable_traits.append(name) if not isdefined(getattr(trait_object, name)): continue - if raise_exception: + if not permissive: raise Exception( "Trait %s (%s) (version %s > required %s)" % (name, self.__class__.__name__, version, max_ver) diff --git a/nipype/interfaces/base/tests/test_core.py b/nipype/interfaces/base/tests/test_core.py index 233635a972..6b587554fa 100644 --- a/nipype/interfaces/base/tests/test_core.py +++ b/nipype/interfaces/base/tests/test_core.py @@ -245,14 +245,12 @@ class input_spec(nib.TraitedSpec): _version = "misparsed-garbage" - with caplog.at_level(logging.WARNING, logger="nipype.interface"): - obj = DerivedInterface() - assert len(caplog.records) == 2 + obj = DerivedInterface() obj.inputs.foo = 1 obj.inputs.bar = 1 with caplog.at_level(logging.WARNING, logger="nipype.interface"): obj._check_version_requirements(obj.inputs) - assert len(caplog.records) == 4 + assert len(caplog.records) == 2 def test_input_version_missing_error(caplog): @@ -265,17 +263,15 @@ class input_spec(nib.TraitedSpec): _version = "misparsed-garbage" - with caplog.at_level(logging.WARNING, logger="nipype.interface"): - obj1 = DerivedInterface(foo=1) - obj2 = DerivedInterface(bar=1) - assert len(caplog.records) == 4 + obj1 = DerivedInterface(foo=1) + obj2 = DerivedInterface(bar=1) with caplog.at_level(logging.WARNING, logger="nipype.interface"): with mock.patch.object(config, "getboolean", return_value=True): with pytest.raises(ValueError): obj1._check_version_requirements(obj1.inputs) with pytest.raises(ValueError): obj2._check_version_requirements(obj2.inputs) - assert len(caplog.records) == 6 + assert len(caplog.records) == 2 def test_unavailable_input(): From 35ea6354aedbfd21f9432be5ddc27262bcfdfccf Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Sat, 21 Nov 2020 20:00:34 -0500 Subject: [PATCH 6/6] TEST/FIX: Bad input value now causing problems --- nipype/interfaces/tests/test_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/interfaces/tests/test_io.py b/nipype/interfaces/tests/test_io.py index ef210de030..f2afedd492 100644 --- a/nipype/interfaces/tests/test_io.py +++ b/nipype/interfaces/tests/test_io.py @@ -465,7 +465,7 @@ def test_datasink_substitutions(tmpdir): files.append(f) open(f, "w") ds = nio.DataSink( - parametrization=False, + parameterization=False, base_directory=str(outdir), substitutions=[("ababab", "ABABAB")], # end archoring ($) is used to assure operation on the filename