diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f85840ef56f69a20c2fbdd3c2f80466358e97a98..c6e789223f5e5b398572c612185b0a967266f8dc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,20 @@ This document contains the ``fslpy`` release history in reverse chronological order. +3.3.3 (Wednesday 13th October 2020) +----------------------------------- + + +Changed +^^^^^^^ + + +* The :func:`.fileOrImage` (and related) decorators will not manipulate the + return value of a decorated function if an argument ``cmdonly=True`` is + passed. This is so that wrapper functions will directly return the command + that would be executed when ``cmdonly=True``. + + 3.3.2 (Tuesday 12th October 2020) --------------------------------- diff --git a/fsl/wrappers/wrapperutils.py b/fsl/wrappers/wrapperutils.py index 2bfbf500c69dcef76c1e3b0a3a2de6fb8a043eb1..51f269bcbdf3f700c17145b14cf3a5aa67dbf246 100644 --- a/fsl/wrappers/wrapperutils.py +++ b/fsl/wrappers/wrapperutils.py @@ -482,25 +482,27 @@ class FileOrThing(object): the dictionary. - **Cluster submission** + **Exceptions** - The above description holds in all situations, except when an argument - called ``submit`` is passed, and is set to a value which evaluates to - ``True``. In this case, the ``FileOrThing`` decorator will pass all - arguments straight through to the decorated function, and will return its - return value unchanged. - + The above description holds in all situations, except when arguments called + ``submit`` and/or ``cmdonly`` are passed, and are set to values which + evaluate to ``True``. In this case, the ``FileOrThing`` decorator will pass + all arguments straight through to the decorated function, and will return + its return value unchanged. This is because most functions that are decorated with the :func:`fileOrImage` or :func:`fileOrArray` decorators will invoke a call - to :func:`.run.run` or :func:`.runfsl`, where a value of ``submit=True`` - will cause the command to be executed asynchronously on a cluster - platform. + to :func:`.run.run` or :func:`.runfsl`, where: + - a value of ``submit=True`` will cause the command to be executed + asynchronously on a cluster platform. + - a value of ``cmdonly=True`` will cause the command to *not* be executed, + but instead the command that would have been executed is returned. A :exc:`ValueError` will be raised if the decorated function is called - with ``submit=True``, and with any in-memory objects or ``LOAD`` symbols. + with ``submit=True`` and/or ``cmdonly=True``, and with any in-memory + objects or ``LOAD`` symbols. **Example** @@ -684,9 +686,11 @@ class FileOrThing(object): # Special case - if fsl.utils.run[fsl] is # being decorated (e.g. via cmdwrapper/ - # fslwrapper), and submit=True, this call - # will ultimately submit the job to the - # cluster, and will return immediately. + # fslwrapper), and submit=True or + # cmdonly=True, this call will ultimately + # submit the job to the cluster, or will + # return the command that would have been + # executed, and will return immediately. # # We error if we are given any in-memory # things, or LOAD symbols. @@ -694,7 +698,8 @@ class FileOrThing(object): # n.b. testing values to be strings could # interfere with the fileOrText decorator. # Possible solution is to use pathlib? - if kwargs.get('submit', False): + if kwargs.get('submit', False) or \ + kwargs.get('cmdonly', False): allargs = {**dict(zip(argnames, args)), **kwargs} for name, val in allargs.items(): if (name in self.__things) and \ diff --git a/tests/test_wrappers/test_wrapperutils.py b/tests/test_wrappers/test_wrapperutils.py index d19e3ed13374217fb4d7e393ee5d87ccb5327802..7e2116d1d3c3a7d5cf4c457fff4d84447b31de93 100644 --- a/tests/test_wrappers/test_wrapperutils.py +++ b/tests/test_wrappers/test_wrapperutils.py @@ -714,13 +714,15 @@ def test_fileOrThing_chained_outprefix(): assert np.all(res['out_array'] == exparr) -def test_fileOrThing_submit(): +def test_fileOrThing_submit_cmdonly(): @wutils.fileOrImage('input', 'output') - def func(input, output, submit=False): + def func(input, output, submit=False, cmdonly=False): if submit: return 'submitted!' + if cmdonly: + return 'cmdonly!' img = nib.load(input) img = nib.nifti1.Nifti1Image(np.asanyarray(img.dataobj) * 2, np.eye(4)) @@ -735,7 +737,8 @@ def test_fileOrThing_submit(): result = func(img, wutils.LOAD) assert np.all(np.asanyarray(result['output'].dataobj) == exp) - assert func('input.nii.gz', 'output.nii.gz', submit=True) == 'submitted!' + assert func('input.nii.gz', 'output.nii.gz', submit=True) == 'submitted!' + assert func('input.nii.gz', 'output.nii.gz', cmdonly=True) == 'cmdonly!' with pytest.raises(ValueError): func(img, wutils.LOAD, submit=True) @@ -766,7 +769,7 @@ def test_fslwrapper(): with run.dryrun(): assert func(1, 2)[0] == expected - func(1, 2, cmdonly=True)[0] == list(shlex.split(expected)) + assert func(1, 2, cmdonly=True) == list(shlex.split(expected)) _test_script = textwrap.dedent(""" @@ -840,3 +843,21 @@ def test_fslwrapper_submit(): assert stdout.strip() == 'test_script running: 1 2' assert stderr.strip() == experr + + +@pytest.mark.unixtest +def test_cmdwrapper_fileorthing_cmdonly(): + + test_func = wutils.fileOrImage('a')(wutils.cmdwrapper(_test_script_func)) + newpath = op.pathsep.join(('.', os.environ['PATH'])) + with tempdir.tempdir(), \ + mock.patch.dict(os.environ, {'PATH' : newpath}): + + with open('test_script', 'wt') as f: + f.write(_test_script) + os.chmod('test_script', 0o755) + + ran = test_func('1', '2') + cmd = test_func('1', '2', cmdonly=True) + assert ran.stdout[0].strip() == 'test_script running: 1 2' + assert cmd == ['test_script', '1', '2']