Skip to content

Commit 16992d8

Browse files
committed
ref: isolate working with functions to avoid __future__ conflicts
1 parent 97fc8c2 commit 16992d8

File tree

8 files changed

+94
-80
lines changed

8 files changed

+94
-80
lines changed

doc/users/saving_workflows.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ This will create a file "outputtestsave.py" with the following content:
5555
from nipype.pipeline.engine import Workflow, Node, MapNode
5656
from nipype.interfaces.utility import IdentityInterface
5757
from nipype.interfaces.utility import Function
58-
from nipype.utils.misc import getsource
58+
from nipype.utils.functions import getsource
5959
from nipype.interfaces.fsl.preprocess import BET
6060
from nipype.interfaces.fsl.utils import ImageMaths
6161
# Functions

nipype/interfaces/utility/wrappers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
BaseInterfaceInputSpec, get_max_resources_used)
2525
from ..io import IOBase, add_traits
2626
from ...utils.filemanip import filename_to_list
27-
from ...utils.misc import getsource, create_function_from_source
27+
from ...utils.functions import getsource, create_function_from_source
2828

2929
logger = logging.getLogger('interface')
3030
if runtime_profile:

nipype/pipeline/engine/utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131

3232
from ...utils.filemanip import (fname_presuffix, FileNotFoundError, to_str,
3333
filename_to_list, get_related_files)
34-
from ...utils.misc import create_function_from_source, str2bool
34+
from ...utils.misc import str2bool
35+
from ...utils.functions import create_function_from_source
3536
from ...interfaces.base import (CommandLine, isdefined, Undefined,
3637
InterfaceResult)
3738
from ...interfaces.utility import IdentityInterface
@@ -100,7 +101,7 @@ def _write_inputs(node):
100101
lines[-1] = lines[-1].replace(' %s(' % funcname,
101102
' %s_1(' % funcname)
102103
funcname = '%s_1' % funcname
103-
lines.append('from nipype.utils.misc import getsource')
104+
lines.append('from nipype.utils.functions import getsource')
104105
lines.append("%s.inputs.%s = getsource(%s)" % (nodename,
105106
key,
106107
funcname))

nipype/pipeline/engine/workflows.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@
3636

3737

3838
from ... import config, logging
39-
from ...utils.misc import (unflatten, package_check, str2bool,
40-
getsource, create_function_from_source)
39+
from ...utils.misc import (unflatten, package_check, str2bool)
40+
from ...utils.functions import (getsource, create_function_from_source)
4141
from ...interfaces.base import (traits, InputMultiPath, CommandLine,
4242
Undefined, TraitedSpec, DynamicTraitedSpec,
4343
Bunch, InterfaceResult, md5, Interface,

nipype/utils/functions.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Handles custom functions used in Function interface. Future imports
4+
are avoided to keep namespace as clear as possible.
5+
"""
6+
from builtins import next, str
7+
from future.utils import raise_from
8+
import inspect
9+
from textwrap import dedent
10+
11+
def getsource(function):
12+
"""Returns the source code of a function"""
13+
return dedent(inspect.getsource(function))
14+
15+
16+
def create_function_from_source(function_source, imports=None):
17+
"""Return a function object from a function source
18+
19+
Parameters
20+
----------
21+
function_source : unicode string
22+
unicode string defining a function
23+
imports : list of strings
24+
list of import statements in string form that allow the function
25+
to be executed in an otherwise empty namespace
26+
"""
27+
ns = {}
28+
import_keys = []
29+
30+
try:
31+
if imports is not None:
32+
for statement in imports:
33+
exec(statement, ns)
34+
import_keys = list(ns.keys())
35+
exec(function_source, ns)
36+
37+
except Exception as e:
38+
msg = 'Error executing function\n{}\n'.format(function_source)
39+
msg += ("Functions in connection strings have to be standalone. "
40+
"They cannot be declared either interactively or inside "
41+
"another function or inline in the connect string. Any "
42+
"imports should be done inside the function.")
43+
raise_from(RuntimeError(msg), e)
44+
ns_funcs = list(set(ns) - set(import_keys + ['__builtins__']))
45+
assert len(ns_funcs) == 1, "Function or inputs are ill-defined"
46+
func = ns[ns_funcs[0]]
47+
return func

nipype/utils/misc.py

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# vi: set ft=python sts=4 ts=4 sw=4 et:
44
"""Miscellaneous utility functions
55
"""
6-
from __future__ import print_function, division, absolute_import
6+
from __future__ import print_function, unicode_literals, division, absolute_import
77
from future import standard_library
88
standard_library.install_aliases()
99
from builtins import next, str
@@ -66,47 +66,6 @@ def trim(docstring, marker=None):
6666
return '\n'.join(trimmed)
6767

6868

69-
def getsource(function):
70-
"""Returns the source code of a function"""
71-
src = dedent(inspect.getsource(function))
72-
return src
73-
74-
75-
def create_function_from_source(function_source, imports=None):
76-
"""Return a function object from a function source
77-
78-
Parameters
79-
----------
80-
function_source : pickled string
81-
string in pickled form defining a function
82-
imports : list of strings
83-
list of import statements in string form that allow the function
84-
to be executed in an otherwise empty namespace
85-
"""
86-
ns = {}
87-
import_keys = []
88-
try:
89-
if imports is not None:
90-
for statement in imports:
91-
exec(statement, ns)
92-
import_keys = list(ns.keys())
93-
exec(function_source, ns)
94-
95-
except Exception as e:
96-
msg = '\nError executing function:\n %s\n' % function_source
97-
msg += '\n'.join(["Functions in connection strings have to be standalone.",
98-
"They cannot be declared either interactively or inside",
99-
"another function or inline in the connect string. Any",
100-
"imports should be done inside the function"
101-
])
102-
raise_from(RuntimeError(msg), e)
103-
ns_funcs = list(set(ns) - set(import_keys + ['__builtins__']))
104-
assert len(ns_funcs) == 1, "Function or inputs are ill-defined"
105-
funcname = ns_funcs[0]
106-
func = ns[funcname]
107-
return func
108-
109-
11069
def find_indices(condition):
11170
"Return the indices where ravel(condition) is true"
11271
res, = np.nonzero(np.ravel(condition))

nipype/utils/tests/test_functions.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# -*- coding: utf-8 -*-
2+
import sys
3+
import pytest
4+
from nipype.utils.functions import (getsource, create_function_from_source)
5+
6+
def _func1(x):
7+
return x**3
8+
9+
def test_func_to_str():
10+
11+
def func1(x):
12+
return x**2
13+
14+
# Should be ok with both functions!
15+
for f in _func1, func1:
16+
f_src = getsource(f)
17+
f_recreated = create_function_from_source(f_src)
18+
assert f(2.3) == f_recreated(2.3)
19+
20+
def test_func_to_str_err():
21+
bad_src = "obbledygobbledygook"
22+
with pytest.raises(RuntimeError): create_function_from_source(bad_src)
23+
24+
@pytest.mark.skipif(sys.version_info[0] > 2, reason="breaks python 3")
25+
def test_func_py2():
26+
def is_string():
27+
return isinstance('string', str)
28+
29+
def print_statement():
30+
# test python 2 compatibility
31+
exec('print ""')
32+
33+
wrapped_func = create_function_from_source(getsource(is_string))
34+
assert is_string() == wrapped_func()
35+
36+
wrapped_func2 = create_function_from_source(getsource(print_statement))
37+
wrapped_func2()

nipype/utils/tests/test_misc.py

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@
77
from builtins import next
88

99
import pytest
10-
import sys
1110

12-
from nipype.utils.misc import (container_to_string, getsource,
13-
create_function_from_source, str2bool, flatten,
14-
unflatten)
11+
from nipype.utils.misc import (container_to_string, str2bool,
12+
flatten, unflatten)
1513

1614

1715
def test_cont_to_str():
@@ -36,26 +34,6 @@ def test_cont_to_str():
3634
assert (container_to_string(123) == '123')
3735

3836

39-
def _func1(x):
40-
return x**3
41-
42-
43-
def test_func_to_str():
44-
45-
def func1(x):
46-
return x**2
47-
48-
# Should be ok with both functions!
49-
for f in _func1, func1:
50-
f_src = getsource(f)
51-
f_recreated = create_function_from_source(f_src)
52-
assert f(2.3) == f_recreated(2.3)
53-
54-
def test_func_to_str_err():
55-
bad_src = "obbledygobbledygook"
56-
with pytest.raises(RuntimeError): create_function_from_source(bad_src)
57-
58-
5937
@pytest.mark.parametrize("string, expected", [
6038
("yes", True), ("true", True), ("t", True), ("1", True),
6139
("no", False), ("false", False), ("n", False), ("f", False), ("0", False)
@@ -82,11 +60,3 @@ def test_flatten():
8260

8361
back = unflatten([], [])
8462
assert back == []
85-
86-
@pytest.mark.skipif(sys.version_info[0] > 2, reason="test unicode in functions")
87-
def test_func_py2():
88-
def is_string():
89-
return isinstance('string', str)
90-
91-
wrapped_func = create_function_from_source(getsource(is_string))
92-
assert is_string() == wrapped_func()

0 commit comments

Comments
 (0)