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 afa4dd3909..0000000000 --- a/bin/nipype_cmd +++ /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.nipype_cmd import main - -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/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 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' ] 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..0398ae1ae8 --- /dev/null +++ b/nipype/scripts/cli.py @@ -0,0 +1,200 @@ +#!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 .utils import (CONTEXT_SETTINGS, + UNKNOWN_OPTIONS, + ExistingDirPath, + ExistingFilePath, + UnexistingFilePath, + RegularExpression, + PythonModule, + check_not_none,) + + +# declare the CLI group +@click.group(context_settings=CONTEXT_SETTINGS) +def cli(): + pass + + +@cli.command(context_settings=CONTEXT_SETTINGS) +@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. + + Search for traceback inside a folder of nipype crash log files that match + a given regular expression. + + Examples:\n + nipype search nipype/wd/log -r '.*subject123.*' + """ + from .crash_files import iter_tracebacks + + for file, trace in iter_tracebacks(logdir): + if regex.search(trace): + click.echo("-" * len(file)) + click.echo(file) + click.echo("-" * len(file)) + click.echo(trace) + + +@cli.command(context_settings=CONTEXT_SETTINGS) +@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('-w', '--dir', type=ExistingDirPath, + help='Directory where to run the node in.') +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. + + Examples:\n + nipype crash crashfile.pklz\n + nipype crash crashfile.pklz -r -i\n + """ + 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, dir) + + +@cli.command(context_settings=CONTEXT_SETTINGS) +@click.argument('pklz_file', type=ExistingFilePath, callback=check_not_none) +def show(pklz_file): + """Print the content of Nipype node .pklz file. + + Examples:\n + nipype show node.pklz + """ + from pprint import pprint + from ..utils.filemanip import loadpkl + + pkl_data = loadpkl(pklz_file) + pprint(pkl_data) + + +@cli.command(context_settings=UNKNOWN_OPTIONS) +@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.') +@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 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:') + for if_name in iface_names: + click.echo(' {}'.format(if_name)) + + # 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) + 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 + 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) + run_instance(node, args) + + +@cli.group() +def convert(): + """Export nipype interfaces to other formats.""" + pass + + +@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, + 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.") +@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 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. + """ + 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/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/nipype/scripts/instance.py b/nipype/scripts/instance.py new file mode 100644 index 0000000000..db605b9741 --- /dev/null +++ b/nipype/scripts/instance.py @@ -0,0 +1,44 @@ +# -*- 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 diff --git a/nipype/scripts/utils.py b/nipype/scripts/utils.py new file mode 100644 index 0000000000..6885d7cc4d --- /dev/null +++ b/nipype/scripts/utils.py @@ -0,0 +1,77 @@ +# -*- 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) +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' + + 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 path' + + 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..d0b780e27d 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. @@ -63,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 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): diff --git a/requirements.txt b/requirements.txt index a2e3a04853..c9156b5289 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ nose>=1.2 future==0.15.2 simplejson>=3.8.0 prov>=1.4.0 +click>=6.6.0 xvfbwrapper psutil funcsigs 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__": 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', 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')