diff --git a/bob/extension/config.py b/bob/extension/config.py index 9919b3e23b9d5d496ef28dca2420795054f67027..0587336bfc7a292a9cc9a8e95e291fbdf4917433 100644 --- a/bob/extension/config.py +++ b/bob/extension/config.py @@ -5,10 +5,10 @@ ''' import imp -import pkg_resources import pkgutil from os.path import isfile import logging +import pkg_resources logger = logging.getLogger(__name__) @@ -211,3 +211,29 @@ def mod_to_context(mod): """ return {k: v for k, v in mod.__dict__.items() if not (k.startswith('__') and k.endswith('__'))} + + +def resource_keys(entry_point_group, exclude_packages=[], strip=['dummy']): + """Reads and returns all resources that are registered with the given + entry_point_group. Entry points from the given ``exclude_packages`` are + ignored. + + Parameters + ---------- + entry_point_group : str + The entry point group name. + exclude_packages : :any:`list`, optional + List of packages to exclude when finding resources. + strip : :any:`list`, optional + Entrypoint names that start with any value in ``strip`` will be ignored. + + Returns + ------- + :any:`list` + List of found resources. + """ + ret_list = [entry_point.name for entry_point in + pkg_resources.iter_entry_points(entry_point_group) + if (entry_point.dist.project_name not in exclude_packages and + not entry_point.name.startswith(tuple(strip)))] + return sorted(ret_list) diff --git a/bob/extension/data/test_dump_config.py b/bob/extension/data/test_dump_config.py new file mode 100644 index 0000000000000000000000000000000000000000..910002415e72250bc11e544e3263ad0ade5dbe64 --- /dev/null +++ b/bob/extension/data/test_dump_config.py @@ -0,0 +1,14 @@ +'''Configuration file automatically generated at 08/07/2018 +cli test + +Test command + +Examples!''' + +# test = /my/path/test.txt +'''Required parameter: test (-t, --test) +Path leading to test blablabla''' + +# verbose = 0 +'''Optional parameter: verbose (-v, --verbose) [default: 0] +Increase the verbosity level from 0 (only error messages) to 1 (warnings), 2 (log messages), 3 (debug information) by adding the --verbose option as often as desired (e.g. '-vvv' for debug).''' diff --git a/bob/extension/data/test_dump_config2.py b/bob/extension/data/test_dump_config2.py new file mode 100644 index 0000000000000000000000000000000000000000..06d28fe6d19f4571744e1578b965062ac77e33e6 --- /dev/null +++ b/bob/extension/data/test_dump_config2.py @@ -0,0 +1,43 @@ +'''Configuration file automatically generated at 08/07/2018 +cli test + +Blablabla bli blo + +Parameters +---------- +xxx : :any:`list` + blabla blablo +yyy : callable + bli bla blo bla bla bla + +[CONFIG]... BLA BLA BLA BLA''' + +# database = None +'''Required parameter: database (--database, -d) +bla bla bla Can be a ``bob.extension.test_config_load`` entry point, a module name, or a path to a Python file which contains a variable named `database`. +Registered entries are: ['basic_config', 'resource_config', 'subpackage_config']''' + +# annotator = None +'''Required parameter: annotator (--annotator, -a) +bli bli bli Can be a ``bob.extension.test_config_load`` entry point, a module name, or a path to a Python file which contains a variable named `annotator`. +Registered entries are: ['basic_config', 'resource_config', 'subpackage_config']''' + +# output_dir = None +'''Required parameter: output_dir (--output-dir, -o) +blo blo blo''' + +# force = False +'''Optional parameter: force (--force, -f) [default: False] +lalalalalala''' + +# array = 1 +'''Optional parameter: array (--array) [default: 1] +lililili''' + +# database_directories_file = ~/databases.txt +'''Optional parameter: database_directories_file (--database-directories-file) [default: ~/databases.txt] +lklklklk''' + +# verbose = 0 +'''Optional parameter: verbose (-v, --verbose) [default: 0] +Increase the verbosity level from 0 (only error messages) to 1 (warnings), 2 (log messages), 3 (debug information) by adding the --verbose option as often as desired (e.g. '-vvv' for debug).''' diff --git a/bob/extension/scripts/click_helper.py b/bob/extension/scripts/click_helper.py index 8bdb097d71c9caac646071d6fbc562a2516e038c..2b2aa84613019a8e33abca7f9c8a9bbcc1610ade 100644 --- a/bob/extension/scripts/click_helper.py +++ b/bob/extension/scripts/click_helper.py @@ -1,5 +1,6 @@ from ..log import set_verbosity_level -from ..config import load, mod_to_context +from ..config import load, mod_to_context, resource_keys +import time import click import logging @@ -137,7 +138,6 @@ def verbosity_option(**kwargs): return value return click.option( '-v', '--verbose', count=True, - expose_value=False, help="Increase the verbosity level from 0 (only error messages) to 1 " "(warnings), 2 (log messages), 3 (debug information) by adding the " "--verbose option as often as desired (e.g. '-vvv' for debug).", @@ -165,6 +165,17 @@ class ConfigCommand(click.Command): config_argument_name='CONFIG', **kwargs): self.config_argument_name = config_argument_name self.entry_point_group = entry_point_group + # Augment help for the config file argument + self.extra_help = '''\n\nIt is possible to pass one or several Python files +(or names of ``{entry_point_group}`` entry points or module names) as {CONFIG} +arguments to the command line which contain the parameters listed below as +Python variables. The options through the command-line (see below) will +override the values of configuration files. You can run this command with +``<COMMAND> -H example_config.py`` to create a template config +file.'''.format(CONFIG=config_argument_name, + entry_point_group=entry_point_group) + help = (help or '').rstrip() + self.extra_help + # kwargs['help'] = help click.Command.__init__( self, name, context_settings=context_settings, callback=callback, params=params, help=help, epilog=epilog, short_help=short_help, @@ -172,15 +183,29 @@ class ConfigCommand(click.Command): **kwargs) # Add the config argument to the command click.argument(config_argument_name, nargs=-1)(self) + # Option for config file generation + click.option('-H', '--dump-config', type=click.File(mode='wt'), + help="Name of the config file to be generated")(self) + + def is_resource(self, param, ctx): + """Checks if the param is an option and is also in the current context.""" + return (param.name in ctx.params and + param.name != 'dump_config' and + isinstance(param, click.Option)) def invoke(self, ctx): + dump_file = ctx.params.get('dump_config') + if dump_file is not None: + click.echo("Configuration file '{}' was written; exiting".format( + dump_file.name)) + return self.dump_config(ctx) config_files = ctx.params[self.config_argument_name.lower()] # load and normalize context from config files config_context = load( config_files, entry_point_group=self.entry_point_group) config_context = mod_to_context(config_context) for param in self.params: - if param.name not in ctx.params: + if not self.is_resource(param, ctx): continue value = ctx.params[param.name] if not hasattr(param, 'user_provided'): @@ -203,6 +228,58 @@ class ConfigCommand(click.Command): return super(ConfigCommand, self).invoke(ctx) + def dump_config(self, ctx): + """Generate configuration file from parameters and context + + Parameters + ---------- + ctx : object + Click context + """ + config_file = ctx.params['dump_config'] + logger.debug("Generating configuration file `%s'...", config_file) + config_file.write("'''") + config_file.write('Configuration file automatically generated at ' + '%s\n%s\n' % (time.strftime("%d/%m/%Y"), + ctx.command_path)) + + if self.help: + h = self.help.replace(self.extra_help, '').replace('\b\n', '') + config_file.write('\n{}'.format(h.rstrip())) + + if self.epilog: + config_file.write('\n\n{}'.format(self.epilog.replace('\b\n', ''))) + + config_file.write("'''\n") + + for param in self.params: + if not self.is_resource(param, ctx): + continue + + config_file.write('\n# %s = %s\n' % (param.name, + str(ctx.params[param.name]))) + config_file.write("'''") + + if param.required or (isinstance(param, ResourceOption) and + param.real_required): + begin, dflt = 'Required parameter', '' + else: + begin, dflt = 'Optional parameter', ' [default: {}]'.format( + param.default) + config_file.write( + "%s: %s (%s)%s" % ( + begin, param.name, ', '.join(param.opts), dflt)) + + if param.help is not None: + config_file.write("\n%s" % param.help) + + if isinstance(param, ResourceOption) and \ + param.entry_point_group is not None: + config_file.write("\nRegistered entries are: {}".format( + resource_keys(param.entry_point_group))) + + config_file.write("'''\n") + class ResourceOption(click.Option): """A click.Option that is aware if the user actually provided this option @@ -231,6 +308,13 @@ class ResourceOption(click.Option): self.entry_point_group = entry_point_group self.real_required = required kwargs['required'] = False + if entry_point_group is not None: + name, _, _ = self._parse_decls(param_decls, kwargs.get('expose_value')) + help = help or '' + help += ( + ' Can be a ``{entry_point_group}`` entry point, a module name, or ' + 'a path to a Python file which contains a variable named `{name}`.') + help = help.format(entry_point_group=entry_point_group, name=name) click.Option.__init__( self, param_decls=param_decls, show_default=show_default, prompt=prompt, confirmation_prompt=confirmation_prompt, @@ -259,7 +343,6 @@ class ResourceOption(click.Option): while isinstance(value, basestring): value = load([value], entry_point_group=self.entry_point_group) value = getattr(value, keyword) - return value @@ -286,3 +369,26 @@ class AliasedGroup(click.Group): elif len(matches) == 1: return click.Group.get_command(self, ctx, matches[0]) ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) + + +def log_parameters(logger_handle): + """Logs the click parameters with the logging module. + + Parameters + ---------- + logger_handle : object + The logger handle to write debug information into. + """ + ctx = click.get_current_context() + # do not sort the ctx.params dict. The insertion order is kept in Python 3 + # and is useful (but not necessary so works on Python 2 too). + for k, v in ctx.params.items(): + logger_handle.debug('%s: %s', k, v) + + +def assert_click_runner_result(result, exit_code=0): + """Helper for asserting click runner results""" + m = ("Click command exited with code `{}' and exception:\n{}" + "\nThe output was:\n{}") + m = m.format(result.exit_code, result.exception, result.output) + assert result.exit_code == exit_code, m diff --git a/bob/extension/scripts/config.py b/bob/extension/scripts/config.py index b1c7d445ba37a18c42fe25c0344cf82d1d4b9b14..251b2a05496d2bc9f5d3576027596c0748dd840d 100644 --- a/bob/extension/scripts/config.py +++ b/bob/extension/scripts/config.py @@ -13,7 +13,7 @@ logger = logging.getLogger(__name__) @click.group(cls=AliasedGroup) @verbosity_option() -def config(): +def config(**kwargs): """The manager for bob's global configuration.""" # Load the config file again. This may be needed since the environment # variable might change the config path during the tests. Otherwise, this diff --git a/bob/extension/test_click_helper.py b/bob/extension/test_click_helper.py index 2872fe1a01addac80d0f3a9f850f44c66d5d870f..540dff9de7c4a509b150a79c95260ed9beaf7f63 100644 --- a/bob/extension/test_click_helper.py +++ b/bob/extension/test_click_helper.py @@ -1,8 +1,10 @@ import click +import time +import pkg_resources from click.testing import CliRunner from bob.extension.scripts.click_helper import ( verbosity_option, bool_option, list_float_option, - open_file_mode_option, ConfigCommand, ResourceOption, AliasedGroup) + ConfigCommand, ResourceOption, AliasedGroup) def test_verbosity_option(): @@ -11,7 +13,7 @@ def test_verbosity_option(): [[], ['-v'], ['-vv'], ['-vvv']]): @click.command() @verbosity_option() - def cli(): + def cli(verbose): ctx = click.get_current_context() verbose = ctx.meta['verbosity'] assert verbose == VERBOSITY, verbose @@ -20,6 +22,7 @@ def test_verbosity_option(): result = runner.invoke(cli, OPTIONS, catch_exceptions=False) assert result.exit_code == 0, (result.exit_code, result.output) + def test_bool_option(): @click.command() @@ -45,6 +48,7 @@ def test_bool_option(): result = runner.invoke(cli2) assert result.exit_code == 0, (result.exit_code, result.output) + def test_list_float_option(): @click.command() @@ -59,6 +63,7 @@ def test_list_float_option(): result = runner.invoke(cli, ['-T', '1,2,3']) assert result.exit_code == 0, (result.exit_code, result.output) + def test_list_float_option_empty(): @click.command() @@ -72,6 +77,7 @@ def test_list_float_option_empty(): result = runner.invoke(cli, ['-T', ' ']) assert result.exit_code == 0, (result.exit_code, result.output) + def test_commands_with_config_1(): # random test @click.command( @@ -146,6 +152,7 @@ def test_commands_with_config_3(): assert result.exit_code == 0, (result.exit_code, result.output) assert result.output.strip() == '3', result.output + def test_prefix_aliasing(): @click.group(cls=AliasedGroup) def cli(): @@ -159,7 +166,6 @@ def test_prefix_aliasing(): def test_aaa(): click.echo("AAA") - runner = CliRunner() result = runner.invoke(cli, ['te'], catch_exceptions=False) assert result.exit_code != 0, (result.exit_code, result.output) @@ -171,3 +177,78 @@ def test_prefix_aliasing(): result = runner.invoke(cli, ['test_a'], catch_exceptions=False) assert result.exit_code == 0, (result.exit_code, result.output) assert 'AAA' in result.output, (result.exit_code, result.output) + + +def _assert_config_dump(ref, ref_date): + today = time.strftime("%d/%m/%Y") + # uncomment below to re-write tests + # open(ref, 'wt').write(open('TEST_CONF').read()) + with open('TEST_CONF', 'r') as f, open(ref, 'r') as f2: + text = f.read() + ref_text = f2.read().replace(ref_date, today) + assert text == ref_text, '\n'.join([text, ref_text]) + + +def test_config_dump(): + @click.group(cls=AliasedGroup) + def cli(): + pass + + @cli.command(cls=ConfigCommand, epilog='Examples!') + @click.option('-t', '--test', required=True, default="/my/path/test.txt", + help="Path leading to test blablabla", cls=ResourceOption) + @verbosity_option() + def test(config, test, **kwargs): + """Test command""" + pass + runner = CliRunner() + with runner.isolated_filesystem(): + result = runner.invoke( + cli, ['test', '-H', 'TEST_CONF'], catch_exceptions=False) + ref = pkg_resources.resource_filename('bob.extension', + 'data/test_dump_config.py') + assert result.exit_code == 0, (result.exit_code, result.output) + _assert_config_dump(ref, '08/07/2018') + + +def test_config_dump2(): + @click.group(cls=AliasedGroup) + def cli(): + pass + + @cli.command(cls=ConfigCommand, entry_point_group='bob.extension.test_config_load') + @click.option('--database', '-d', required=True, cls=ResourceOption, + entry_point_group='bob.extension.test_config_load', help="bla bla bla") + @click.option('--annotator', '-a', required=True, cls=ResourceOption, + entry_point_group='bob.extension.test_config_load', help="bli bli bli") + @click.option('--output-dir', '-o', required=True, cls=ResourceOption, + help="blo blo blo") + @click.option('--force', '-f', is_flag=True, cls=ResourceOption, + help="lalalalalala") + @click.option('--array', type=click.INT, default=1, cls=ResourceOption, + help="lililili") + @click.option('--database-directories-file', cls=ResourceOption, + default='~/databases.txt', help="lklklklk") + @verbosity_option(cls=ResourceOption) + def test(**kwargs): + """Blablabla bli blo + + Parameters + ---------- + xxx : :any:`list` + blabla blablo + yyy : callable + bli bla blo bla bla bla + + [CONFIG]... BLA BLA BLA BLA + """ + pass + + runner = CliRunner() + with runner.isolated_filesystem(): + result = runner.invoke( + cli, ['test', '-H', 'TEST_CONF'], catch_exceptions=False) + ref = pkg_resources.resource_filename('bob.extension', + 'data/test_dump_config2.py') + assert result.exit_code == 0, (result.exit_code, result.output) + _assert_config_dump(ref, '08/07/2018') diff --git a/bob/extension/test_extensions.py b/bob/extension/test_extensions.py index 85bae8381cb80cf9f8560f56f4a236342232d374..ac740cdb91b2dcef6f26f5e433e5e79d1419fb05 100644 --- a/bob/extension/test_extensions.py +++ b/bob/extension/test_extensions.py @@ -35,7 +35,7 @@ def _run(package, run_call): assert os.path.exists(_bin('python')) # nosetests - subprocess.call(['python', _bin('nosetests'), '-sv']) + subprocess.call(['python', _bin('nosetests'), '-sv', 'bob.example.{0}'.format(package)]) # check that the call is working subprocess.call(['python', _bin(run_call[0])] + run_call[1:]) diff --git a/bob/extension/test_rc.py b/bob/extension/test_rc.py index 18729604f9a3db55f620c0753f2b87ae71146a03..a6ca238d2fe0b9fe5c7bcb8950a466010a7c0d22 100644 --- a/bob/extension/test_rc.py +++ b/bob/extension/test_rc.py @@ -2,6 +2,7 @@ from .rc_config import _loadrc, ENVNAME from .scripts import main_cli +from .scripts.click_helper import assert_click_runner_result from click.testing import CliRunner import os import pkg_resources @@ -27,19 +28,19 @@ def test_bob_config(): # test config show result = runner.invoke(main_cli, ['config', 'show']) - assert result.exit_code == 0, result.exit_code + assert_click_runner_result(result, 0) assert 'defaults-config' in result.output, result.output assert open(defaults_config).read() in result.output, result.output # test config get (existing key) result = runner.invoke(main_cli, ['config', 'get', 'bob.db.atnt.directory']) - assert result.exit_code == 0, result.exit_code + assert_click_runner_result(result, 0) assert result.output == '/home/bob/databases/atnt\n', result.output # test config get (non-existing key) result = runner.invoke(main_cli, ['config', 'get', 'bob.db.atnt']) - assert result.exit_code == 1, result.exit_code + assert_click_runner_result(result, 1) # test config set runner = CliRunner() @@ -53,14 +54,14 @@ def test_bob_config(): env={ ENVNAME: bobrcfile }) - assert result.exit_code == 0, result.exit_code + assert_click_runner_result(result, 0) # read the config back to make sure it is ok. result = runner.invoke( main_cli, ['config', 'show'], env={ ENVNAME: bobrcfile }) - assert result.exit_code == 0, result.exit_code + assert_click_runner_result(result, 0) expected_output = '''Displaying `bobrc': { "bob.db.atnt.directory": "/home/bob/databases/orl_faces" diff --git a/conda/meta.yaml b/conda/meta.yaml index 53ff294b13f6607732f8402a371703758f208a70..85e2d2cadaf518dde51bba58c25e357e6680c741 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -33,6 +33,10 @@ test: - {{ name }} commands: - bob_dependecy_graph.py --help + - bob -h + - bob --help + - bob config -h + - bob config --help - nosetests --with-coverage --cover-package={{ name }} -sv {{ name }} --exclude=test_extensions - sphinx-build -aEW {{ project_dir }}/doc {{ project_dir }}/sphinx - sphinx-build -aEb doctest {{ project_dir }}/doc sphinx diff --git a/doc/annotate.py b/doc/annotate.py index b34c75acbcf5d45c590d9efeffc99ab7cec2c001..ef9b537625754b858198b4f15584c9839e49423f 100644 --- a/doc/annotate.py +++ b/doc/annotate.py @@ -3,53 +3,40 @@ import logging import click from bob.extension.scripts.click_helper import ( - verbosity_option, ConfigCommand, ResourceOption) + verbosity_option, ConfigCommand, ResourceOption, log_parameters) logger = logging.getLogger(__name__) -@click.command(entry_point_group='bob.bio.config', cls=ConfigCommand) +ANNOTATE_EPILOG = '''\b +Examples: + + $ bob bio annotate -vvv -d <database> -a <annotator> -o /tmp/annotations + $ jman submit --array 64 -- bob bio annotate ... --array 64 +''' + + +@click.command(entry_point_group='bob.bio.config', cls=ConfigCommand, + epilog=ANNOTATE_EPILOG) @click.option('--database', '-d', required=True, cls=ResourceOption, - entry_point_group='bob.bio.database') + entry_point_group='bob.bio.database', + help='''The database that you want to annotate.''') @click.option('--annotator', '-a', required=True, cls=ResourceOption, - entry_point_group='bob.bio.annotator') -@click.option('--output-dir', '-o', required=True, cls=ResourceOption) -@click.option('--force', '-f', is_flag=True, cls=ResourceOption) + entry_point_group='bob.bio.annotator', + help='A callable that takes the database and a sample (biofile) ' + 'of the database and returns the annotations in a dictionary.') +@click.option('--output-dir', '-o', required=True, cls=ResourceOption, + help='The directory to save the annotations.') +@click.option('--force', '-f', is_flag=True, cls=ResourceOption, + help='Whether to overwrite existing annotations.') +@click.option('--array', type=click.INT, default=1, cls=ResourceOption, + help='Use this option alongside gridtk to submit this script as ' + 'an array job.') @verbosity_option(cls=ResourceOption) -def annotate(database, annotator, output_dir, force, **kwargs): +def annotate(database, annotator, output_dir, force, array, **kwargs): """Annotates a database. + The annotations are written in text file (json) format which can be read back using :any:`bob.db.base.read_annotation_file` (annotation_type='json') - - \b - Parameters - ---------- - database : :any:`bob.bio.database` - The database that you want to annotate. Can be a ``bob.bio.database`` - entry point or a path to a Python file which contains a variable - named `database`. - annotator : callable - A function that takes the database and a sample (biofile) of the - database and returns the annotations in a dictionary. Can be a - ``bob.bio.annotator`` entry point or a path to a Python file which - contains a variable named `annotator`. - output_dir : str - The directory to save the annotations. - force : bool, optional - Wether to overwrite existing annotations. - verbose : int, optional - Increases verbosity (see help for --verbose). - - \b - [CONFIG]... Configuration files. It is possible to pass one or - several Python files (or names of ``bob.bio.config`` - entry points) which contain the parameters listed - above as Python variables. The options through the - command-line (see below) will override the values of - configuration files. """ - print('database', database) - print('annotator', annotator) - print('force', force) - print('output_dir', output_dir) - print('kwargs', kwargs) + log_parameters(logger) diff --git a/doc/framework.rst b/doc/framework.rst index 8bc2a8f078a55004fa1c7e50e1f9781d7f60cdea..715e76432f07d6b0caa1b2e4386606791634b76f 100644 --- a/doc/framework.rst +++ b/doc/framework.rst @@ -245,47 +245,49 @@ example: This will produce the following help message to the users:: - Usage: bob annotate [OPTIONS] [CONFIG]... - - Annotates a database. The annotations are written in text file (json) - format which can be read back using - :any:`bob.db.base.read_annotation_file` (annotation_type='json') - - Parameters - ---------- - database : :any:`bob.bio.database` - The database that you want to annotate. Can be a ``bob.bio.database`` - entry point or a path to a Python file which contains a variable - named `database`. - annotator : callable - A function that takes the database and a sample (biofile) of the - database and returns the annotations in a dictionary. Can be a - ``bob.bio.annotator`` entry point or a path to a Python file which - contains a variable named `annotator`. - output_dir : str - The directory to save the annotations. - force : bool, optional - Wether to overwrite existing annotations. - verbose : int, optional - Increases verbosity (see help for --verbose). - - [CONFIG]... Configuration files. It is possible to pass one or - several Python files (or names of ``bob.bio.config`` - entry points) which contain the parameters listed - above as Python variables. The options through the - command-line (see below) will override the values of - configuration files. - - Options: - -d, --database TEXT - -a, --annotator TEXT - -o, --output-dir TEXT - -f, --force - -v, --verbose Increase the verbosity level from 0 (only error - messages) to 1 (warnings), 2 (log messages), 3 (debug - information) by adding the --verbose option as often - as desired (e.g. '-vvv' for debug). - --help Show this message and exit. + Usage: bob bio annotate [OPTIONS] [CONFIG]... + + Annotates a database. + + The annotations are written in text file (json) format which can be read + back using :any:`bob.db.base.read_annotation_file` + (annotation_type='json') + + It is possible to pass one or several Python files (or names of + ``bob.bio.config`` entry points or module names) as CONFIG arguments to + the command line which contain the parameters listed below as Python + variables. The options through the command-line (see below) will override + the values of configuration files. You can run this command with + ``<COMMAND> -H example_config.py`` to create a template config file. + + Options: + -d, --database TEXT The database that you want to annotate. Can + be a ``bob.bio.database`` entry point, a + module name, or a path to a Python file + which contains a variable named `database`. + -a, --annotator TEXT A callable that takes the database and a + sample (biofile) of the database and returns + the annotations in a dictionary. Can be a + ``bob.bio.annotator`` entry point, a module + name, or a path to a Python file which + contains a variable named `annotator`. + -o, --output-dir TEXT The directory to save the annotations. + -f, --force Whether to overwrite existing annotations. + --array INTEGER Use this option alongside gridtk to submit + this script as an array job. + databases. + -v, --verbose Increase the verbosity level from 0 (only + error messages) to 1 (warnings), 2 (log + messages), 3 (debug information) by adding + the --verbose option as often as desired + (e.g. '-vvv' for debug). + -H, --dump-config FILENAME Name of the config file to be generated + -?, -h, --help Show this message and exit. + + Examples: + + $ bob bio annotate -vvv -d <database> -a <annotator> -o /tmp/annotations + $ jman submit --array 64 -- bob bio annotate ... --array 64 This script takes configuration files (``CONFIG``) and command line options @@ -328,6 +330,8 @@ Below you can see several ways that this script can be invoked: $ bob annotate bob.package.config_with_all_parameters -o /tmp # below, each resource option can be loaded through config loading mechanism too. $ bob annotate -d /path/to/config/database.py -a bob.package.annotate.config --output /tmp + # Using the command below users can generate a template config file + $ bob annotate -H example_config.py As you can see the command line interface can accept its inputs through several different mechanism. Normally to keep things simple, you would encourage users diff --git a/doc/py_api.rst b/doc/py_api.rst index f57055b5d16aaffac88eea3e8f58c83ea7bae5c6..0b36619287f1b7079d8ccc459fa65c5164c98f4d 100644 --- a/doc/py_api.rst +++ b/doc/py_api.rst @@ -77,6 +77,8 @@ Scripts bob.extension.scripts.click_helper.list_float_option bob.extension.scripts.click_helper.open_file_mode_option bob.extension.scripts.click_helper.AliasedGroup + bob.extension.scripts.click_helper.log_parameters + bob.extension.scripts.click_helper.assert_click_runner_result Core Functionality