soleil.cli_tools.solconfarg#

Classes

SolConfArg([config_source, resolve])

Enables adding soleil configured objects as arguments in argparse CLIs.

class soleil.cli_tools.solconfarg.SolConfArg(config_source: Path | str | None = None, resolve=True, **load_kwargs)#

Bases: object

Enables adding soleil configured objects as arguments in argparse CLIs.

Parameters:
  • config_source – The path to the configuration file to load. If not specified, will be required as a CLI argument.

  • resolve – Whether ArgumentParser.parse_args (equivalently, __call__()) returns the resolved or unresolved object.

  • load_kwargs – Any extra keyword arguments to pass internally to load_solconf().

Instances of this object can be passed as a value for the type keyword argument when calling argparse.ArgumentParser.add_argument() (see the argparse documentation).

Assuming no overrides are specified, doing so will set the parsed value of that argument to the resolved object loaded from the config_source initialization argument:

>>> from argparse import ArgumentParser
>>> from soleil.cli_tools import SolConfArg

>>> parser = ArgumentParser()
>>> parser.add_argument('my_obj', type=SolConfArg(soleil_examples / 'vanilla/main.solconf'))
ReduceAction(...)
>>> parser.parse_args([])
Namespace(my_obj={'a': 1, 'b': 2, 'c': 3})

Note

Monkey-patching of argparse

SolConfArg monkey-patches the built-in argparse module in order to support having a SolConfArg-typed argument consume multiple command line overrides and reduce them to a single parsed element (as opposed to a list with one entry per override).

The applied patch should not affect normal operation of argparse and is only applied once module soleil.cli_tools (or one of its member) is imported.

The source code for theses patches is in module soleil.cli_tools._argparse_patches.

Under the hood

Using a SolConfArg instance as the type of a parser argument implicitly sets the default value of the nargs and action arguments:

parser = ArgumentParser()
parser.add_argument(
  ...
  type=SolConfArg(...),
  action=<defaults to ReduceAction()>,
  nargs=<defaults to '+' or '*'>,
  ...
)
parser.parse_args([...])

The action keyword is set to an instance of ReduceAction() that is responsible for gathering all the CLI arguments corresponding to the SolConfArg() parser entry as overrides and resolving the described object with these overrides included by means of a call to SolConfArg.__call__().

Accordingly, the object returned for the argument added in the code above can also be obtained as follows:

sc = SolConfArg(...)
sc([...])

Note

For conciseness, we use the above syntax in examples below.

Number of consumed CLI arguments

If config_source is not provided at SolConfArg initialization, the first CLI argument will be used in its place. Accordingly CLI arguments of type SolConfArg will by default consume

  • one-or-more CLI arguments when config_source is not provided, or

  • zero-or-more CLI arguments when config_source is provided.

Any extra CLI arguments besides config_source are treated as CLI overrides.

This behavior is implemented by internally setting the default value of keyword argument nargs using

  • nargs='+' or nargs='*', respectively,

in the argparse.ArgumentParser.add_argument() call. Users can change this behavior, e.g., by using

  • nargs=1 or nargs=0, respectively,

in effect disabling CLI overrides.

Note

An argparse argument of type SolConfArg with the default nargs='+' or nargs='*' will consume an unbounded number of CLI arguments. Such arguments must either be the last non-optional argument added to the ArgumentParser object, or an optional argument.

Note

Even when SolConfArg is instantiated with a config_source argument, the value of config_source can be overriden from the command line using a source clobber override.

# Option 1: Path must be provided with argparse arguments
>>> sca1 = SolConfArg()
>>> sca1([soleil_examples/'vanilla/main.solconf'])
{'a': 1, 'b': 2, 'c': 3}

# Option 2: Path provided with argument definition
>>> sca2 = SolConfArg(soleil_examples/'vanilla/main.solconf')
>>> sca2(["a=10", "c=30"])
{'a': 10, 'b': 2, 'c': 30}

Source clobber

In the case where a path is specified in the initializer, it can still be overriden using a source clobber override:

# Source clobber assignment must be the first argument in the overrides list

>>> sca2 = SolConfArg(soleil_examples/'vanilla/main.solconf')
>>> sca2([f"**={soleil_examples/'vanilla/nested.solconf'}"])
{'letters': {'a': 1, 'b': 2, 'c': 3}}

Note that the source clobber override must be the first item in the overrides list.

Deeper overrides

The target of an override provided to the left of the override assignment operator can consist of any valid variable name path:

>>> sca = SolConfArg(soleil_examples/'vanilla/nested.solconf')
>>> sca() ##
{'letters': {'a': 1, 'b': 2, 'c': 3}}
>>> sca(['letters.b=20'])
{'letters': {'a': 1, 'b': 20, 'c': 3}}
property DFLT_ARGPARSE_KWARGS#

Sets the default argparser.add_argument keyword argument values to use when this instance is used as the type keyword argument.

__call__(overrides: List[str] | None = None)#

Resolves the argument, applying all input overrides.

get_config_source()#

Returns the config source path, which will depend on whether a config source was specified explicitly at initialization or not, and whether a source clobber override was specified.