Skip to content

Commit ac6709b

Browse files
committed
enh: added versioning to traits and interfaces
1 parent 47cdfdb commit ac6709b

File tree

5 files changed

+133
-11
lines changed

5 files changed

+133
-11
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 versioning option and checking to traits
910

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

doc/devel/interface_specs.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,16 @@ 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+
``min_ver`` and ``max_ver``
267+
These metadata determine if a particular trait will be available when a
268+
given version of the underlying interface runs. Note that this check is
269+
performed at runtime.::
270+
271+
class RealignInputSpec(BaseInterfaceInputSpec):
272+
jobtype = traits.Enum('estwrite', 'estimate', 'write', min_ver=5,
273+
usedefault=True)
274+
266275
CommandLine
267276
^^^^^^^^^^^
268277

nipype/interfaces/base.py

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
has_metadata)
2828
from ..utils.filemanip import (md5, hash_infile, FileNotFoundError,
2929
hash_timestamp)
30-
from ..utils.misc import is_container, trim
30+
from ..utils.misc import is_container, trim, str2bool
3131
from .. import config, logging
3232

3333
iflogger = logging.getLogger('interface')
@@ -602,6 +602,10 @@ def _get_filecopy_info(self):
602602
"""
603603
raise NotImplementedError
604604

605+
@property
606+
def version(self):
607+
raise NotImplementedError
608+
605609

606610
class BaseInterfaceInputSpec(TraitedSpec):
607611
ignore_exception = traits.Bool(False, desc="Print an error message instead \
@@ -628,6 +632,7 @@ class BaseInterface(Interface):
628632
629633
"""
630634
input_spec = BaseInterfaceInputSpec
635+
_version = None
631636

632637
def __init__(self, **inputs):
633638
if not self.input_spec:
@@ -787,6 +792,26 @@ def _check_mandatory_inputs(self):
787792
transient=None).items():
788793
self._check_requires(spec, name, getattr(self.inputs, name))
789794

795+
def _check_input_version_requirements(self):
796+
""" Raises an exception on version mismatch
797+
"""
798+
version = str(self.version)
799+
if not version:
800+
return
801+
# check minimum version
802+
names = self.inputs.trait_names(**dict(min_ver=lambda t: t is not None))
803+
for name in names:
804+
min_ver = str(self.inputs.traits()[name].min_ver)
805+
if min_ver > version:
806+
raise Exception('Input %s (%s) (version %s < required %s)' %
807+
(name, self.__class__.__name__, version, min_ver))
808+
names = self.inputs.trait_names(**dict(max_ver=lambda t: t is not None))
809+
for name in names:
810+
max_ver = str(self.inputs.traits()[name].max_ver)
811+
if max_ver < version:
812+
raise Exception('Input %s (%s) (version %s > required %s)' %
813+
(name, self.__class__.__name__, version, max_ver))
814+
790815
def _run_interface(self, runtime):
791816
""" Core function that executes interface
792817
"""
@@ -809,6 +834,7 @@ def run(self, **inputs):
809834
"""
810835
self.inputs.set(**inputs)
811836
self._check_mandatory_inputs()
837+
self._check_input_version_requirements()
812838
interface = self.__class__
813839
# initialize provenance tracking
814840
env = deepcopy(os.environ.data)
@@ -883,6 +909,14 @@ def aggregate_outputs(self, runtime=None, needed_outputs=None):
883909
raise error
884910
return outputs
885911

912+
@property
913+
def version(self):
914+
if self._version is None:
915+
if str2bool(config.get('execution', 'stop_on_unknown_version')):
916+
raise ValueError('Interface %s has no version information' %
917+
self.__class__.__name__)
918+
return self._version
919+
886920

887921
class Stream(object):
888922
"""Function to capture stdout and stderr streams with timestamps
@@ -1026,6 +1060,7 @@ class must be instantiated with a command argument
10261060

10271061
input_spec = CommandLineInputSpec
10281062
_cmd = None
1063+
_version = None
10291064

10301065
def __init__(self, command=None, **inputs):
10311066
super(CommandLine, self).__init__(**inputs)
@@ -1069,6 +1104,32 @@ def help(cls, returnhelp=False):
10691104
else:
10701105
print allhelp
10711106

1107+
def _get_environ(self):
1108+
out_environ = {}
1109+
try:
1110+
display_var = config.get('execution', 'display_variable')
1111+
out_environ = {'DISPLAY': display_var}
1112+
except NoOptionError:
1113+
pass
1114+
iflogger.debug(out_environ)
1115+
if isdefined(self.inputs.environ):
1116+
out_environ.update(self.inputs.environ)
1117+
return out_environ
1118+
1119+
def version_from_command(self, flag='-v'):
1120+
cmdname = self.cmd.split()[0]
1121+
if self._exists_in_path(cmdname):
1122+
env = deepcopy(os.environ.data)
1123+
out_environ = self._get_environ()
1124+
env.update(out_environ)
1125+
proc = subprocess.Popen(' '.join((cmdname, flag)),
1126+
shell=True,
1127+
env=env,
1128+
stdout=subprocess.PIPE,
1129+
stderr=subprocess.PIPE,
1130+
)
1131+
o, e = proc.communicate()
1132+
return o
10721133

10731134
def _run_interface(self, runtime):
10741135
"""Execute command via subprocess
@@ -1085,15 +1146,7 @@ def _run_interface(self, runtime):
10851146
setattr(runtime, 'stdout', None)
10861147
setattr(runtime, 'stderr', None)
10871148
setattr(runtime, 'cmdline', self.cmdline)
1088-
out_environ = {}
1089-
try:
1090-
display_var = config.get('execution', 'display_variable')
1091-
out_environ = {'DISPLAY': display_var}
1092-
except NoOptionError:
1093-
pass
1094-
iflogger.debug(out_environ)
1095-
if isdefined(self.inputs.environ):
1096-
out_environ.update(self.inputs.environ)
1149+
out_environ = self._get_environ()
10971150
runtime.environ.update(out_environ)
10981151
if not self._exists_in_path(self.cmd.split()[0]):
10991152
raise IOError("%s could not be found on host %s" % (self.cmd.split()[0],

nipype/interfaces/tests/test_base.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,64 @@ def _run_interface(self, runtime):
229229
nib.BaseInterface.input_spec = None
230230
yield assert_raises, Exception, nib.BaseInterface
231231

232+
def test_version():
233+
class InputSpec(nib.TraitedSpec):
234+
foo = nib.traits.Int(desc='a random int', min_ver=0.9)
235+
class DerivedInterface1(nib.BaseInterface):
236+
input_spec = InputSpec
237+
obj = DerivedInterface1()
238+
not_raised = True
239+
try:
240+
obj._check_input_version_requirements()
241+
except:
242+
not_raised = False
243+
yield assert_true, not_raised
244+
config.set('execution', 'stop_on_unknown_version', True)
245+
try:
246+
obj._check_input_version_requirements()
247+
except:
248+
not_raised = False
249+
yield assert_false, not_raised
250+
config.set_default_config()
251+
class InputSpec(nib.TraitedSpec):
252+
foo = nib.traits.Int(desc='a random int', min_ver=0.9)
253+
class DerivedInterface1(nib.BaseInterface):
254+
input_spec = InputSpec
255+
_version = 0.8
256+
obj = DerivedInterface1()
257+
yield assert_raises, Exception, obj._check_input_version_requirements
258+
class InputSpec(nib.TraitedSpec):
259+
foo = nib.traits.Int(desc='a random int', min_ver=0.9)
260+
class DerivedInterface1(nib.BaseInterface):
261+
input_spec = InputSpec
262+
_version = 0.9
263+
obj = DerivedInterface1()
264+
not_raised = True
265+
try:
266+
obj._check_input_version_requirements()
267+
except:
268+
not_raised = False
269+
yield assert_true, not_raised
270+
class InputSpec(nib.TraitedSpec):
271+
foo = nib.traits.Int(desc='a random int', max_ver=0.7)
272+
class DerivedInterface2(nib.BaseInterface):
273+
input_spec = InputSpec
274+
_version = 0.8
275+
obj = DerivedInterface2()
276+
yield assert_raises, Exception, obj._check_input_version_requirements
277+
class InputSpec(nib.TraitedSpec):
278+
foo = nib.traits.Int(desc='a random int', max_ver=0.9)
279+
class DerivedInterface1(nib.BaseInterface):
280+
input_spec = InputSpec
281+
_version = 0.9
282+
obj = DerivedInterface1()
283+
not_raised = True
284+
try:
285+
obj._check_input_version_requirements()
286+
except:
287+
not_raised = False
288+
yield assert_true, not_raised
289+
232290
def test_Commandline():
233291
yield assert_raises, Exception, nib.CommandLine
234292
ci = nib.CommandLine(command='which')

nipype/utils/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
stop_on_first_crash = false
4646
stop_on_first_rerun = false
4747
use_relative_paths = false
48+
stop_on_unknown_version = false
4849
4950
[check]
5051
interval = 1209600

0 commit comments

Comments
 (0)