Skip to content

Commit 4c202a9

Browse files
authored
Merge pull request #2384 from oesteban/fix/2380
FIX: Change to interface workdir within Interface.run() instead Node
2 parents 04d8e0e + eb95fc8 commit 4c202a9

File tree

6 files changed

+79
-25
lines changed

6 files changed

+79
-25
lines changed

nipype/interfaces/base/core.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
from ... import config, logging, LooseVersion
3535
from ...utils.provenance import write_provenance
36-
from ...utils.misc import trim, str2bool
36+
from ...utils.misc import trim, str2bool, rgetcwd
3737
from ...utils.filemanip import (FileNotFoundError, split_filename, read_stream,
3838
which, get_dependencies, canonicalize_env as
3939
_canonicalize_env)
@@ -438,14 +438,16 @@ def _duecredit_cite(self):
438438
r['path'] = self.__module__
439439
due.cite(**r)
440440

441-
def run(self, **inputs):
441+
def run(self, cwd=None, **inputs):
442442
"""Execute this interface.
443443
444444
This interface will not raise an exception if runtime.returncode is
445445
non-zero.
446446
447447
Parameters
448448
----------
449+
450+
cwd : specify a folder where the interface should be run
449451
inputs : allows the interface settings to be updated
450452
451453
Returns
@@ -455,6 +457,13 @@ def run(self, **inputs):
455457
"""
456458
from ...utils.profiler import ResourceMonitor
457459

460+
# Tear-up: get current and prev directories
461+
syscwd = rgetcwd(error=False) # Recover when wd does not exist
462+
if cwd is None:
463+
cwd = syscwd
464+
465+
os.chdir(cwd) # Change to the interface wd
466+
458467
enable_rm = config.resource_monitor and self.resource_monitor
459468
force_raise = not getattr(self.inputs, 'ignore_exception', False)
460469
self.inputs.trait_set(**inputs)
@@ -471,7 +480,8 @@ def run(self, **inputs):
471480
env['DISPLAY'] = config.get_display()
472481

473482
runtime = Bunch(
474-
cwd=os.getcwd(),
483+
cwd=cwd,
484+
prevcwd=syscwd,
475485
returncode=None,
476486
duration=None,
477487
environ=env,
@@ -556,6 +566,7 @@ def run(self, **inputs):
556566
'rss_GiB': (vals[:, 2] / 1024).tolist(),
557567
'vms_GiB': (vals[:, 3] / 1024).tolist(),
558568
}
569+
os.chdir(syscwd)
559570

560571
return results
561572

nipype/pipeline/engine/nodes.py

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -449,26 +449,13 @@ def run(self, updatehash=False):
449449
savepkl(op.join(outdir, '_node.pklz'), self)
450450
savepkl(op.join(outdir, '_inputs.pklz'), self.inputs.get_traitsfree())
451451

452-
try:
453-
cwd = os.getcwd()
454-
except OSError:
455-
# Changing back to cwd is probably not necessary
456-
# but this makes sure there's somewhere to change to.
457-
cwd = op.split(outdir)[0]
458-
logger.warning(
459-
'Current folder "%s" does not exist, changing to "%s" instead.',
460-
os.getenv('PWD', 'unknown'), cwd)
461-
462-
os.chdir(outdir)
463452
try:
464453
result = self._run_interface(execute=True)
465454
except Exception:
466455
logger.warning('[Node] Error on "%s" (%s)', self.fullname, outdir)
467456
# Tear-up after error
468457
os.remove(hashfile_unfinished)
469458
raise
470-
finally: # Ensure we come back to the original CWD
471-
os.chdir(cwd)
472459

473460
# Tear-up after success
474461
shutil.move(hashfile_unfinished, hashfile)
@@ -586,17 +573,18 @@ def _run_command(self, execute, copyfiles=True):
586573
logger.info("[Node] Cached - collecting precomputed outputs")
587574
return result
588575

576+
outdir = self.output_dir()
589577
# Run command: either execute is true or load_results failed.
590-
runtime = Bunch(
591-
returncode=1,
592-
environ=dict(os.environ),
593-
hostname=socket.gethostname())
594578
result = InterfaceResult(
595579
interface=self._interface.__class__,
596-
runtime=runtime,
580+
runtime=Bunch(
581+
cwd=outdir,
582+
returncode=1,
583+
environ=dict(os.environ),
584+
hostname=socket.gethostname()
585+
),
597586
inputs=self._interface.inputs.get_traitsfree())
598587

599-
outdir = self.output_dir()
600588
if copyfiles:
601589
self._originputs = deepcopy(self._interface.inputs)
602590
self._copyfiles_to_wd(execute=execute)
@@ -618,7 +606,7 @@ def _run_command(self, execute, copyfiles=True):
618606
message += ', a CommandLine Interface with command:\n{}'.format(cmd)
619607
logger.info(message)
620608
try:
621-
result = self._interface.run()
609+
result = self._interface.run(cwd=outdir)
622610
except Exception as msg:
623611
result.runtime.stderr = '%s\n\n%s'.format(
624612
getattr(result.runtime, 'stderr', ''), msg)

nipype/pipeline/engine/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ def write_report(node, report_type=None, is_mapnode=False):
189189
'hostname': result.runtime.hostname,
190190
'duration': result.runtime.duration,
191191
'working_dir': result.runtime.cwd,
192+
'prev_wd': getattr(result.runtime, 'prevcwd', '<not-set>'),
192193
}
193194

194195
if hasattr(result.runtime, 'cmdline'):

nipype/utils/misc.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
absolute_import)
88
from builtins import next, str
99

10+
import os
1011
import sys
1112
import re
1213
from collections import Iterator
14+
from warnings import warn
1315

1416
from distutils.version import LooseVersion
1517

@@ -301,3 +303,25 @@ def dict_diff(dold, dnew, indent=0):
301303
diff.insert(diffkeys, "Some dictionary entries had differing values:")
302304

303305
return textwrap_indent('\n'.join(diff), ' ' * indent)
306+
307+
308+
def rgetcwd(error=True):
309+
"""
310+
Robust replacement for getcwd when folders get removed
311+
If error==True, this is just an alias for os.getcwd()
312+
"""
313+
if error:
314+
return os.getcwd()
315+
316+
try:
317+
cwd = os.getcwd()
318+
except OSError as exc:
319+
# Changing back to cwd is probably not necessary
320+
# but this makes sure there's somewhere to change to.
321+
cwd = os.getenv('PWD')
322+
if cwd is None:
323+
raise OSError((
324+
exc.errno, 'Current directory does not exist anymore, '
325+
'and nipype was not able to guess it from the environment'))
326+
warn('Current folder does not exist, replacing with "%s" instead.' % cwd)
327+
return cwd

nipype/utils/tests/test_misc.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from future import standard_library
55
standard_library.install_aliases()
66

7+
import os
8+
from shutil import rmtree
79
from builtins import next
810

911
import pytest
@@ -60,3 +62,30 @@ def test_flatten():
6062

6163
back = unflatten([], [])
6264
assert back == []
65+
66+
67+
def test_rgetcwd(monkeypatch, tmpdir):
68+
from ..misc import rgetcwd
69+
oldpath = tmpdir.strpath
70+
tmpdir.mkdir("sub").chdir()
71+
newpath = os.getcwd()
72+
73+
# Path still there
74+
assert rgetcwd() == newpath
75+
76+
# Remove path
77+
rmtree(newpath, ignore_errors=True)
78+
with pytest.raises(OSError):
79+
os.getcwd()
80+
81+
monkeypatch.setenv('PWD', oldpath)
82+
assert rgetcwd(error=False) == oldpath
83+
84+
# Test when error should be raised
85+
with pytest.raises(OSError):
86+
rgetcwd()
87+
88+
# Deleted env variable
89+
monkeypatch.delenv('PWD')
90+
with pytest.raises(OSError):
91+
rgetcwd(error=False)

nipype/utils/tests/test_provenance.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@
1111
from nipype.utils.provenance import ProvStore, safe_encode
1212

1313

14-
def test_provenance():
15-
ps = ProvStore()
14+
def test_provenance(tmpdir):
1615
from nipype.interfaces.base import CommandLine
16+
tmpdir.chdir()
17+
ps = ProvStore()
1718
results = CommandLine('echo hello').run()
1819
ps.add_results(results)
1920
provn = ps.g.get_provn()

0 commit comments

Comments
 (0)