Skip to content

Commit ee31238

Browse files
committed
Merge pull request #1047 from oesteban/enh/ImproveJSONInterfaces
EHN: Improve JSON interfaces
2 parents 97ec846 + b716f46 commit ee31238

File tree

5 files changed

+121
-34
lines changed

5 files changed

+121
-34
lines changed

CHANGES

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Next release
22
============
33

4+
* ENH: Improve JSON interfaces: default settings when reading and consistent output creation
5+
when writing (https://github.com/nipy/nipype/pull/1047)
46
* FIX: AddCSVRow problems when using infields (https://github.com/nipy/nipype/pull/1028)
57
* FIX: Removed unused ANTS registration flag (https://github.com/nipy/nipype/pull/999)
68
* FIX: Amend create_tbss_non_fa() workflow to match FSL's tbss_non_fa command. (https://github.com/nipy/nipype/pull/1033)

nipype/interfaces/io.py

Lines changed: 73 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1804,8 +1804,9 @@ def _get_ssh_client(self):
18041804

18051805

18061806
class JSONFileGrabberInputSpec(DynamicTraitedSpec, BaseInterfaceInputSpec):
1807-
in_file = File(exists=True, mandatory=True,
1808-
desc='JSON source file')
1807+
in_file = File(exists=True, desc='JSON source file')
1808+
defaults = traits.Dict(desc=('JSON dictionary that sets default output'
1809+
'values, overridden by values found in in_file'))
18091810

18101811

18111812
class JSONFileGrabber(IOBase):
@@ -1819,12 +1820,15 @@ class JSONFileGrabber(IOBase):
18191820
18201821
>>> from nipype.interfaces.io import JSONFileGrabber
18211822
>>> jsonSource = JSONFileGrabber()
1823+
>>> jsonSource.inputs.defaults = {'param1': u'overrideMe', 'param3': 1.0}
1824+
>>> res = jsonSource.run()
1825+
>>> res.outputs.get()
1826+
{'param3': 1.0, 'param1': u'overrideMe'}
18221827
>>> jsonSource.inputs.in_file = 'jsongrabber.txt'
18231828
>>> res = jsonSource.run()
1824-
>>> print res.outputs.param1
1825-
exampleStr
1826-
>>> print res.outputs.param2
1827-
4
1829+
>>> res.outputs.get()
1830+
{'param3': 1.0, 'param2': 4, 'param1': u'exampleStr'}
1831+
18281832
18291833
"""
18301834
input_spec = JSONFileGrabberInputSpec
@@ -1834,22 +1838,41 @@ class JSONFileGrabber(IOBase):
18341838
def _list_outputs(self):
18351839
import json
18361840

1837-
with open(self.inputs.in_file, 'r') as f:
1838-
data = json.load(f)
1841+
outputs = {}
1842+
if isdefined(self.inputs.in_file):
1843+
with open(self.inputs.in_file, 'r') as f:
1844+
data = json.load(f)
18391845

1840-
if not isinstance(data, dict):
1841-
raise RuntimeError('JSON input has no dictionary structure')
1846+
if not isinstance(data, dict):
1847+
raise RuntimeError('JSON input has no dictionary structure')
18421848

1843-
outputs = {}
1844-
for key, value in data.iteritems():
1845-
outputs[key] = value
1849+
for key, value in data.iteritems():
1850+
outputs[key] = value
1851+
1852+
if isdefined(self.inputs.defaults):
1853+
defaults = self.inputs.defaults
1854+
for key, value in defaults.iteritems():
1855+
if key not in outputs.keys():
1856+
outputs[key] = value
18461857

18471858
return outputs
18481859

18491860

18501861
class JSONFileSinkInputSpec(DynamicTraitedSpec, BaseInterfaceInputSpec):
18511862
out_file = File(desc='JSON sink file')
1852-
in_dict = traits.Dict(desc='input JSON dictionary')
1863+
in_dict = traits.Dict(value={}, usedefault=True,
1864+
desc='input JSON dictionary')
1865+
_outputs = traits.Dict(value={}, usedefault=True)
1866+
1867+
def __setattr__(self, key, value):
1868+
if key not in self.copyable_trait_names():
1869+
if not isdefined(value):
1870+
super(JSONFileSinkInputSpec, self).__setattr__(key, value)
1871+
self._outputs[key] = value
1872+
else:
1873+
if key in self._outputs:
1874+
self._outputs[key] = value
1875+
super(JSONFileSinkInputSpec, self).__setattr__(key, value)
18531876

18541877

18551878
class JSONFileSinkOutputSpec(TraitedSpec):
@@ -1858,7 +1881,10 @@ class JSONFileSinkOutputSpec(TraitedSpec):
18581881

18591882
class JSONFileSink(IOBase):
18601883

1861-
""" Very simple frontend for storing values into a JSON file.
1884+
"""
1885+
Very simple frontend for storing values into a JSON file.
1886+
Entries already existing in in_dict will be overridden by matching
1887+
entries dynamically added as inputs.
18621888
18631889
.. warning::
18641890
@@ -1885,34 +1911,52 @@ class JSONFileSink(IOBase):
18851911
input_spec = JSONFileSinkInputSpec
18861912
output_spec = JSONFileSinkOutputSpec
18871913

1888-
def __init__(self, input_names=[], **inputs):
1914+
def __init__(self, infields=[], force_run=True, **inputs):
18891915
super(JSONFileSink, self).__init__(**inputs)
1890-
self._input_names = filename_to_list(input_names)
1891-
add_traits(self.inputs, [name for name in self._input_names])
1916+
self._input_names = infields
1917+
1918+
undefined_traits = {}
1919+
for key in infields:
1920+
self.inputs.add_trait(key, traits.Any)
1921+
self.inputs._outputs[key] = Undefined
1922+
undefined_traits[key] = Undefined
1923+
self.inputs.trait_set(trait_change_notify=False, **undefined_traits)
1924+
1925+
if force_run:
1926+
self._always_run = True
1927+
1928+
def _process_name(self, name, val):
1929+
if '.' in name:
1930+
newkeys = name.split('.')
1931+
name = newkeys.pop(0)
1932+
nested_dict = {newkeys.pop(): val}
1933+
1934+
for nk in reversed(newkeys):
1935+
nested_dict = {nk: nested_dict}
1936+
val = nested_dict
1937+
1938+
return name, val
18921939

18931940
def _list_outputs(self):
18941941
import json
18951942
import os.path as op
1943+
18961944
if not isdefined(self.inputs.out_file):
18971945
out_file = op.abspath('datasink.json')
18981946
else:
18991947
out_file = self.inputs.out_file
19001948

1901-
out_dict = dict()
1949+
out_dict = self.inputs.in_dict
19021950

1903-
if isdefined(self.inputs.in_dict):
1904-
if isinstance(self.inputs.in_dict, dict):
1905-
out_dict = self.inputs.in_dict
1906-
else:
1907-
for name in self._input_names:
1908-
val = getattr(self.inputs, name)
1909-
val = val if isdefined(val) else 'undefined'
1910-
out_dict[name] = val
1951+
# Overwrite in_dict entries automatically
1952+
for key, val in self.inputs._outputs.items():
1953+
if not isdefined(val) or key == 'trait_added':
1954+
continue
1955+
key, val = self._process_name(key, val)
1956+
out_dict[key] = val
19111957

19121958
with open(out_file, 'w') as f:
19131959
json.dump(out_dict, f)
19141960
outputs = self.output_spec().get()
19151961
outputs['out_file'] = out_file
19161962
return outputs
1917-
1918-

nipype/interfaces/tests/test_auto_JSONFileGrabber.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
from nipype.interfaces.io import JSONFileGrabber
44

55
def test_JSONFileGrabber_inputs():
6-
input_map = dict(ignore_exception=dict(nohash=True,
6+
input_map = dict(defaults=dict(),
7+
ignore_exception=dict(nohash=True,
78
usedefault=True,
89
),
9-
in_file=dict(mandatory=True,
10-
),
10+
in_file=dict(),
1111
)
1212
inputs = JSONFileGrabber.input_spec()
1313

nipype/interfaces/tests/test_auto_JSONFileSink.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
from nipype.interfaces.io import JSONFileSink
44

55
def test_JSONFileSink_inputs():
6-
input_map = dict(ignore_exception=dict(nohash=True,
6+
input_map = dict(_outputs=dict(usedefault=True,
7+
),
8+
ignore_exception=dict(nohash=True,
79
usedefault=True,
810
),
9-
in_dict=dict(),
11+
in_dict=dict(usedefault=True,
12+
),
1013
out_file=dict(),
1114
)
1215
inputs = JSONFileSink.input_spec()

nipype/interfaces/tests/test_io.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,3 +238,41 @@ def test_freesurfersource():
238238
yield assert_equal, fss.inputs.hemi, 'both'
239239
yield assert_equal, fss.inputs.subject_id, Undefined
240240
yield assert_equal, fss.inputs.subjects_dir, Undefined
241+
242+
243+
def test_jsonsink():
244+
import json
245+
import os
246+
247+
ds = nio.JSONFileSink()
248+
yield assert_equal, ds.inputs._outputs, {}
249+
ds = nio.JSONFileSink(in_dict={'foo': 'var'})
250+
yield assert_equal, ds.inputs.in_dict, {'foo': 'var'}
251+
ds = nio.JSONFileSink(infields=['test'])
252+
yield assert_true, 'test' in ds.inputs.copyable_trait_names()
253+
254+
curdir = os.getcwd()
255+
outdir = mkdtemp()
256+
os.chdir(outdir)
257+
js = nio.JSONFileSink(infields=['test'], in_dict={'foo': 'var'})
258+
js.inputs.new_entry = 'someValue'
259+
setattr(js.inputs, 'contrasts.alt', 'someNestedValue')
260+
res = js.run()
261+
262+
with open(res.outputs.out_file, 'r') as f:
263+
data = json.load(f)
264+
yield assert_true, data == {"contrasts": {"alt": "someNestedValue"}, "foo": "var", "new_entry": "someValue"}
265+
266+
js = nio.JSONFileSink(infields=['test'], in_dict={'foo': 'var'})
267+
js.inputs.new_entry = 'someValue'
268+
js.inputs.test = 'testInfields'
269+
setattr(js.inputs, 'contrasts.alt', 'someNestedValue')
270+
res = js.run()
271+
272+
with open(res.outputs.out_file, 'r') as f:
273+
data = json.load(f)
274+
yield assert_true, data == {"test": "testInfields", "contrasts": {"alt": "someNestedValue"}, "foo": "var", "new_entry": "someValue"}
275+
276+
os.chdir(curdir)
277+
shutil.rmtree(outdir)
278+

0 commit comments

Comments
 (0)