diff --git a/bob/extension/config.py b/bob/extension/config.py index 2457b6f4124867691b1006bb802d818b9dcb11e9..0587336bfc7a292a9cc9a8e95e291fbdf4917433 100644 --- a/bob/extension/config.py +++ b/bob/extension/config.py @@ -212,3 +212,28 @@ 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 index 6f9a30406d57f8fd19fdeaa9a3571cac6bb06e2f..910002415e72250bc11e544e3263ad0ade5dbe64 100644 --- a/bob/extension/data/test_dump_config.py +++ b/bob/extension/data/test_dump_config.py @@ -1,3 +1,14 @@ -## Path leading to test blablabla. -## Option: -t, --test +'''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 index 775c97ac6ec818b43f11364142e46f43e3faac0e..06d28fe6d19f4571744e1578b965062ac77e33e6 100644 --- a/bob/extension/data/test_dump_config2.py +++ b/bob/extension/data/test_dump_config2.py @@ -1,4 +1,7 @@ -'''Blablabla bli blo +'''Configuration file automatically generated at 08/07/2018 +cli test + +Blablabla bli blo Parameters ---------- @@ -9,30 +12,32 @@ yyy : callable [CONFIG]... BLA BLA BLA BLA''' - -## bla bla bla. -## Option: --database, -d -## registered entries are: ['basic_config', 'resource_config', 'subpackage_config'] # 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']''' -## bli bli bli. -## Option: --annotator, -a -## 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']''' -## blo blo blo. -## Option: --output-dir, -o # output_dir = None +'''Required parameter: output_dir (--output-dir, -o) +blo blo blo''' -## lalalalalala. -## Option: --force, -f [default: False] # force = False +'''Optional parameter: force (--force, -f) [default: False] +lalalalalala''' -## lililili. -## Option: --array [default: 1] # array = 1 +'''Optional parameter: array (--array) [default: 1] +lililili''' -## lklklklk. -## Option: --database-directories-file [default: ~/databases.txt] # 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 b5e4a019ff3fc7cd18ba26e8e3c9fd5c063c930c..2b2aa84613019a8e33abca7f9c8a9bbcc1610ade 100644 --- a/bob/extension/scripts/click_helper.py +++ b/bob/extension/scripts/click_helper.py @@ -1,6 +1,5 @@ from ..log import set_verbosity_level -from ..config import load, mod_to_context -from ..utils import resource_keys +from ..config import load, mod_to_context, resource_keys import time import click import logging @@ -139,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).", @@ -147,45 +145,6 @@ def verbosity_option(**kwargs): return custom_verbosity_option -def dump_config(command, params, ctx): - """ Generate configuration file from parameters and context - - Parameters - ---------- - params : :any:`list` - List of parameters. For example, params attributes of click.Option. - ctx : dict - Click context dictionary. - - """ - with open(ctx.params.get('dump_config'), 'w') as config_file: - logger.debug("Generating configuration file `%s'...", config_file) - config_file.write('## Configuration file automatically generated at %s ' - 'for %s.\n\n\n' % (time.strftime("%d/%m/%Y"), - ctx.command_path)) - if command.help is not None: - config_file.write("'''" + command.help + "'''\n\n\n") - for param in params: - if param.name not in ctx.params or param.name == 'dump_config': - continue - if not isinstance(param, click.Option): - continue - if param.help is not None: - config_file.write('## %s.\n' % param.help) - dflt='' if param.required or (isinstance(param, ResourceOption) and - param.real_required) else \ - "[default: {}]".format(param.default) - config_file.write( - '## Option: %s %s\n' % (', '.join(param.opts), dflt) - ) - if isinstance(param, ResourceOption) and param.entry_point_group is not\ - None: - config_file.write("## registered entries are: {}\n".format( - resource_keys(param.entry_point_group))) - config_file.write('# %s = %s\n\n' % (param.name, - str(ctx.params[param.name]))) - - class ConfigCommand(click.Command): """A click.Command that can take options both form command line options and configuration files. In order to use this class, you have to use the @@ -206,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, @@ -214,23 +184,28 @@ class ConfigCommand(click.Command): # 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.Path(exists=False), + 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) - ) - return dump_config(self, self.params, ctx) + 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 or param.name == 'dump_config': + if not self.is_resource(param, ctx): continue value = ctx.params[param.name] if not hasattr(param, 'user_provided'): @@ -253,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 @@ -281,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, @@ -335,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 b3584e33f48005d91c1297c7fafe9d0dc3afc15d..540dff9de7c4a509b150a79c95260ed9beaf7f63 100644 --- a/bob/extension/test_click_helper.py +++ b/bob/extension/test_click_helper.py @@ -1,9 +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(): @@ -12,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 @@ -21,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() @@ -46,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() @@ -60,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() @@ -73,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( @@ -147,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(): @@ -160,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) @@ -174,25 +179,36 @@ def test_prefix_aliasing(): 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) + @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) + 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) + result = runner.invoke( + cli, ['test', '-H', 'TEST_CONF'], catch_exceptions=False) ref = pkg_resources.resource_filename('bob.extension', - 'data/test_dump_config.py') + 'data/test_dump_config.py') assert result.exit_code == 0, (result.exit_code, result.output) - with open('TEST_CONF', 'r') as f, open(ref, 'r') as f2: - assert f2.read() in f.read() + _assert_config_dump(ref, '08/07/2018') def test_config_dump2(): @@ -206,33 +222,33 @@ def test_config_dump2(): @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") + help="blo blo blo") @click.option('--force', '-f', is_flag=True, cls=ResourceOption, - help="lalalalalala") + help="lalalalalala") @click.option('--array', type=click.INT, default=1, cls=ResourceOption, - help="lililili") + help="lililili") @click.option('--database-directories-file', cls=ResourceOption, default='~/databases.txt', help="lklklklk") @verbosity_option(cls=ResourceOption) def test(**kwargs): - """Blablabla bli blo + """Blablabla bli blo - Parameters - ---------- - xxx : :any:`list` - blabla blablo - yyy : callable - bli bla blo bla bla bla + Parameters + ---------- + xxx : :any:`list` + blabla blablo + yyy : callable + bli bla blo bla bla bla - [CONFIG]... BLA BLA BLA BLA - """ - pass + [CONFIG]... BLA BLA BLA BLA + """ + pass runner = CliRunner() with runner.isolated_filesystem(): - result = runner.invoke(cli, ['test', '-H', 'TEST_CONF'], catch_exceptions=False) + result = runner.invoke( + cli, ['test', '-H', 'TEST_CONF'], catch_exceptions=False) ref = pkg_resources.resource_filename('bob.extension', - 'data/test_dump_config2.py') + 'data/test_dump_config2.py') assert result.exit_code == 0, (result.exit_code, result.output) - with open('TEST_CONF', 'r') as f, open(ref, 'r') as f2: - assert f2.read() in f.read() + _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/bob/extension/utils.py b/bob/extension/utils.py index f8cf1036f6175f1ed4b438eb140a94799595af5b..0af64e71441b3cd2fc6b5f9948dec7b4a02c1fee 100644 --- a/bob/extension/utils.py +++ b/bob/extension/utils.py @@ -586,14 +586,3 @@ def link_documentation(additional_packages = ['python', 'numpy'], requirements_f print ("Path %s does not exist. The error was: %s" % (url, exc)) return mapping - - -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.""" - 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/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 00ccee16233b7afc0f57ac051c8e19309f6eff08..0b36619287f1b7079d8ccc459fa65c5164c98f4d 100644 --- a/doc/py_api.rst +++ b/doc/py_api.rst @@ -77,7 +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.dump_config + bob.extension.scripts.click_helper.log_parameters + bob.extension.scripts.click_helper.assert_click_runner_result Core Functionality