27
27
has_metadata )
28
28
from ..utils .filemanip import (md5 , hash_infile , FileNotFoundError ,
29
29
hash_timestamp )
30
- from ..utils .misc import is_container , trim
30
+ from ..utils .misc import is_container , trim , str2bool
31
31
from .. import config , logging , LooseVersion
32
32
from .. import __version__
33
33
from nipype .utils .filemanip import split_filename
@@ -639,6 +639,10 @@ def _get_filecopy_info(self):
639
639
"""
640
640
raise NotImplementedError
641
641
642
+ @property
643
+ def version (self ):
644
+ raise NotImplementedError
645
+
642
646
643
647
class BaseInterfaceInputSpec (TraitedSpec ):
644
648
ignore_exception = traits .Bool (False , desc = "Print an error message instead \
@@ -665,6 +669,7 @@ class BaseInterface(Interface):
665
669
666
670
"""
667
671
input_spec = BaseInterfaceInputSpec
672
+ _version = None
668
673
669
674
def __init__ (self , ** inputs ):
670
675
if not self .input_spec :
@@ -824,6 +829,36 @@ def _check_mandatory_inputs(self):
824
829
transient = None ).items ():
825
830
self ._check_requires (spec , name , getattr (self .inputs , name ))
826
831
832
+ def _check_version_requirements (self , trait_object , raise_exception = True ):
833
+ """ Raises an exception on version mismatch
834
+ """
835
+ unavailable_traits = []
836
+ version = LooseVersion (str (self .version ))
837
+ if not version :
838
+ return
839
+ # check minimum version
840
+ names = trait_object .trait_names (** dict (min_ver = lambda t : t is not None ))
841
+ for name in names :
842
+ min_ver = LooseVersion (str (trait_object .traits ()[name ].min_ver ))
843
+ if min_ver > version :
844
+ unavailable_traits .append (name )
845
+ if not isdefined (getattr (trait_object , name )):
846
+ continue
847
+ if raise_exception :
848
+ raise Exception ('Trait %s (%s) (version %s < required %s)' %
849
+ (name , self .__class__ .__name__ , version , min_ver ))
850
+ names = trait_object .trait_names (** dict (max_ver = lambda t : t is not None ))
851
+ for name in names :
852
+ max_ver = LooseVersion (str (trait_object .traits ()[name ].max_ver ))
853
+ if max_ver < version :
854
+ unavailable_traits .append (name )
855
+ if not isdefined (getattr (trait_object , name )):
856
+ continue
857
+ if raise_exception :
858
+ raise Exception ('Trait %s (%s) (version %s > required %s)' %
859
+ (name , self .__class__ .__name__ , version , max_ver ))
860
+ return unavailable_traits
861
+
827
862
def _run_interface (self , runtime ):
828
863
""" Core function that executes interface
829
864
"""
@@ -846,6 +881,7 @@ def run(self, **inputs):
846
881
"""
847
882
self .inputs .set (** inputs )
848
883
self ._check_mandatory_inputs ()
884
+ self ._check_version_requirements (self .inputs )
849
885
interface = self .__class__
850
886
# initialize provenance tracking
851
887
env = deepcopy (os .environ .data )
@@ -905,9 +941,18 @@ def aggregate_outputs(self, runtime=None, needed_outputs=None):
905
941
predicted_outputs = self ._list_outputs ()
906
942
outputs = self ._outputs ()
907
943
if predicted_outputs :
944
+ _unavailable_outputs = []
945
+ if outputs :
946
+ _unavailable_outputs = \
947
+ self ._check_version_requirements (self ._outputs ())
908
948
for key , val in predicted_outputs .items ():
909
949
if needed_outputs and key not in needed_outputs :
910
950
continue
951
+ if key in _unavailable_outputs :
952
+ raise KeyError (('Output trait %s not available in version '
953
+ '%s of interface %s. Please inform '
954
+ 'developers.' ) % (key , self .version ,
955
+ self .__class__ .__name__ ))
911
956
try :
912
957
setattr (outputs , key , val )
913
958
_ = getattr (outputs , key )
@@ -920,6 +965,14 @@ def aggregate_outputs(self, runtime=None, needed_outputs=None):
920
965
raise error
921
966
return outputs
922
967
968
+ @property
969
+ def version (self ):
970
+ if self ._version is None :
971
+ if str2bool (config .get ('execution' , 'stop_on_unknown_version' )):
972
+ raise ValueError ('Interface %s has no version information' %
973
+ self .__class__ .__name__ )
974
+ return self ._version
975
+
923
976
924
977
class Stream (object ):
925
978
"""Function to capture stdout and stderr streams with timestamps
@@ -1063,6 +1116,7 @@ class must be instantiated with a command argument
1063
1116
1064
1117
input_spec = CommandLineInputSpec
1065
1118
_cmd = None
1119
+ _version = None
1066
1120
1067
1121
def __init__ (self , command = None , ** inputs ):
1068
1122
super (CommandLine , self ).__init__ (** inputs )
@@ -1106,6 +1160,32 @@ def help(cls, returnhelp=False):
1106
1160
else :
1107
1161
print allhelp
1108
1162
1163
+ def _get_environ (self ):
1164
+ out_environ = {}
1165
+ try :
1166
+ display_var = config .get ('execution' , 'display_variable' )
1167
+ out_environ = {'DISPLAY' : display_var }
1168
+ except NoOptionError :
1169
+ pass
1170
+ iflogger .debug (out_environ )
1171
+ if isdefined (self .inputs .environ ):
1172
+ out_environ .update (self .inputs .environ )
1173
+ return out_environ
1174
+
1175
+ def version_from_command (self , flag = '-v' ):
1176
+ cmdname = self .cmd .split ()[0 ]
1177
+ if self ._exists_in_path (cmdname ):
1178
+ env = deepcopy (os .environ .data )
1179
+ out_environ = self ._get_environ ()
1180
+ env .update (out_environ )
1181
+ proc = subprocess .Popen (' ' .join ((cmdname , flag )),
1182
+ shell = True ,
1183
+ env = env ,
1184
+ stdout = subprocess .PIPE ,
1185
+ stderr = subprocess .PIPE ,
1186
+ )
1187
+ o , e = proc .communicate ()
1188
+ return o
1109
1189
1110
1190
def _run_interface (self , runtime ):
1111
1191
"""Execute command via subprocess
@@ -1122,15 +1202,7 @@ def _run_interface(self, runtime):
1122
1202
setattr (runtime , 'stdout' , None )
1123
1203
setattr (runtime , 'stderr' , None )
1124
1204
setattr (runtime , 'cmdline' , self .cmdline )
1125
- out_environ = {}
1126
- try :
1127
- display_var = config .get ('execution' , 'display_variable' )
1128
- out_environ = {'DISPLAY' : display_var }
1129
- except NoOptionError :
1130
- pass
1131
- iflogger .debug (out_environ )
1132
- if isdefined (self .inputs .environ ):
1133
- out_environ .update (self .inputs .environ )
1205
+ out_environ = self ._get_environ ()
1134
1206
runtime .environ .update (out_environ )
1135
1207
if not self ._exists_in_path (self .cmd .split ()[0 ]):
1136
1208
raise IOError ("%s could not be found on host %s" % (self .cmd .split ()[0 ],
0 commit comments