From 4237c652c21696612180f5dbe440540fbfba6db3 Mon Sep 17 00:00:00 2001 From: Alexandre Manhaes Savio Date: Thu, 8 Sep 2016 19:19:45 +0200 Subject: [PATCH 01/17] wip: move first nipype commands to a group command --- bin/nipype_cmd | 3 ++ bin/nipype_crash_search | 82 -------------------------------- bin/nipype_display_crash | 85 --------------------------------- bin/nipype_display_pklz | 36 -------------- nipype/scripts/__init__.py | 1 + nipype/scripts/cli.py | 82 ++++++++++++++++++++++++++++++++ nipype/scripts/crash_files.py | 88 +++++++++++++++++++++++++++++++++++ requirements.txt | 2 +- setup.py | 5 ++ 9 files changed, 180 insertions(+), 204 deletions(-) delete mode 100755 bin/nipype_crash_search delete mode 100755 bin/nipype_display_crash delete mode 100644 bin/nipype_display_pklz create mode 100644 nipype/scripts/__init__.py create mode 100644 nipype/scripts/cli.py create mode 100644 nipype/scripts/crash_files.py diff --git a/bin/nipype_cmd b/bin/nipype_cmd index afa4dd3909..e8d514d38d 100755 --- a/bin/nipype_cmd +++ b/bin/nipype_cmd @@ -1,8 +1,11 @@ #!python # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: + import sys from nipype.utils.nipype_cmd import main +import click + if __name__ == '__main__': main(sys.argv) diff --git a/bin/nipype_crash_search b/bin/nipype_crash_search deleted file mode 100755 index e6cfd20088..0000000000 --- a/bin/nipype_crash_search +++ /dev/null @@ -1,82 +0,0 @@ -#!python -"""Search for tracebacks inside a folder of nipype crash -log files that match a given regular expression. - -Examples: -nipype_crash_search -d nipype/wd/log -r '.*subject123.*' -""" -import re -import sys -import os.path as op -from glob import glob - -from traits.trait_errors import TraitError -from nipype.utils.filemanip import loadcrash - - -def load_pklz_traceback(crash_filepath): - """ Return the traceback message in the given crash file.""" - try: - data = loadcrash(crash_filepath) - except TraitError as te: - return str(te) - except: - raise - else: - return '\n'.join(data['traceback']) - - -def iter_tracebacks(logdir): - """ Return an iterator over each file path and - traceback field inside `logdir`. - Parameters - ---------- - logdir: str - Path to the log folder. - - field: str - Field name to be read from the crash file. - - Yields - ------ - path_file: str - - traceback: str - """ - crash_files = sorted(glob(op.join(logdir, '*.pkl*'))) - - for cf in crash_files: - yield cf, load_pklz_traceback(cf) - - -def display_crash_search(logdir, regex): - rex = re.compile(regex, re.IGNORECASE) - for file, trace in iter_tracebacks(logdir): - if rex.search(trace): - print("-" * len(file)) - print(file) - print("-" * len(file)) - print(trace) - - -if __name__ == "__main__": - from argparse import ArgumentParser, RawTextHelpFormatter - - defstr = ' (default %(default)s)' - parser = ArgumentParser(prog='nipype_crash_search', - description=__doc__, - formatter_class=RawTextHelpFormatter) - parser.add_argument('-l','--logdir', type=str, dest='logdir', - action="store", default=None, - help='The working directory log file.' + defstr) - parser.add_argument('-r', '--regex', dest='regex', - default='*', - help='Regular expression to be searched in each traceback.' + defstr) - - if len(sys.argv) == 1: - parser.print_help() - exit(0) - - args = parser.parse_args() - display_crash_search(args.logdir, args.regex) - exit(0) diff --git a/bin/nipype_display_crash b/bin/nipype_display_crash deleted file mode 100755 index bb2ee584c1..0000000000 --- a/bin/nipype_display_crash +++ /dev/null @@ -1,85 +0,0 @@ -#!python -"""Displays crash information from Nipype crash files. For certain crash files, -one can rerun a failed node in a temp directory. - -Examples: - -nipype_display_crash crashfile.pklz -nipype_display_crash crashfile.pklz -r -i -nipype_display_crash crashfile.pklz -r -i - -""" - -def display_crash_files(crashfile, rerun, debug, directory): - """display crash file content and rerun if required""" - - from nipype.utils.filemanip import loadcrash - crash_data = loadcrash(crashfile) - node = None - if 'node' in crash_data: - node = crash_data['node'] - tb = crash_data['traceback'] - print("\n") - print("File: %s" % crashfile) - if node: - print("Node: %s" % node) - if node.base_dir: - print("Working directory: %s" % node.output_dir()) - else: - print("Node crashed before execution") - print("\n") - print("Node inputs:") - print(node.inputs) - print("\n") - print("Traceback: ") - print(''.join(tb)) - print ("\n") - - if rerun: - if node is None: - print("No node in crashfile. Cannot rerun") - return - print("Rerunning node") - node.base_dir = directory - node.config = {'execution': {'crashdump_dir': '/tmp'}} - try: - node.run() - except: - if debug and debug != 'ipython': - import pdb - pdb.post_mortem() - else: - raise - print("\n") - -if __name__ == "__main__": - from argparse import ArgumentParser, RawTextHelpFormatter - defstr = ' (default %(default)s)' - parser = ArgumentParser(prog='nipype_display_crash', - description=__doc__, - formatter_class=RawTextHelpFormatter) - parser.add_argument('crashfile', metavar='f', type=str, - help='crash file to display') - parser.add_argument('-r','--rerun', dest='rerun', - default=False, action="store_true", - help='rerun crashed node' + defstr) - group = parser.add_mutually_exclusive_group() - group.add_argument('-d','--debug', dest='debug', - default=False, action="store_true", - help='enable python debugger when re-executing' + defstr) - group.add_argument('-i','--ipydebug', dest='ipydebug', - default=False, action="store_true", - help='enable ipython debugger when re-executing' + defstr) - parser.add_argument('--dir', dest='directory', - default=None, - help='Directory to run the node in' + defstr) - args = parser.parse_args() - debug = 'ipython' if args.ipydebug else args.debug - if debug == 'ipython': - import sys - from IPython.core import ultratb - sys.excepthook = ultratb.FormattedTB(mode='Verbose', - color_scheme='Linux', - call_pdb=1) - display_crash_files(args.crashfile, args.rerun, - debug, args.directory) diff --git a/bin/nipype_display_pklz b/bin/nipype_display_pklz deleted file mode 100644 index cfd71bc17c..0000000000 --- a/bin/nipype_display_pklz +++ /dev/null @@ -1,36 +0,0 @@ -#!python -"""Prints the content of any .pklz file in your working directory. - -Examples: - -nipype_print_pklz _inputs.pklz -nipype_print_pklz _node.pklz -""" - -def pprint_pklz_file(pklz_file): - """ Print the content of the pklz_file. """ - from pprint import pprint - from nipype.utils.filemanip import loadpkl - - pkl_data = loadpkl(pklz_file) - pprint(pkl_data) - - -if __name__ == "__main__": - - import sys - from argparse import ArgumentParser, RawTextHelpFormatter - - defstr = ' (default %(default)s)' - parser = ArgumentParser(prog='nipype_print_pklz', - description=__doc__, - formatter_class=RawTextHelpFormatter) - parser.add_argument('pklzfile', metavar='f', type=str, - help='pklz file to display') - - if len(sys.argv) == 1: - parser.print_help() - exit(0) - - args = parser.parse_args() - pprint_pklz_file(args.pklzfile) diff --git a/nipype/scripts/__init__.py b/nipype/scripts/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/nipype/scripts/__init__.py @@ -0,0 +1 @@ + diff --git a/nipype/scripts/cli.py b/nipype/scripts/cli.py new file mode 100644 index 0000000000..1a0ec2c3cd --- /dev/null +++ b/nipype/scripts/cli.py @@ -0,0 +1,82 @@ +#!python +# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: + +import click + + +@click.group() +def cli(): + pass + + +@cli.command() +@click.argument('logdir', type=str) +@click.option('-r', '--regex', type=str, default='*', + help='Regular expression to be searched in each traceback.') +def search(logdir, regex): + """Search for tracebacks content. + + Search for traceback inside a folder of nipype crash log files that match + a given regular expression. + + Examples: + nipype search -d nipype/wd/log -r '.*subject123.*' + """ + import re + from .crash_files import iter_tracebacks + + rex = re.compile(regex, re.IGNORECASE) + for file, trace in iter_tracebacks(logdir): + if rex.search(trace): + click.echo("-" * len(file)) + click.echo(file) + click.echo("-" * len(file)) + click.echo(trace) + + +@cli.command() +@click.argument('crashfile', type=str) +@click.option('-r', '--rerun', is_flag=True, flag_value=True, + help='Rerun crashed node.') +@click.option('-d', '--debug', is_flag=True, flag_value=True, + help='Enable Python debugger when re-executing.') +@click.option('-i', '--ipydebug', is_flag=True, flag_value=True, + help='Enable IPython debugger when re-executing.') +@click.option('--dir', type=str, + help='Directory where to run the node in.') +def crash(crashfile, rerun, debug, ipydebug, directory): + """Display Nipype crash files. + + For certain crash files, one can rerun a failed node in a temp directory. + + Examples: + nipype crash crashfile.pklz + nipype crash crashfile.pklz -r -i + nipype crash crashfile.pklz -r -i + """ + from .crash_files import display_crash_file + + debug = 'ipython' if ipydebug else debug + if debug == 'ipython': + import sys + from IPython.core import ultratb + sys.excepthook = ultratb.FormattedTB(mode='Verbose', + color_scheme='Linux', + call_pdb=1) + display_crash_file(crashfile, rerun, debug, directory) + + +@cli.command() +@click.argument('pklz_file', type=str) +def show(pklz_file): + """Print the content of Nipype node .pklz file. + + Examples: + nipype show node.pklz + """ + from pprint import pprint + from ..utils.filemanip import loadpkl + + pkl_data = loadpkl(pklz_file) + pprint(pkl_data) diff --git a/nipype/scripts/crash_files.py b/nipype/scripts/crash_files.py new file mode 100644 index 0000000000..363e0abf80 --- /dev/null +++ b/nipype/scripts/crash_files.py @@ -0,0 +1,88 @@ +"""Utilities to manipulate and search through .pklz crash files.""" + +import re +import sys +import os.path as op +from glob import glob + +from traits.trait_errors import TraitError +from nipype.utils.filemanip import loadcrash + + +def load_pklz_traceback(crash_filepath): + """Return the traceback message in the given crash file.""" + try: + data = loadcrash(crash_filepath) + except TraitError as te: + return str(te) + except: + raise + else: + return '\n'.join(data['traceback']) + + +def iter_tracebacks(logdir): + """Return an iterator over each file path and + traceback field inside `logdir`. + Parameters + ---------- + logdir: str + Path to the log folder. + + field: str + Field name to be read from the crash file. + + Yields + ------ + path_file: str + + traceback: str + """ + crash_files = sorted(glob(op.join(logdir, '*.pkl*'))) + + for cf in crash_files: + yield cf, load_pklz_traceback(cf) + + +def display_crash_file(crashfile, rerun, debug, directory): + """display crash file content and rerun if required""" + from nipype.utils.filemanip import loadcrash + + crash_data = loadcrash(crashfile) + node = None + if 'node' in crash_data: + node = crash_data['node'] + tb = crash_data['traceback'] + print("\n") + print("File: %s" % crashfile) + + if node: + print("Node: %s" % node) + if node.base_dir: + print("Working directory: %s" % node.output_dir()) + else: + print("Node crashed before execution") + print("\n") + print("Node inputs:") + print(node.inputs) + print("\n") + print("Traceback: ") + print(''.join(tb)) + print ("\n") + + if rerun: + if node is None: + print("No node in crashfile. Cannot rerun") + return + print("Rerunning node") + node.base_dir = directory + node.config = {'execution': {'crashdump_dir': '/tmp'}} + try: + node.run() + except: + if debug and debug != 'ipython': + import pdb + pdb.post_mortem() + else: + raise + print("\n") diff --git a/requirements.txt b/requirements.txt index ef66036744..2ed6100f9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ nose>=1.2 future==0.15.2 simplejson>=3.8.0 prov>=1.4.0 +click>=6.6.0 xvfbwrapper psutil funcsigs -configparser diff --git a/setup.py b/setup.py index cefae4d443..df2d950d24 100755 --- a/setup.py +++ b/setup.py @@ -401,6 +401,7 @@ def main(**extra_args): 'nipype.pipeline.engine.tests', 'nipype.pipeline.plugins', 'nipype.pipeline.plugins.tests', + 'nipype.scripts', 'nipype.testing', 'nipype.testing.data', 'nipype.testing.data.bedpostxout', @@ -438,6 +439,10 @@ def main(**extra_args): # only a workaround to get things started -- not a solution package_data={'nipype': testdatafiles}, scripts=glob('bin/*') + ['nipype/external/fsl_imglob.py'], + entry_points=''' + [console_scripts] + nipype=nipype.scripts.cli:cli + ''', cmdclass=cmdclass, **extra_args ) From 578970eb08acc7b64aee0a67a45ad7a8bbacc183 Mon Sep 17 00:00:00 2001 From: Alexandre Manhaes Savio Date: Fri, 9 Sep 2016 17:10:41 +0200 Subject: [PATCH 02/17] wip: add "nipype run" command --- nipype/scripts/cli.py | 152 +++++++++++++++++++++++++++---- nipype/scripts/instance.py | 74 +++++++++++++++ nipype/scripts/utils.py | 69 ++++++++++++++ nipype/utils/nipype2boutiques.py | 28 ------ nipype/utils/nipype_cmd.py | 49 +++++----- 5 files changed, 299 insertions(+), 73 deletions(-) create mode 100644 nipype/scripts/instance.py create mode 100644 nipype/scripts/utils.py diff --git a/nipype/scripts/cli.py b/nipype/scripts/cli.py index 1a0ec2c3cd..bfcf51f317 100644 --- a/nipype/scripts/cli.py +++ b/nipype/scripts/cli.py @@ -1,18 +1,26 @@ #!python # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: - import click +from .instance import list_interfaces +from .utils import (CONTEXT_SETTINGS, + UNKNOWN_OPTIONS, + ExistingDirPath, + ExistingFilePath, + RegularExpression, + PythonModule) + -@click.group() +# declare the CLI group +@click.group(context_settings=CONTEXT_SETTINGS) def cli(): pass -@cli.command() -@click.argument('logdir', type=str) -@click.option('-r', '--regex', type=str, default='*', +@cli.command(context_settings=CONTEXT_SETTINGS) +@click.argument('logdir', type=ExistingDirPath) +@click.option('-r', '--regex', type=RegularExpression(), default='*', help='Regular expression to be searched in each traceback.') def search(logdir, regex): """Search for tracebacks content. @@ -20,40 +28,37 @@ def search(logdir, regex): Search for traceback inside a folder of nipype crash log files that match a given regular expression. - Examples: - nipype search -d nipype/wd/log -r '.*subject123.*' + Examples:\n + nipype search nipype/wd/log -r '.*subject123.*' """ - import re from .crash_files import iter_tracebacks - rex = re.compile(regex, re.IGNORECASE) for file, trace in iter_tracebacks(logdir): - if rex.search(trace): + if regex.search(trace): click.echo("-" * len(file)) click.echo(file) click.echo("-" * len(file)) click.echo(trace) -@cli.command() -@click.argument('crashfile', type=str) +@cli.command(context_settings=CONTEXT_SETTINGS) +@click.argument('crashfile', type=ExistingFilePath,) @click.option('-r', '--rerun', is_flag=True, flag_value=True, help='Rerun crashed node.') @click.option('-d', '--debug', is_flag=True, flag_value=True, help='Enable Python debugger when re-executing.') @click.option('-i', '--ipydebug', is_flag=True, flag_value=True, help='Enable IPython debugger when re-executing.') -@click.option('--dir', type=str, +@click.option('--dir', type=ExistingDirPath, help='Directory where to run the node in.') def crash(crashfile, rerun, debug, ipydebug, directory): """Display Nipype crash files. For certain crash files, one can rerun a failed node in a temp directory. - Examples: - nipype crash crashfile.pklz - nipype crash crashfile.pklz -r -i - nipype crash crashfile.pklz -r -i + Examples:\n + nipype crash crashfile.pklz\n + nipype crash crashfile.pklz -r -i\n """ from .crash_files import display_crash_file @@ -67,12 +72,12 @@ def crash(crashfile, rerun, debug, ipydebug, directory): display_crash_file(crashfile, rerun, debug, directory) -@cli.command() -@click.argument('pklz_file', type=str) +@cli.command(context_settings=CONTEXT_SETTINGS) +@click.argument('pklz_file', type=ExistingFilePath) def show(pklz_file): """Print the content of Nipype node .pklz file. - Examples: + Examples:\n nipype show node.pklz """ from pprint import pprint @@ -80,3 +85,110 @@ def show(pklz_file): pkl_data = loadpkl(pklz_file) pprint(pkl_data) + + +@cli.command(context_settings=UNKNOWN_OPTIONS) +@click.argument('module', type=PythonModule(), required=False) +@click.argument('interface', type=str, required=False) +@click.option('--list', is_flag=True, flag_value=True, + help='List the available Interfaces inside the given module.') +@click.option('-h', '--help', is_flag=True, flag_value=True, + help='Show help message and exit.') +@click.pass_context +def run(ctx, module, interface, list, help): + """Run a Nipype Interface. + + Examples:\n + nipype run nipype.interfaces.nipy --list\n + nipype run nipype.interfaces.nipy ComputeMask --help + """ + import argparse + from .utils import add_args_options + from ..utils.nipype_cmd import run_instance + + # print run command help if no arguments are given + module_given = bool(module) + if not module_given: + click.echo(ctx.command.get_help(ctx)) + + # print the list available interfaces for the given module + elif (module_given and list) or (module_given and not interface): + iface_names = list_interfaces(module) + click.echo('Available Interfaces:') + for if_name in iface_names: + click.echo(' {}'.format(if_name)) + + # check the interface + elif (module_given and interface): + description = "Run {}".format(interface) + prog = " ".join([ctx.command_path, + module.__name__, + interface] + + ctx.args) + iface_parser = argparse.ArgumentParser(description=description, + prog=prog) + + # instantiate the interface + node = getattr(module, interface)() + iface_parser = add_args_options(iface_parser, node) + + if not ctx.args: + # print the interface help + iface_parser.print_help() + else: + # run the interface + args = iface_parser.parse_args(args=ctx.args) + run_instance(node, args) + + +# +# @cli.command(context_settings=CONTEXT_SETTINGS.update(dict(ignore_unknown_options=True,))) +# @click.argument('-f', '--format', type=click.Choice(['boutiques'])) +# @click.argument('format_args', nargs=-1, type=click.UNPROCESSED) +# @click.pass_context +# def convert(ctx, format): +# """Export nipype interfaces to other formats.""" +# if format == 'boutiques': +# ctx.forward(to_boutiques) +# import pdb; pdb.set_trace() +# ctx.invoke(to_boutiques, **format_args) +# +# +# @cli.command(context_settings=CONTEXT_SETTINGS) +# @click.option("-i", "--interface", type=str, required=True, +# help="Name of the Nipype interface to export.") +# @click.option("-m", "--module", type=PythonModule(), required=True, +# help="Module where the interface is defined.") +# @click.option("-o", "--output", type=str, required=True, +# help="JSON file name where the Boutiques descriptor will be written.") +# @click.option("-t", "--ignored-template-inputs", type=str, multiple=True, +# help="Interface inputs ignored in path template creations.") +# @click.option("-d", "--docker-image", type=str, +# help="Name of the Docker image where the Nipype interface is available.") +# @click.option("-r", "--docker-index", type=str, +# help="Docker index where the Docker image is stored (e.g. http://index.docker.io).") +# @click.option("-n", "--ignore-template-numbers", is_flag=True, flag_value=True, +# help="Ignore all numbers in path template creations.") +# @click.option("-v", "--verbose", is_flag=True, flag_value=True, +# help="Enable verbose output.") +# def to_boutiques(interface, module, output, ignored_template_inputs, +# docker_image, docker_index, ignore_template_numbers, +# verbose): +# """Nipype Boutiques exporter. +# +# See Boutiques specification at https://github.com/boutiques/schema. +# """ +# from nipype.utils.nipype2boutiques import generate_boutiques_descriptor +# +# # Generates JSON string +# json_string = generate_boutiques_descriptor(module, +# interface, +# ignored_template_inputs, +# docker_image, +# docker_index, +# verbose, +# ignore_template_numbers) +# +# # Writes JSON string to file +# with open(output, 'w') as f: +# f.write(json_string) diff --git a/nipype/scripts/instance.py b/nipype/scripts/instance.py new file mode 100644 index 0000000000..18339dbddc --- /dev/null +++ b/nipype/scripts/instance.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +""" +Import lib and class meta programming utilities. +""" +import inspect +import importlib + +from ..interfaces.base import Interface + + +def import_module(module_path): + """Import any module to the global Python environment. + The module_path argument specifies what module to import in + absolute or relative terms (e.g. either pkg.mod or ..mod). + If the name is specified in relative terms, then the package argument + must be set to the name of the package which is to act as the anchor + for resolving the package name (e.g. import_module('..mod', 'pkg.subpkg') + + will import pkg.mod). + + Parameters + ---------- + module_path: str + Path to the module to be imported + + Returns + ------- + The specified module will be inserted into sys.modules and returned. + """ + try: + mod = importlib.import_module(module_path) + return mod + except: + raise ImportError('Error when importing object {}.'.format(module_path)) + + +def list_interfaces(module): + """Return a list with the names of the Interface subclasses inside + the given module. + """ + iface_names = [] + for k, v in sorted(list(module.__dict__.items())): + if inspect.isclass(v) and issubclass(v, Interface): + iface_names.append(k) + return iface_names + + +def instantiate_this(class_path, init_args): + """Instantiates an object of the class in class_path with the given + initialization arguments. + + Parameters + ---------- + class_path: str + String to the path of the class. + + init_args: dict + Dictionary of the names and values of the initialization arguments + to the class + + Return + ------ + Instantiated object + """ + try: + cls = import_this(class_path) + if init_args is None: + return cls() + else: + return cls(**init_args) + except: + raise RuntimeError('Error instantiating class {} ' + 'with the arguments {}.'.format(class_path, + init_args)) diff --git a/nipype/scripts/utils.py b/nipype/scripts/utils.py new file mode 100644 index 0000000000..29d73ab6e2 --- /dev/null +++ b/nipype/scripts/utils.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +""" +Utilities for the CLI functions. +""" +from __future__ import print_function, division, unicode_literals, absolute_import +import re + +import click + +from .instance import import_module +from ..interfaces.base import InputMultiPath, traits + + +# different context options +CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) +UNKNOWN_OPTIONS = dict(allow_extra_args=True, + ignore_unknown_options=True) + + +# specification of existing ParamTypes +ExistingDirPath = click.Path(exists=True, file_okay=False, resolve_path=True) +ExistingFilePath = click.Path(exists=True, dir_okay=False, resolve_path=True) + + +# declare custom click.ParamType +class RegularExpression(click.ParamType): + name = 'regex' + + def convert(self, value, param, ctx): + try: + rex = re.compile(value, re.IGNORECASE) + except ValueError: + self.fail('%s is not a valid regular expression.' % value, param, ctx) + else: + return rex + + +class PythonModule(click.ParamType): + name = 'Python module' + + def convert(self, value, param, ctx): + try: + module = import_module(value) + except ValueError: + self.fail('%s is not a valid Python module.' % value, param, ctx) + else: + return module + + +def add_args_options(arg_parser, interface): + """Add arguments to `arg_parser` to create a CLI for `interface`.""" + inputs = interface.input_spec() + for name, spec in sorted(interface.inputs.traits(transient=None).items()): + desc = "\n".join(interface._get_trait_desc(inputs, name, spec))[len(name) + 2:] + args = {} + + if spec.is_trait_type(traits.Bool): + args["action"] = 'store_true' + + if hasattr(spec, "mandatory") and spec.mandatory: + if spec.is_trait_type(InputMultiPath): + args["nargs"] = "+" + arg_parser.add_argument(name, help=desc, **args) + else: + if spec.is_trait_type(InputMultiPath): + args["nargs"] = "*" + arg_parser.add_argument("--%s" % name, dest=name, + help=desc, **args) + return arg_parser diff --git a/nipype/utils/nipype2boutiques.py b/nipype/utils/nipype2boutiques.py index 8696c321f7..1258c81e65 100644 --- a/nipype/utils/nipype2boutiques.py +++ b/nipype/utils/nipype2boutiques.py @@ -21,34 +21,6 @@ import simplejson as json -def main(argv): - - # Parses arguments - parser = argparse.ArgumentParser(description='Nipype Boutiques exporter. See Boutiques specification at https://github.com/boutiques/schema.', prog=argv[0]) - parser.add_argument("-i", "--interface", type=str, help="Name of the Nipype interface to export.", required=True) - parser.add_argument("-m", "--module", type=str, help="Module where the interface is defined.", required=True) - parser.add_argument("-o", "--output", type=str, help="JSON file name where the Boutiques descriptor will be written.", required=True) - parser.add_argument("-t", "--ignored-template-inputs", type=str, help="Interface inputs ignored in path template creations.", nargs='+') - parser.add_argument("-d", "--docker-image", type=str, help="Name of the Docker image where the Nipype interface is available.") - parser.add_argument("-r", "--docker-index", type=str, help="Docker index where the Docker image is stored (e.g. http://index.docker.io).") - parser.add_argument("-n", "--ignore-template-numbers", action='store_true', default=False, help="Ignore all numbers in path template creations.") - parser.add_argument("-v", "--verbose", action='store_true', default=False, help="Enable verbose output.") - - parsed = parser.parse_args() - - # Generates JSON string - json_string = generate_boutiques_descriptor(parsed.module, - parsed.interface, - parsed.ignored_template_inputs, - parsed.docker_image, parsed.docker_index, - parsed.verbose, - parsed.ignore_template_numbers) - - # Writes JSON string to file - with open(parsed.output, 'w') as f: - f.write(json_string) - - def generate_boutiques_descriptor(module, interface_name, ignored_template_inputs, docker_image, docker_index, verbose, ignore_template_numbers): ''' Returns a JSON string containing a JSON Boutiques description of a Nipype interface. diff --git a/nipype/utils/nipype_cmd.py b/nipype/utils/nipype_cmd.py index 116dd5f18c..48792ec4ad 100644 --- a/nipype/utils/nipype_cmd.py +++ b/nipype/utils/nipype_cmd.py @@ -47,32 +47,31 @@ def add_options(parser=None, module=None, function=None): def run_instance(interface, options): - if interface: - print("setting function inputs") - - for input_name, _ in list(interface.inputs.items()): - if getattr(options, input_name) != None: - value = getattr(options, input_name) - if not isinstance(value, bool): - # traits cannot cast from string to float or int - try: - value = float(value) - except: - pass - # try to cast string input to boolean - try: - value = str2bool(value) - except: - pass + print("setting function inputs") + + for input_name, _ in list(interface.inputs.items()): + if getattr(options, input_name) != None: + value = getattr(options, input_name) + if not isinstance(value, bool): + # traits cannot cast from string to float or int + try: + value = float(value) + except: + pass + # try to cast string input to boolean try: - setattr(interface.inputs, input_name, - value) - except ValueError as e: - print("Error when setting the value of %s: '%s'" % (input_name, str(e))) - - print(interface.inputs) - res = interface.run() - print(res.outputs) + value = str2bool(value) + except: + pass + try: + setattr(interface.inputs, input_name, + value) + except ValueError as e: + print("Error when setting the value of %s: '%s'" % (input_name, str(e))) + + print(interface.inputs) + res = interface.run() + print(res.outputs) def main(argv): From b2c9867e83f2ffd70832db0068e7b84c374b3c66 Mon Sep 17 00:00:00 2001 From: Alexandre Manhaes Savio Date: Fri, 9 Sep 2016 17:13:35 +0200 Subject: [PATCH 03/17] remove unused function in scripts/instance --- nipype/scripts/instance.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/nipype/scripts/instance.py b/nipype/scripts/instance.py index 18339dbddc..2bde70ced1 100644 --- a/nipype/scripts/instance.py +++ b/nipype/scripts/instance.py @@ -43,32 +43,3 @@ def list_interfaces(module): if inspect.isclass(v) and issubclass(v, Interface): iface_names.append(k) return iface_names - - -def instantiate_this(class_path, init_args): - """Instantiates an object of the class in class_path with the given - initialization arguments. - - Parameters - ---------- - class_path: str - String to the path of the class. - - init_args: dict - Dictionary of the names and values of the initialization arguments - to the class - - Return - ------ - Instantiated object - """ - try: - cls = import_this(class_path) - if init_args is None: - return cls() - else: - return cls(**init_args) - except: - raise RuntimeError('Error instantiating class {} ' - 'with the arguments {}.'.format(class_path, - init_args)) From 82074217f2fc8b6e485942eab761da810a3dce9f Mon Sep 17 00:00:00 2001 From: Alexandre Manhaes Savio Date: Fri, 9 Sep 2016 18:49:51 +0200 Subject: [PATCH 04/17] wip: add to_boutiques command --- nipype/scripts/cli.py | 101 ++++++++++++++++--------------- nipype/scripts/utils.py | 8 +++ nipype/utils/nipype2boutiques.py | 12 ++-- 3 files changed, 67 insertions(+), 54 deletions(-) diff --git a/nipype/scripts/cli.py b/nipype/scripts/cli.py index bfcf51f317..131c35f42a 100644 --- a/nipype/scripts/cli.py +++ b/nipype/scripts/cli.py @@ -9,7 +9,8 @@ ExistingDirPath, ExistingFilePath, RegularExpression, - PythonModule) + PythonModule, + grouper) # declare the CLI group @@ -111,7 +112,7 @@ def run(ctx, module, interface, list, help): if not module_given: click.echo(ctx.command.get_help(ctx)) - # print the list available interfaces for the given module + # print the list of available interfaces for the given module elif (module_given and list) or (module_given and not interface): iface_names = list_interfaces(module) click.echo('Available Interfaces:') @@ -120,11 +121,11 @@ def run(ctx, module, interface, list, help): # check the interface elif (module_given and interface): + # create the argument parser description = "Run {}".format(interface) prog = " ".join([ctx.command_path, module.__name__, - interface] + - ctx.args) + interface] + ctx.args) iface_parser = argparse.ArgumentParser(description=description, prog=prog) @@ -141,54 +142,54 @@ def run(ctx, module, interface, list, help): run_instance(node, args) -# -# @cli.command(context_settings=CONTEXT_SETTINGS.update(dict(ignore_unknown_options=True,))) -# @click.argument('-f', '--format', type=click.Choice(['boutiques'])) +# @cli.command(context_settings=UNKNOWN_OPTIONS) +# @click.option('-f', '--format', type=click.Choice(['boutiques']), +# help='Output format type.') # @click.argument('format_args', nargs=-1, type=click.UNPROCESSED) # @click.pass_context -# def convert(ctx, format): +# def convert(ctx, format, format_args): # """Export nipype interfaces to other formats.""" # if format == 'boutiques': +# ctx.params.pop('format') +# ctx.params = dict(grouper(ctx.params['format_args'], 2)) # ctx.forward(to_boutiques) -# import pdb; pdb.set_trace() -# ctx.invoke(to_boutiques, **format_args) -# -# -# @cli.command(context_settings=CONTEXT_SETTINGS) -# @click.option("-i", "--interface", type=str, required=True, -# help="Name of the Nipype interface to export.") -# @click.option("-m", "--module", type=PythonModule(), required=True, -# help="Module where the interface is defined.") -# @click.option("-o", "--output", type=str, required=True, -# help="JSON file name where the Boutiques descriptor will be written.") -# @click.option("-t", "--ignored-template-inputs", type=str, multiple=True, -# help="Interface inputs ignored in path template creations.") -# @click.option("-d", "--docker-image", type=str, -# help="Name of the Docker image where the Nipype interface is available.") -# @click.option("-r", "--docker-index", type=str, -# help="Docker index where the Docker image is stored (e.g. http://index.docker.io).") -# @click.option("-n", "--ignore-template-numbers", is_flag=True, flag_value=True, -# help="Ignore all numbers in path template creations.") -# @click.option("-v", "--verbose", is_flag=True, flag_value=True, -# help="Enable verbose output.") -# def to_boutiques(interface, module, output, ignored_template_inputs, -# docker_image, docker_index, ignore_template_numbers, -# verbose): -# """Nipype Boutiques exporter. -# -# See Boutiques specification at https://github.com/boutiques/schema. -# """ -# from nipype.utils.nipype2boutiques import generate_boutiques_descriptor -# -# # Generates JSON string -# json_string = generate_boutiques_descriptor(module, -# interface, -# ignored_template_inputs, -# docker_image, -# docker_index, -# verbose, -# ignore_template_numbers) -# -# # Writes JSON string to file -# with open(output, 'w') as f: -# f.write(json_string) + + +@cli.command(context_settings=CONTEXT_SETTINGS) +@click.option("-i", "--interface", type=str, required=True, + help="Name of the Nipype interface to export.") +@click.option("-m", "--module", type=PythonModule(), required=True, + help="Module where the interface is defined.") +@click.option("-o", "--output", type=str, required=True, + help="JSON file name where the Boutiques descriptor will be written.") +@click.option("-t", "--ignored-template-inputs", type=str, multiple=True, + help="Interface inputs ignored in path template creations.") +@click.option("-d", "--docker-image", type=str, + help="Name of the Docker image where the Nipype interface is available.") +@click.option("-r", "--docker-index", type=str, + help="Docker index where the Docker image is stored (e.g. http://index.docker.io).") +@click.option("-n", "--ignore-template-numbers", is_flag=True, flag_value=True, + help="Ignore all numbers in path template creations.") +@click.option("-v", "--verbose", is_flag=True, flag_value=True, + help="Enable verbose output.") +def to_boutiques(interface, module, output, ignored_template_inputs, + docker_image, docker_index, ignore_template_numbers, + verbose): + """Nipype Boutiques exporter. + + See Boutiques specification at https://github.com/boutiques/schema. + """ + from nipype.utils.nipype2boutiques import generate_boutiques_descriptor + + # Generates JSON string + json_string = generate_boutiques_descriptor(module, + interface, + ignored_template_inputs, + docker_image, + docker_index, + verbose, + ignore_template_numbers) + + # Writes JSON string to file + with open(output, 'w') as f: + f.write(json_string) diff --git a/nipype/scripts/utils.py b/nipype/scripts/utils.py index 29d73ab6e2..de95cb4027 100644 --- a/nipype/scripts/utils.py +++ b/nipype/scripts/utils.py @@ -4,6 +4,7 @@ """ from __future__ import print_function, division, unicode_literals, absolute_import import re +from itertools import zip_longest import click @@ -67,3 +68,10 @@ def add_args_options(arg_parser, interface): arg_parser.add_argument("--%s" % name, dest=name, help=desc, **args) return arg_parser + + +def grouper(iterable, n, fillvalue=None): + "Collect data into fixed-length chunks or blocks" + # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx + args = [iter(iterable)] * n + return zip_longest(fillvalue=fillvalue, *args) diff --git a/nipype/utils/nipype2boutiques.py b/nipype/utils/nipype2boutiques.py index 1258c81e65..d0b780e27d 100644 --- a/nipype/utils/nipype2boutiques.py +++ b/nipype/utils/nipype2boutiques.py @@ -35,16 +35,20 @@ def generate_boutiques_descriptor(module, interface_name, ignored_template_input raise Exception("Undefined module.") # Retrieves Nipype interface - __import__(module) - interface = getattr(sys.modules[module], interface_name)() + if isinstance(module, str): + __import__(module) + module_name = str(module) + module = sys.modules[module] + + interface = getattr(module, interface_name)() inputs = interface.input_spec() outputs = interface.output_spec() # Tool description tool_desc = {} tool_desc['name'] = interface_name - tool_desc['command-line'] = "nipype_cmd " + str(module) + " " + interface_name + " " - tool_desc['description'] = interface_name + ", as implemented in Nipype (module: " + str(module) + ", interface: " + interface_name + ")." + tool_desc['command-line'] = "nipype_cmd " + module_name + " " + interface_name + " " + tool_desc['description'] = interface_name + ", as implemented in Nipype (module: " + module_name + ", interface: " + interface_name + ")." tool_desc['inputs'] = [] tool_desc['outputs'] = [] tool_desc['tool-version'] = interface.version From 64317f4f6463bcaeb22a8f35cd12ae9188315327 Mon Sep 17 00:00:00 2001 From: Alexandre Manhaes Savio Date: Mon, 12 Sep 2016 15:38:24 +0200 Subject: [PATCH 05/17] add convert subgroup for boutiques --- nipype/scripts/cli.py | 28 +++++++++++----------------- nipype/scripts/utils.py | 3 ++- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/nipype/scripts/cli.py b/nipype/scripts/cli.py index 131c35f42a..b621342fae 100644 --- a/nipype/scripts/cli.py +++ b/nipype/scripts/cli.py @@ -8,6 +8,7 @@ UNKNOWN_OPTIONS, ExistingDirPath, ExistingFilePath, + UnexistingFilePath, RegularExpression, PythonModule, grouper) @@ -142,25 +143,18 @@ def run(ctx, module, interface, list, help): run_instance(node, args) -# @cli.command(context_settings=UNKNOWN_OPTIONS) -# @click.option('-f', '--format', type=click.Choice(['boutiques']), -# help='Output format type.') -# @click.argument('format_args', nargs=-1, type=click.UNPROCESSED) -# @click.pass_context -# def convert(ctx, format, format_args): -# """Export nipype interfaces to other formats.""" -# if format == 'boutiques': -# ctx.params.pop('format') -# ctx.params = dict(grouper(ctx.params['format_args'], 2)) -# ctx.forward(to_boutiques) +@cli.group() +def convert(): + """Export nipype interfaces to other formats.""" + pass -@cli.command(context_settings=CONTEXT_SETTINGS) +@convert.command(context_settings=CONTEXT_SETTINGS) @click.option("-i", "--interface", type=str, required=True, help="Name of the Nipype interface to export.") @click.option("-m", "--module", type=PythonModule(), required=True, help="Module where the interface is defined.") -@click.option("-o", "--output", type=str, required=True, +@click.option("-o", "--output", type=UnexistingFilePath, required=True, help="JSON file name where the Boutiques descriptor will be written.") @click.option("-t", "--ignored-template-inputs", type=str, multiple=True, help="Interface inputs ignored in path template creations.") @@ -172,10 +166,10 @@ def run(ctx, module, interface, list, help): help="Ignore all numbers in path template creations.") @click.option("-v", "--verbose", is_flag=True, flag_value=True, help="Enable verbose output.") -def to_boutiques(interface, module, output, ignored_template_inputs, - docker_image, docker_index, ignore_template_numbers, - verbose): - """Nipype Boutiques exporter. +def boutiques(interface, module, output, ignored_template_inputs, + docker_image, docker_index, ignore_template_numbers, + verbose): + """Nipype to Boutiques exporter. See Boutiques specification at https://github.com/boutiques/schema. """ diff --git a/nipype/scripts/utils.py b/nipype/scripts/utils.py index de95cb4027..0ebbc0167c 100644 --- a/nipype/scripts/utils.py +++ b/nipype/scripts/utils.py @@ -21,6 +21,7 @@ # specification of existing ParamTypes ExistingDirPath = click.Path(exists=True, file_okay=False, resolve_path=True) ExistingFilePath = click.Path(exists=True, dir_okay=False, resolve_path=True) +UnexistingFilePath = click.Path(dir_okay=False, resolve_path=True) # declare custom click.ParamType @@ -37,7 +38,7 @@ def convert(self, value, param, ctx): class PythonModule(click.ParamType): - name = 'Python module' + name = 'Python module path' def convert(self, value, param, ctx): try: From a40ee7eb9f02624bf4be5be354dfa91c0bbffd7f Mon Sep 17 00:00:00 2001 From: Alexandre Manhaes Savio Date: Mon, 12 Sep 2016 15:45:31 +0200 Subject: [PATCH 06/17] remove unused function --- nipype/scripts/cli.py | 3 +-- nipype/scripts/utils.py | 7 ------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/nipype/scripts/cli.py b/nipype/scripts/cli.py index b621342fae..3dd4cfabad 100644 --- a/nipype/scripts/cli.py +++ b/nipype/scripts/cli.py @@ -10,8 +10,7 @@ ExistingFilePath, UnexistingFilePath, RegularExpression, - PythonModule, - grouper) + PythonModule,) # declare the CLI group diff --git a/nipype/scripts/utils.py b/nipype/scripts/utils.py index 0ebbc0167c..2f4ef6752a 100644 --- a/nipype/scripts/utils.py +++ b/nipype/scripts/utils.py @@ -69,10 +69,3 @@ def add_args_options(arg_parser, interface): arg_parser.add_argument("--%s" % name, dest=name, help=desc, **args) return arg_parser - - -def grouper(iterable, n, fillvalue=None): - "Collect data into fixed-length chunks or blocks" - # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx - args = [iter(iterable)] * n - return zip_longest(fillvalue=fillvalue, *args) From d448c7f75433ad53bbf5d110136d7c968268fd51 Mon Sep 17 00:00:00 2001 From: Alexandre Manhaes Savio Date: Tue, 13 Sep 2016 14:27:40 +0200 Subject: [PATCH 07/17] fix argument name for crash CLI. add callbacks --- nipype/scripts/cli.py | 22 +++++++++++++--------- nipype/scripts/utils.py | 7 +++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/nipype/scripts/cli.py b/nipype/scripts/cli.py index 3dd4cfabad..66be7e00fa 100644 --- a/nipype/scripts/cli.py +++ b/nipype/scripts/cli.py @@ -10,7 +10,8 @@ ExistingFilePath, UnexistingFilePath, RegularExpression, - PythonModule,) + PythonModule, + check_not_none,) # declare the CLI group @@ -20,8 +21,8 @@ def cli(): @cli.command(context_settings=CONTEXT_SETTINGS) -@click.argument('logdir', type=ExistingDirPath) -@click.option('-r', '--regex', type=RegularExpression(), default='*', +@click.argument('logdir', type=ExistingDirPath, callback=check_not_none) +@click.option('-r', '--regex', type=RegularExpression(), callback=check_not_none, help='Regular expression to be searched in each traceback.') def search(logdir, regex): """Search for tracebacks content. @@ -43,16 +44,16 @@ def search(logdir, regex): @cli.command(context_settings=CONTEXT_SETTINGS) -@click.argument('crashfile', type=ExistingFilePath,) +@click.argument('crashfile', type=ExistingFilePath, callback=check_not_none) @click.option('-r', '--rerun', is_flag=True, flag_value=True, help='Rerun crashed node.') @click.option('-d', '--debug', is_flag=True, flag_value=True, help='Enable Python debugger when re-executing.') @click.option('-i', '--ipydebug', is_flag=True, flag_value=True, help='Enable IPython debugger when re-executing.') -@click.option('--dir', type=ExistingDirPath, +@click.option('-w', '--dir', type=ExistingDirPath, help='Directory where to run the node in.') -def crash(crashfile, rerun, debug, ipydebug, directory): +def crash(crashfile, rerun, debug, ipydebug, dir): """Display Nipype crash files. For certain crash files, one can rerun a failed node in a temp directory. @@ -70,11 +71,11 @@ def crash(crashfile, rerun, debug, ipydebug, directory): sys.excepthook = ultratb.FormattedTB(mode='Verbose', color_scheme='Linux', call_pdb=1) - display_crash_file(crashfile, rerun, debug, directory) + display_crash_file(crashfile, rerun, debug, dir) @cli.command(context_settings=CONTEXT_SETTINGS) -@click.argument('pklz_file', type=ExistingFilePath) +@click.argument('pklz_file', type=ExistingFilePath, callback=check_not_none) def show(pklz_file): """Print the content of Nipype node .pklz file. @@ -89,7 +90,8 @@ def show(pklz_file): @cli.command(context_settings=UNKNOWN_OPTIONS) -@click.argument('module', type=PythonModule(), required=False) +@click.argument('module', type=PythonModule(), required=False, + callback=check_not_none) @click.argument('interface', type=str, required=False) @click.option('--list', is_flag=True, flag_value=True, help='List the available Interfaces inside the given module.') @@ -152,8 +154,10 @@ def convert(): @click.option("-i", "--interface", type=str, required=True, help="Name of the Nipype interface to export.") @click.option("-m", "--module", type=PythonModule(), required=True, + callback=check_not_none, help="Module where the interface is defined.") @click.option("-o", "--output", type=UnexistingFilePath, required=True, + callback=check_not_none, help="JSON file name where the Boutiques descriptor will be written.") @click.option("-t", "--ignored-template-inputs", type=str, multiple=True, help="Interface inputs ignored in path template creations.") diff --git a/nipype/scripts/utils.py b/nipype/scripts/utils.py index 2f4ef6752a..654a0da15b 100644 --- a/nipype/scripts/utils.py +++ b/nipype/scripts/utils.py @@ -24,6 +24,13 @@ UnexistingFilePath = click.Path(dir_okay=False, resolve_path=True) +# validators +def check_not_none(ctx, param, value): + if value is None: + raise click.BadParameter('got {}.'.format(value)) + return value + + # declare custom click.ParamType class RegularExpression(click.ParamType): name = 'regex' From 029ddda93f1fb2e058489cf20a2fc3e8e6cd84eb Mon Sep 17 00:00:00 2001 From: Alexandre Manhaes Savio Date: Fri, 16 Sep 2016 10:46:04 +0200 Subject: [PATCH 08/17] fix CLI for print_usage commands fail print_help --- nipype/scripts/cli.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nipype/scripts/cli.py b/nipype/scripts/cli.py index 66be7e00fa..52bcc38aa4 100644 --- a/nipype/scripts/cli.py +++ b/nipype/scripts/cli.py @@ -137,7 +137,13 @@ def run(ctx, module, interface, list, help): if not ctx.args: # print the interface help - iface_parser.print_help() + try: + iface_parser.print_help() + except: + print('An error ocurred when trying to print the full' + 'command help, printing usage.') + finally: + iface_parser.print_usage() else: # run the interface args = iface_parser.parse_args(args=ctx.args) From d1cb857e8336c8d3e99e7cf41af1329810b4b88a Mon Sep 17 00:00:00 2001 From: Alexandre Manhaes Savio Date: Mon, 19 Sep 2016 13:57:36 +0200 Subject: [PATCH 09/17] remove bin/ folder, replaced by click --- bin/nipype2boutiques | 8 -------- bin/nipype_cmd | 11 ----------- setup.py | 2 +- 3 files changed, 1 insertion(+), 20 deletions(-) delete mode 100755 bin/nipype2boutiques delete mode 100755 bin/nipype_cmd diff --git a/bin/nipype2boutiques b/bin/nipype2boutiques deleted file mode 100755 index 55b2ffed47..0000000000 --- a/bin/nipype2boutiques +++ /dev/null @@ -1,8 +0,0 @@ -#!python -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: -import sys -from nipype.utils.nipype2boutiques import main - -if __name__ == '__main__': - main(sys.argv) diff --git a/bin/nipype_cmd b/bin/nipype_cmd deleted file mode 100755 index e8d514d38d..0000000000 --- a/bin/nipype_cmd +++ /dev/null @@ -1,11 +0,0 @@ -#!python -# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- -# vi: set ft=python sts=4 ts=4 sw=4 et: - -import sys -from nipype.utils.nipype_cmd import main - -import click - -if __name__ == '__main__': - main(sys.argv) diff --git a/setup.py b/setup.py index df2d950d24..e0fc726550 100755 --- a/setup.py +++ b/setup.py @@ -438,7 +438,7 @@ def main(**extra_args): # python -- duplicating things into MANIFEST.in but this is admittedly # only a workaround to get things started -- not a solution package_data={'nipype': testdatafiles}, - scripts=glob('bin/*') + ['nipype/external/fsl_imglob.py'], + scripts=['nipype/external/fsl_imglob.py'], entry_points=''' [console_scripts] nipype=nipype.scripts.cli:cli From 44d78af3d007efc00c454086ca20de23c2508bd1 Mon Sep 17 00:00:00 2001 From: Alexandre Manhaes Savio Date: Mon, 19 Sep 2016 14:03:10 +0200 Subject: [PATCH 10/17] remove unused import --- nipype/scripts/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nipype/scripts/utils.py b/nipype/scripts/utils.py index 654a0da15b..6885d7cc4d 100644 --- a/nipype/scripts/utils.py +++ b/nipype/scripts/utils.py @@ -4,7 +4,6 @@ """ from __future__ import print_function, division, unicode_literals, absolute_import import re -from itertools import zip_longest import click From f17d8e203c2d8f6a9195479d6066eb7efb81c855 Mon Sep 17 00:00:00 2001 From: Alexandre Manhaes Savio Date: Thu, 6 Oct 2016 17:52:51 +0200 Subject: [PATCH 11/17] add click to info.py REQUIRES --- nipype/info.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nipype/info.py b/nipype/info.py index 565ecd4577..43c0f81dbb 100644 --- a/nipype/info.py +++ b/nipype/info.py @@ -112,6 +112,7 @@ def get_nipype_gitversion(): FUTURE_MIN_VERSION = '0.15.2' SIMPLEJSON_MIN_VERSION = '3.8.0' PROV_MIN_VERSION = '1.4.0' +CLICK_MIN_VERSION = '6.6.0' NAME = 'nipype' MAINTAINER = 'nipype developers' @@ -141,6 +142,7 @@ def get_nipype_gitversion(): 'future>=%s' % FUTURE_MIN_VERSION, 'simplejson>=%s' % SIMPLEJSON_MIN_VERSION, 'prov>=%s' % PROV_MIN_VERSION, + 'click>=%s' % CLICK_MIN_VERSION, 'xvfbwrapper', 'funcsigs' ] From 3d0c790740844a64c61217c38bbee56dd72fc935 Mon Sep 17 00:00:00 2001 From: Alexandre Manhaes Savio Date: Thu, 6 Oct 2016 17:53:47 +0200 Subject: [PATCH 12/17] fix missing import in cli.py --- nipype/scripts/cli.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nipype/scripts/cli.py b/nipype/scripts/cli.py index 52bcc38aa4..0398ae1ae8 100644 --- a/nipype/scripts/cli.py +++ b/nipype/scripts/cli.py @@ -1,6 +1,8 @@ #!python # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: +from io import open + import click from .instance import list_interfaces From a6697a8b66ffe0d1d93b3e9e8047d87bf3c80aca Mon Sep 17 00:00:00 2001 From: Alexandre Manhaes Savio Date: Thu, 6 Oct 2016 17:57:10 +0200 Subject: [PATCH 13/17] fix docstring in scripts.instance --- nipype/scripts/instance.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nipype/scripts/instance.py b/nipype/scripts/instance.py index 2bde70ced1..db605b9741 100644 --- a/nipype/scripts/instance.py +++ b/nipype/scripts/instance.py @@ -15,7 +15,6 @@ def import_module(module_path): If the name is specified in relative terms, then the package argument must be set to the name of the package which is to act as the anchor for resolving the package name (e.g. import_module('..mod', 'pkg.subpkg') - will import pkg.mod). Parameters From 8f4e335245fff66ef2fc4859d376d16f7497bfce Mon Sep 17 00:00:00 2001 From: Satrajit Ghosh Date: Fri, 7 Oct 2016 01:06:39 -0400 Subject: [PATCH 14/17] add script installer --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c1426c5e3f..4bf982902f 100755 --- a/setup.py +++ b/setup.py @@ -137,7 +137,11 @@ def main(): tests_require=ldict['TESTS_REQUIRES'], test_suite='nose.collector', zip_safe=False, - extras_require=ldict['EXTRA_REQUIRES'] + extras_require=ldict['EXTRA_REQUIRES'], + entry_points=''' + [console_scripts] + nipypecli=nipype.scripts.cli:cli + ''' ) if __name__ == "__main__": From d022077d37855dfb07a165e7508df33512c9a408 Mon Sep 17 00:00:00 2001 From: Satrajit Ghosh Date: Fri, 7 Oct 2016 11:04:42 -0400 Subject: [PATCH 15/17] fix interface doc builder skip patterns --- tools/build_interface_docs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/build_interface_docs.py b/tools/build_interface_docs.py index a1189c63bb..820cd699cb 100755 --- a/tools/build_interface_docs.py +++ b/tools/build_interface_docs.py @@ -24,6 +24,7 @@ '\.pipeline', '\.testing', '\.caching', + '\.scripts', ] # Modules that should not be included in generated API docs. docwriter.module_skip_patterns += ['\.version$', @@ -36,7 +37,8 @@ '\.interfaces\.traits', '\.pipeline\.alloy$', '\.pipeline\.s3_node_wrapper$', - '.\testing', + '\.testing', + '\.scripts', ] docwriter.class_skip_patterns += ['AFNI', 'ANTS', From 2483b7ae56d5a8581c1977b6d2822ec351099625 Mon Sep 17 00:00:00 2001 From: Satrajit Ghosh Date: Fri, 7 Oct 2016 15:49:33 -0400 Subject: [PATCH 16/17] doc+fix: do not generate api from scripts directory --- tools/build_modref_templates.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/build_modref_templates.py b/tools/build_modref_templates.py index 8c868c6ffe..66482271b8 100755 --- a/tools/build_modref_templates.py +++ b/tools/build_modref_templates.py @@ -27,6 +27,7 @@ '\.testing$', '\.fixes$', '\.algorithms$', + '\.scripts$', ] # Modules that should not be included in generated API docs. docwriter.module_skip_patterns += ['\.version$', @@ -35,6 +36,7 @@ '\.pipeline\.utils$', '\.interfaces\.slicer\.generate_classes$', '\.interfaces\.pymvpa$', + '\.scripts$', ] docwriter.write_api_docs(outdir) docwriter.write_index(outdir, 'gen', relative_to='api') From 083e31bae200130ae53a7ac3d59b74098770ff5f Mon Sep 17 00:00:00 2001 From: Satrajit Ghosh Date: Sat, 8 Oct 2016 10:09:37 -0400 Subject: [PATCH 17/17] doc: update cli related docs --- doc/users/cli.rst | 24 ++++++++++++++++++++++++ doc/users/debug.rst | 11 +++++++---- doc/users/index.rst | 2 +- 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 doc/users/cli.rst diff --git a/doc/users/cli.rst b/doc/users/cli.rst new file mode 100644 index 0000000000..04dddd3fee --- /dev/null +++ b/doc/users/cli.rst @@ -0,0 +1,24 @@ +.. _cli: + +============================= +Nipype Command Line Interface +============================= + +The Nipype Command Line Interface allows a variety of operations:: + + $ nipypecli + Usage: nipypecli [OPTIONS] COMMAND [ARGS]... + + Options: + -h, --help Show this message and exit. + + Commands: + convert Export nipype interfaces to other formats. + crash Display Nipype crash files. + run Run a Nipype Interface. + search Search for tracebacks content. + show Print the content of Nipype node .pklz file. + +These have replaced previous nipype command line tools such as +`nipype_display_crash`, `nipype_crash_search`, `nipype2boutiques`, +`nipype_cmd` and `nipype_display_pklz`. diff --git a/doc/users/debug.rst b/doc/users/debug.rst index 2102e82690..d5064fd37d 100644 --- a/doc/users/debug.rst +++ b/doc/users/debug.rst @@ -20,7 +20,10 @@ performance issues. from nipype import config config.enable_debug_mode() - as the first import of your nipype script. + as the first import of your nipype script. To enable debug logging use:: + + from nipype import logging + logging.update_logging(config) .. note:: @@ -39,10 +42,10 @@ performance issues. node to fail without generating a crash file in the crashdump directory. In such cases, it will store a crash file in the `batch` directory. -#. All Nipype crashfiles can be inspected with the `nipype_display_crash` +#. All Nipype crashfiles can be inspected with the `nipypecli crash` utility. -#. The `nipype_crash_search` command allows you to search for regular expressions +#. The `nipypecli search` command allows you to search for regular expressions in the tracebacks of the Nipype crashfiles within a log folder. #. Nipype determines the hash of the input state of a node. If any input @@ -66,6 +69,6 @@ performance issues. PBS/LSF/SGE/Condor plugins in such cases the workflow may crash because it cannot retrieve the node result. Setting the `job_finished_timeout` can help:: - workflow.config['execution']['job_finished_timeout'] = 65 + workflow.config['execution']['job_finished_timeout'] = 65 .. include:: ../links_names.txt diff --git a/doc/users/index.rst b/doc/users/index.rst index 13c1487ae0..c9cbcd961b 100644 --- a/doc/users/index.rst +++ b/doc/users/index.rst @@ -23,7 +23,7 @@ plugins config_file debug - + cli .. toctree:: :maxdepth: 1