Repository: nipy/nipype

Issue:
With NiPype Memory with version 0.12.1 and >=0.13.0
Hi Nipype team,

### Summary
Trying to understand what is causing this difference between versions 0.12.1 and >=0.13.0. Is this with Memory or Realign. I am suspecting that this is the problem. But, I am sure I am missing some important details on this ?
 We are experiencing this in package pypreprocess which is reliant on Nipype. See full traceback in https://github.com/neurospin/pypreprocess/issues/288
### Actual behavior
```python
Nipype version:0.13.0
Traceback (most recent call last):
  File "report_nipype.py", line 11, in <module>
    print(realign)
  File "/home/kamalakar/miniconda2/envs/pypreprocess0_13/lib/python2.7/site-packages/nipype/caching/memory.py", line 98, in __repr__
    self.base_dir)
ValueError: Single '}' encountered in format string
```
### Expected behavior
```python
Nipype version:0.12.1
PipeFunc(nipype.interfaces.spm.preprocess.Realign, base_dir=/tmp/nipype_mem)
```
### How to replicate the behavior
```python
import nipype
print("Nipype version:{0}".format(nipype.__version__))

import nipype.interfaces.spm as spm
from nipype.caching import Memory

mem = Memory(base_dir='/tmp/')
realign = mem.cache(spm.Realign)
print(realign)
```
Thanks a lot for your time.



Discussion:
That was a bug. Fixed in #2407.

You can still use the interface prior to getting the fix, you just can't print the object directly. If you need it, you can manually edit your file (`/home/kamalakar/miniconda2/envs/pypreprocess0_13/lib/python2.7/site-packages/nipype/caching/memory.py`) to use the fixed format string.
Thank you @effigies That's very quick. I will try the fix.
I am printing just to explain the problem. But, the actual problem lies around as I mentioned in this issue at pypreprocess https://github.com/neurospin/pypreprocess/issues/288
It was fine with 0.12.1 but not above 0.13.0. 
Okay. That looks like it's probably an issue with `Realign`. The issue looks like it was probably introduced in #1949, which made it into 0.13.0. It may have been fixed in #2375, which made it into 1.0.0, which was released yesterday. Could you try upgrading your nipype to 1.0 (it will need to be installed by pip; the conda build might be out sometime today), and see if your problem persists?
>Could you try upgrading your nipype to 1.0 (it will need to be installed by pip; the conda build might be out sometime today), and see if your problem persists?

I tried with this version too. The problem still exists.
What does the actual input to Realign look like? Is it a list of files, or a list of a list? I can reproduce the error like so:

```Python
from nipype.interfaces.spm import Realign
realign = Realign()
realign.inputs.in_files = [['test.img']]
```

However, if I use the following, I get no error:

```Python
realign.inputs.in_files = ['test.img']
```
List of a list. 

Is it something changed on passing input to Realign between 0.12.1 to 0.13.0 ?

Resolving patch:
diff --git a/nipype/interfaces/base/traits_extension.py b/nipype/interfaces/base/traits_extension.py
--- a/nipype/interfaces/base/traits_extension.py
+++ b/nipype/interfaces/base/traits_extension.py
@@ -217,6 +217,16 @@ def __init__(self,
         super(ImageFile, self).__init__(value, filter, auto_set, entries,
                                         exists, **metadata)
 
+    def info(self):
+        existing = 'n existing' if self.exists else ''
+        comma = ',' if self.exists and not self.allow_compressed else ''
+        uncompressed = ' uncompressed' if not self.allow_compressed else ''
+        with_ext = ' (valid extensions: [{}])'.format(
+            ', '.join(self.grab_exts())) if self.types else ''
+        return 'a{existing}{comma}{uncompressed} file{with_ext}'.format(
+            existing=existing, comma=comma, uncompressed=uncompressed,
+            with_ext=with_ext)
+
     def grab_exts(self):
         # TODO: file type validation
         exts = []
@@ -243,11 +253,11 @@ def validate(self, object, name, value):
         """
         validated_value = super(ImageFile, self).validate(object, name, value)
         if validated_value and self.types:
-            self._exts = self.grab_exts()
-            if not any(validated_value.endswith(x) for x in self._exts):
+            _exts = self.grab_exts()
+            if not any(validated_value.endswith(x) for x in _exts):
                 raise TraitError(
                     args="{} is not included in allowed types: {}".format(
-                        validated_value, ', '.join(self._exts)))
+                        validated_value, ', '.join(_exts)))
         return validated_value
 
 
@@ -322,15 +332,11 @@ def validate(self, object, name, value):
 
         newvalue = value
 
+        inner_trait = self.inner_traits()[0]
         if not isinstance(value, list) \
-            or (self.inner_traits() and
-                isinstance(self.inner_traits()[0].trait_type,
-                           traits.List) and not
-                isinstance(self.inner_traits()[0].trait_type,
-                           InputMultiPath) and
-                isinstance(value, list) and
-                value and not
-                isinstance(value[0], list)):
+            or (isinstance(inner_trait.trait_type, traits.List) and
+                not isinstance(inner_trait.trait_type, InputMultiPath) and
+                not isinstance(value[0], list)):
             newvalue = [value]
         value = super(MultiPath, self).validate(object, name, newvalue)
 
diff --git a/nipype/interfaces/spm/preprocess.py b/nipype/interfaces/spm/preprocess.py
--- a/nipype/interfaces/spm/preprocess.py
+++ b/nipype/interfaces/spm/preprocess.py
@@ -125,7 +125,8 @@ def _list_outputs(self):
 
 class RealignInputSpec(SPMCommandInputSpec):
     in_files = InputMultiPath(
-        ImageFileSPM(exists=True),
+        traits.Either(ImageFileSPM(exists=True),
+                      traits.List(ImageFileSPM(exists=True))),
         field='data',
         mandatory=True,
         copyfile=True,


Test patch: