Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 27 additions & 11 deletions nipype/conftest.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,45 @@
import importlib
import os
import shutil
from tempfile import mkdtemp
import tempfile
import pytest
import numpy as np
import py.path as pp

NIPYPE_DATADIR = os.path.realpath(
os.path.join(os.path.dirname(__file__), "testing/data")
)
temp_folder = mkdtemp()
data_dir = os.path.join(temp_folder, "data")
shutil.copytree(NIPYPE_DATADIR, data_dir)
NIPYPE_TMPDIR = tempfile.mkdtemp()
TMP_DATADIR = os.path.join(NIPYPE_TMPDIR, "data")


def pytest_configure(config):
shutil.copytree(NIPYPE_DATADIR, TMP_DATADIR)

# Pytest uses gettempdir() to construct its tmp_paths, but the logic to get
# `pytest-of-<user>/pytest-<n>` directories is contingent on not directly
# configuring the `config.option.base_temp` value.
# Instead of replicating that logic, inject a new directory into gettempdir()
#
# Use the discovered temp dir as a base, to respect user/system settings.
if ' ' not in (base_temp := tempfile.gettempdir()):
new_base = os.path.join(base_temp, "nipype tmp")
os.makedirs(new_base, exist_ok=True)
os.environ['TMPDIR'] = os.path.join(base_temp, "nipype tmp")
importlib.reload(tempfile)
assert tempfile.gettempdir() == os.path.join(base_temp, "nipype tmp")


def pytest_unconfigure(config):
shutil.rmtree(NIPYPE_TMPDIR)


@pytest.fixture(autouse=True)
def add_np(doctest_namespace):
doctest_namespace["np"] = np
doctest_namespace["os"] = os
doctest_namespace["pytest"] = pytest
doctest_namespace["datadir"] = data_dir
doctest_namespace["datadir"] = TMP_DATADIR


@pytest.fixture(scope='session', autouse=True)
Expand All @@ -33,7 +54,7 @@ def _docdir(request):
doctest_plugin = request.config.pluginmanager.getplugin("doctest")
if isinstance(request.node, doctest_plugin.DoctestItem):
# Get the fixture dynamically by its name.
tmpdir = pp.local(data_dir)
tmpdir = pp.local(TMP_DATADIR)

# Chdir only for the duration of the test.
with tmpdir.as_cwd():
Expand All @@ -42,8 +63,3 @@ def _docdir(request):
else:
# For normal tests, we have to yield, since this is a yield-fixture.
yield


def pytest_unconfigure(config):
# Delete temp folder after session is finished
shutil.rmtree(temp_folder)
2 changes: 2 additions & 0 deletions nipype/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def get_nipype_gitversion():
DATEUTIL_MIN_VERSION = "2.2"
SIMPLEJSON_MIN_VERSION = "3.8.0"
PROV_MIN_VERSION = "1.5.2"
LXML_MIN_VERSION = "5.0"
RDFLIB_MIN_VERSION = "5.0.0"
CLICK_MIN_VERSION = "6.6.0"
PYDOT_MIN_VERSION = "1.2.3"
Expand Down Expand Up @@ -139,6 +140,7 @@ def get_nipype_gitversion():
"numpy>=%s" % NUMPY_MIN_VERSION,
"packaging",
"prov>=%s" % PROV_MIN_VERSION,
f"lxml>={LXML_MIN_VERSION}", # prov < 2.0.2 depended on lxml, now it's an [xml] extra
"pydot>=%s" % PYDOT_MIN_VERSION,
"python-dateutil>=%s" % DATEUTIL_MIN_VERSION,
"rdflib>=%s" % RDFLIB_MIN_VERSION,
Expand Down
2 changes: 1 addition & 1 deletion nipype/interfaces/ants/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -928,7 +928,7 @@ class LabelGeometry(ANTSCommand):
>>> label_extract.inputs.dimension = 3
>>> label_extract.inputs.label_image = 'atlas.nii.gz'
>>> label_extract.cmdline
'LabelGeometryMeasures 3 atlas.nii.gz [] atlas.csv'
"LabelGeometryMeasures 3 atlas.nii.gz '[]' atlas.csv"

>>> label_extract.inputs.intensity_image = 'ants_Warp.nii.gz'
>>> label_extract.cmdline
Expand Down
11 changes: 10 additions & 1 deletion nipype/interfaces/base/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

from ...external.due import due

from .traits_extension import traits, isdefined, Undefined
from .traits_extension import traits, isdefined, BasePath, Undefined
from .specs import (
BaseInterfaceInputSpec,
CommandLineInputSpec,
Expand Down Expand Up @@ -798,6 +798,13 @@ def _format_arg(self, name, trait_spec, value):
# type-checking code here as well
sep = trait_spec.sep if trait_spec.sep is not None else " "

if argstr == '%s':
inner_traits = getattr(trait_spec, "inner_traits", None)
if inner_traits and any(
t.is_trait_type(BasePath) for t in inner_traits
):
values = [shlex.quote(elt) for elt in value]

if argstr.endswith("..."):
# repeatable option
# --id %d... will expand to
Expand All @@ -807,6 +814,8 @@ def _format_arg(self, name, trait_spec, value):
else:
return argstr % sep.join(str(elt) for elt in value)
else:
if argstr == '%s' and trait_spec.is_trait_type(BasePath):
return shlex.quote(value)
# Append options using format string.
return argstr % value

Expand Down
16 changes: 16 additions & 0 deletions nipype/interfaces/base/tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,3 +608,19 @@ def _run_interface(self, runtime):

with pytest.raises(RuntimeError):
BrokenRuntime().run()


def test_CommandLine_escape(tmp_path):
test_file = tmp_path / "test file.txt"
test_file.write_text("content")

class InputSpec(nib.TraitedSpec):
in_file = nib.File(desc="a file", exists=True, argstr="%s")

class CatCommand(nib.CommandLine):
input_spec = InputSpec
_cmd = "cat"

command = CatCommand(in_file=str(test_file))
result = command.run()
assert result.runtime.stdout == "content"
2 changes: 1 addition & 1 deletion nipype/interfaces/freesurfer/preprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -1975,7 +1975,7 @@ def _list_outputs(self):
outputs["out_fsl_file"] = op.abspath(_in.out_fsl_file)

if isdefined(_in.init_cost_file):
if isinstance(_in.out_fsl_file, bool):
if isinstance(_in.init_cost_file, bool):
outputs["init_cost_file"] = outputs["out_reg_file"] + ".initcost"
else:
outputs["init_cost_file"] = op.abspath(_in.init_cost_file)
Expand Down
3 changes: 2 additions & 1 deletion nipype/pipeline/plugins/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from copy import deepcopy
from glob import glob
import os
import shlex
import shutil
from time import sleep, time
from traceback import format_exception
Expand Down Expand Up @@ -564,7 +565,7 @@ def _submit_job(self, node, updatehash=False):
batch_dir, name = os.path.split(pyscript)
name = ".".join(name.split(".")[:-1])
batchscript = "\n".join(
(self._template.rstrip("\n"), f"{sys.executable} {pyscript}")
(self._template.rstrip("\n"), shlex.join([sys.executable, pyscript]))
)
batchscriptfile = os.path.join(batch_dir, "batchscript_%s.sh" % name)
with open(batchscriptfile, "w") as fp:
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ extras =
full: ssh
full: nipy
setenv =
NO_ET=1
FSLOUTPUTTYPE=NIFTI_GZ
pre: PIP_EXTRA_INDEX_URL=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple
pre: UV_EXTRA_INDEX_URL=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple
Expand Down
Loading