Commit a5a74061 authored by Paul McCarthy's avatar Paul McCarthy 🚵
Browse files

Merge branch 'rf/wrapper-run-options' into 'master'

RF: Get rid of global RUN_OPTIONS. Provide a context manager instead which auto-restores

See merge request fsl/fslpy!356
parents 7723dbd7 b06ca998
Pipeline #16199 failed with stages
in 15 minutes and 54 seconds
......@@ -13,15 +13,15 @@ Added
* New :func:`.standard_space_roi` and :func:`.fslswapdim` wrapper functions
for the FSL commands (!351, !354).
* New :mod:`fsl.wrappers.first` wrapper functions (!355).
* New :func:`.wrapperconfig` context manager function, which allows the
default values for arguments passed by the :mod:`fsl.wrappers` functions to
the :func:`fsl.utils.run.run` function to be changed (!352, !356).
Changed
^^^^^^^
* The default values for arguments passed by the :mod:`fsl.wrappers` functions
to the :func:`fsl.utils.run.run` function can now be changed, via the
:attr:`fsl.wrappers.RUN_OPTIONS` dictionary (!352).
* The :class:`fsl.wrappers.fslmaths.fslmaths` and
:class:`fsl.wrappers.fslstats.fslstats` wrapper functions have been updated
to accept arguments destined for :func:`fsl.utils.run.run` (!352).
......
......@@ -169,8 +169,8 @@ def run(*args, **kwargs):
- stderr: Optional file-like object to which the command's
standard error stream can be forwarded.
- cmd: Optional file-like object to which the command
itself is logged.
- cmd: Optional file-like or callable to which
the command itself is logged.
All other keyword arguments are passed through to the ``subprocess.Popen``
object (via :func:`_realrun`), unless ``submit=True``, in which case they
......@@ -275,8 +275,8 @@ def _realrun(tee, logStdout, logStderr, logCmd, *args, **kwargs):
:arg logStderr: Optional file-like object to which the command's standard
error stream can be forwarded.
:arg logCmd: Optional file-like object to which the command itself is
logged.
:arg logCmd: Optional file-like or callable to which the command itself
is logged.
:arg args: Command to run
......@@ -319,9 +319,13 @@ def _realrun(tee, logStdout, logStderr, logCmd, *args, **kwargs):
if logStdout is not None: outstreams.append(logStdout)
if logStderr is not None: errstreams.append(logStderr)
# log the command if requested
if logCmd is not None:
cmd = ' '.join(args) + '\n'
# log the command if requested.
# logCmd can be a callable, or
# can be a file-like.
cmd = ' '.join(args) + '\n'
if callable(logCmd):
logCmd(cmd)
elif logCmd is not None:
if 'b' in getattr(logCmd, 'mode', 'w'):
logCmd.write(cmd.encode('utf-8'))
else:
......
......@@ -98,7 +98,7 @@ decorators.
from fsl.wrappers.wrapperutils import (LOAD,
RUN_OPTIONS)
wrapperconfig)
from fsl.wrappers import tbss
from fsl.wrappers.bet import (bet,
robustfov)
......
......@@ -102,6 +102,7 @@ import logging
import tempfile
import warnings
import functools
import contextlib
import nibabel as nib
import numpy as np
......@@ -148,19 +149,6 @@ def _unwrap(func):
return func
RUN_OPTIONS = {
'stdout' : True,
'stderr' : True,
'exitcode' : False,
'submit' : None,
'log' : {'tee' : True},
'cmdonly' : False
}
"""Contains default settings for :func:`fsl.utils.run.run`. These are used
by the :func:`cmdwrapper` and :func:`fslwrapper` decorators.
"""
def genxwrapper(func, runner):
"""This function is used by :func:`cmdwrapper` and :func:`fslwrapper`.
It is not intended to be used in any other circumstances.
......@@ -188,20 +176,22 @@ def genxwrapper(func, runner):
- ``cmdonly``: Passed to ``runner``. Defaults to ``False``.
The default values for these arguments are stored in the
:attr:`RUN_OPTIONS` dictionary, and may be changed by modifying that
dictionary.
``genxwrapper.run_options`` dictionary. This dictionary should not be
changed directly, but rather can be temporarily modified via the
:func:`wrapperconfig` context manager function.
:arg func: A function which generates a command line.
:arg runner: Either :func:`.run.run` or :func:`.run.runfsl`.
"""
def wrapper(*args, **kwargs):
stdout = kwargs.pop('stdout', RUN_OPTIONS['stdout'])
stderr = kwargs.pop('stderr', RUN_OPTIONS['stderr'])
exitcode = kwargs.pop('exitcode', RUN_OPTIONS['exitcode'])
submit = kwargs.pop('submit', RUN_OPTIONS['submit'])
cmdonly = kwargs.pop('cmdonly', RUN_OPTIONS['cmdonly'])
logg = kwargs.pop('log', RUN_OPTIONS['log'])
opts = genxwrapper.run_options
stdout = kwargs.pop('stdout', opts['stdout'])
stderr = kwargs.pop('stderr', opts['stderr'])
exitcode = kwargs.pop('exitcode', opts['exitcode'])
submit = kwargs.pop('submit', opts['submit'])
cmdonly = kwargs.pop('cmdonly', opts['cmdonly'])
logg = kwargs.pop('log', opts['log'])
# many wrapper functions use fsl.utils.assertions
# statements to check that input arguments are
......@@ -222,6 +212,16 @@ def genxwrapper(func, runner):
return _update_wrapper(wrapper, func)
genxwrapper.run_options = {
'stdout' : True,
'stderr' : True,
'exitcode' : False,
'submit' : None,
'log' : {'tee' : True},
'cmdonly' : False
}
def cmdwrapper(func):
"""This decorator can be used on functions which generate a command line.
It will pass the return value of the function to the
......@@ -242,6 +242,23 @@ def fslwrapper(func):
return genxwrapper(func, run.runfsl)
@contextlib.contextmanager
def wrapperconfig(**kwargs):
"""Context manager to be used when calling wrapper functions. Can modify
the options/arguments that are passed to :func:`fsl.utils.run.run` when
calling a command from a wrapper function. For example::
with wrapperconfig(stdout=False):
bet('struct', 'struct_brain')
"""
opts = dict(genxwrapper.run_options)
genxwrapper.run_options.update(kwargs)
try:
yield
finally:
genxwrapper.run_options = opts
SHOW_IF_TRUE = object()
"""Constant to be used in the ``valmap`` passed to the :func:`applyArgStyle`
function.
......
......@@ -424,3 +424,14 @@ def test_run_logcmd():
assert stdout == expstdout
assert open('my_stdout', 'rt').read() == expcmd + expstdout
logged = []
def logfunc(cmd):
logged.append(cmd)
stdout = run.run('./script.sh 1 2 3', log={'cmd' : logfunc})
assert stdout == expstdout
assert logged[0] == expcmd
stdout = run.run('./script.sh 1 2 3', log={'cmd' : logfunc})
assert stdout == expstdout
assert logged[1] == expcmd
......@@ -140,3 +140,26 @@ def test_cmdonly(fileorthing):
create_mock_command(0)
run_command(fileorthing, ['mock_command'], cmdonly=True)
@pytest.mark.parametrize('fileorthing', [True, False])
def test_wrapperconfig(fileorthing):
newpath = op.pathsep.join(('.', os.environ['PATH']))
with tempdir.tempdir(), \
mock.patch.dict(os.environ, {'PATH' : newpath}):
create_mock_command(0)
# default: stdout=true, stderr=true, exitcode=False
run_command(fileorthing, ('Standard output\n', 'Standard error\n'))
with wrappers.wrapperconfig(stdout=False):
run_command(fileorthing, 'Standard error\n')
run_command(fileorthing, ('Standard error\n', 0), exitcode=True)
with wrappers.wrapperconfig(stderr=False):
run_command(fileorthing, 'Standard output\n')
run_command(fileorthing, ('Standard output\n', 0), exitcode=True)
# check that default behaviour is restored
run_command(fileorthing, ('Standard output\n', 'Standard error\n'))
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment