Configuration¶
The structure of the configuration file is automatically derived from the parameters of the CLI and their types. There is no need to manually produce a configuration data structure to mirror the CLI.
Standalone option¶
The @config_option
decorator provided by Click Extra can be used as-is with vanilla Click:
from click import group, option, echo
from click_extra import config_option
@group(context_settings={"show_default": True})
@option("--dummy-flag/--no-flag")
@option("--my-list", multiple=True)
@config_option
def my_cli(dummy_flag, my_list):
echo(f"dummy_flag is {dummy_flag!r}")
echo(f"my_list is {my_list!r}")
@my_cli.command
@option("--int-param", type=int, default=10)
def subcommand(int_param):
echo(f"int_parameter is {int_param!r}")
The code above is saved into a file named my_cli.py
.
It produces the following help screen:
$ my-cli --help
Usage: my-cli [OPTIONS] COMMAND [ARGS]...
Options:
--dummy-flag / --no-flag [default: no-flag]
--my-list TEXT
-C, --config CONFIG_PATH Location of the configuration file. Supports glob
pattern of local path and remote URL. [default:
~/.config/my-cli/*.{toml,yaml,yml,json,ini,xml}]
--help Show this message and exit.
Commands:
subcommand
See there the explicit mention of the default location of the configuration file. This improves discoverability, and makes sysadmins happy, especially those not familiar with your CLI.
A bare call returns:
$ my-cli subcommand
dummy_flag is False
my_list is ()
int_parameter is 10
With a simple TOML file in the application folder, we will change the CLIâs default output.
Here is what ~/.config/my-cli/config.toml
contains:
# My default configuration file.
top_level_param = "is_ignored"
[my-cli]
extra_value = "is ignored too"
dummy_flag = true # New boolean default.
my_list = ["item 1", "item #2", "Very Last Item!"]
[garbage]
# An empty random section that will be skipped.
[my-cli.subcommand]
int_param = 3
random_stuff = "will be ignored"
In the file above, pay attention to:
the default configuration base path (
~/.config/my-cli/
here on Linux) which is OS-dependant;the appâs folder (
/my-cli/
) which is built from the scriptâs name (my_cli.py
);the top-level config section (
[my-cli]
), based on the CLIâs group ID (def my_cli()
);all the extra comments, sections and values that will be silently ignored.
Now we can verify the configuration file is properly read and change the defaults:
$ my-cli subcommand
dummy_flag is True
my_list is ('item 1', 'item #2', 'Very Last Item!')
int_parameter is 3
Precedence¶
The configuration loader fetch values according the following precedence:
CLI parameters
â
Configuration file
â
Environment variables
â
Defaults
The parameter will take the first value set in that chain.
See how inline parameters takes priority on defaults from the previous example:
$ my-cli subcommand --int-param 555
dummy_flag is True
my_list is ('item 1', 'item #2', 'Very Last Item!')
int_parameter is 555
Get configuration values¶
After gathering all the configuration from the different sources, and assembling them together following the precedence rules above, the configuration values are merged back into the Contextâs default_map
. But only the values that are matching the CLIâs parameters are kept and passed as defaults. All others are silently ignored.
You can still access the full configuration by looking into the contextâs meta
attribute:
from click_extra import option, echo, pass_context, command, config_option
@command
@option("--int-param", type=int, default=10)
@config_option
@pass_context
def my_cli(ctx, int_param):
echo(f"Configuration location: {ctx.meta['click_extra.conf_source']}")
echo(f"Full configuration: {ctx.meta['click_extra.conf_full']}")
echo(f"Default values: {ctx.default_map}")
echo(f"int_param is {int_param!r}")
[my-cli]
int_param = 3
random_stuff = "will be ignored"
[garbage]
dummy_flag = true
$ my-cli --config ./conf.toml --int-param 999
Load configuration matching ./conf.toml
Configuration location: /home/me/conf.toml
Full configuration: {'my-cli': {'int_param': 3, 'random_stuff': 'will be ignored'}, 'garbage': {'dummy_flag': True}}
Default values: {'int_param': 3}
int_parameter is 999
Hint
Variables in meta
are presented in their original Python type:
click_extra.conf_source
is either a normalizedPath
orURL
objectclick_extra.conf_full
is adict
whose values are eitherstr
or richer types, depending on the capabilities of each format
Strictness¶
As you can see in the first example above, all unrecognized content is ignored.
If for any reason you do not want to allow any garbage in configuration files provided by the user, you can use the strict
argument.
Given this cli.toml
file:
[cli]
int_param = 3
random_param = "forbidden"
The use of strict=True
parameter in the CLI below:
from click import command, option, echo
from click_extra import config_option
@command
@option("--int-param", type=int, default=10)
@config_option(strict=True)
def cli(int_param):
echo(f"int_parameter is {int_param!r}")
Will raise an error and stop the CLI execution on unrecognized random_param
value:
$ cli --config "cli.toml"
Load configuration matching cli.toml
(...)
ValueError: Parameter 'random_param' is not allowed in configuration file.
Excluding parameters¶
The excluded_params
argument allows you to block some of your CLI options to be loaded from configuration. By setting this argument, you will prevent your CLI users to set these parameters in their configuration file.
It defaults to the value of ParamStructure.DEFAULT_EXCLUDED_PARAMS
.
You can set your own list of option to ignore with the excluded_params
argument:
from click import command, option, echo
from click_extra import config_option
@command
@option("--int-param", type=int, default=10)
@config_option(excluded_params=["my-cli.non_configurable_option", "my-cli.dangerous_param"])
def my_cli(int_param):
echo(f"int_parameter is {int_param!r}")
Attention
You need to provide the fully-qualified ID of the option youâre looking to block. I.e. the dot-separated ID that is prefixed by the CLI name. That way you can specify an option to ignore at any level, including subcommands.
If you have difficulties identifying your options and their IDs, run your CLI with the --show-params
option for introspection.
Formats¶
Several dialects are supported:
JSON
, with inline and block comments (Python-style#
and Javascript-style//
, thanks tocommentjson
)INI
, with extended interpolation, multi-level sections and non-native types (list
,set
, âŠ)
TOML¶
See the example in the top of this page.
YAML¶
The example above, given for a TOML configuration file, is working as-is with YAML.
Just replace the TOML file with the following configuration at
~/.config/my-cli/config.yaml
:
# My default configuration file.
top_level_param: is_ignored
my-cli:
extra_value: is ignored too
dummy_flag: true # New boolean default.
my_list:
- point 1
- 'point #2'
- Very Last Point!
subcommand:
int_param: 77
random_stuff: will be ignored
garbage: >
An empty random section that will be skipped
$ my-cli --config "~/.config/my-cli/config.yaml" subcommand
dummy_flag is True
my_list is ('point 1', 'point #2', 'Very Last Point!')
int_parameter is 77
JSON¶
Again, same for JSON:
{
"top_level_param": "is_ignored",
"garbage": {},
"my-cli": {
"dummy_flag": true,
"extra_value": "is ignored too",
"my_list": [
"item 1",
"item #2",
"Very Last Item!"
],
"subcommand": {
"int_param": 65,
"random_stuff": "will be ignored"
}
}
}
$ my-cli --config "~/.config/my-cli/config.json" subcommand
dummy_flag is True
my_list is ('item 1', 'item #2', 'Very Last Item!')
int_parameter is 65
INI¶
INI
configuration files are allowed to use ExtendedInterpolation
by default.
Todo
Write example.
XML¶
Todo
Write example.
Pattern matching¶
The configuration file is searched based on a wildcard-based pattern.
By default, the pattern is /<app_dir>/*.{toml,yaml,yml,json,ini,xml}
, where:
<app_dir>
is the default application folder (see section below)*.{toml,yaml,yml,json,ini,xml}
is any file in that folder with any of.toml
,.yaml
,.yml
,.json
,.ini
or.xml
extension.
See also
There is a long history about the choice of the default application folder.
For Unix, the oldest reference I can track is from the Where Configurations Live chapter of The Art of Unix Programming by Eric S. Raymond.
The XDG Base Directory Specification is the latest iteration of this tradition on Linux. This long-due guidelines brings lots of benefits to the platform. This is what Click Extra is implementing by default.
But there is still a lot of cases for which the XDG doesnât cut it, like on other platforms (macOS, Windows, âŠ) or for legacy applications. Thatâs why Click Extra allows you to customize the way configuration is searched and located.
Default folder¶
The configuration file is searched in the default application path, as defined by click.get_app_dir()
.
Like the latter, the @config_option
decorator and ConfigOption
class accept a roaming
and force_posix
argument to alter the default path:
Platform |
|
|
Folder |
---|---|---|---|
macOS (default) |
- |
|
|
macOS |
- |
|
|
Unix (default) |
- |
|
|
Unix |
- |
|
|
Windows (default) |
|
- |
|
Windows |
|
- |
|
Letâs change the default base folder in the following example:
from click import command
from click_extra import config_option
@command(context_settings={"show_default": True})
@config_option(force_posix=True)
def cli():
pass
See how the default to --config
option has been changed to ~/.cli/*.{toml,yaml,yml,json,ini,xml}
:
$ cli --help
Usage: cli [OPTIONS]
Options:
-C, --config CONFIG_PATH Location of the configuration file. Supports glob
pattern of local path and remote URL. [default:
~/.cli/*.{toml,yaml,yml,json,ini,xml}]
--help Show this message and exit.
Custom pattern¶
If youâd like to customize the pattern, you can pass your own to the default
parameter.
Here is how to look for an extension-less YAML dotfile in the home directory, with a pre-defined .commandrc
name:
from click import command
from click_extra import config_option
from click_extra.config import Formats
@command(context_settings={"show_default": True})
@config_option(default="~/.commandrc", formats=Formats.YAML)
def cli():
pass
$ cli --help
Usage: cli [OPTIONS]
Options:
-C, --config CONFIG_PATH Location of the configuration file. Supports glob
pattern of local path and remote URL. [default:
~/.commandrc]
--help Show this message and exit.
Pattern specifications¶
Patterns provided to @config_option
:
should be written with Unix separators (
/
), even for Windows (the pattern will be normalized to the local platform dialect)are configured with the following default flags:
IGNORECASE
: case-insensitive matchingGLOBSTAR
: recursive directory search via**
FOLLOW
: traverse symlink directoriesDOTGLOB
: allow match of file or directory starting with a dot (.
)BRACE
: allow brace expansion for greater expressivenessGLOBTILDE
: allows for user path expansion via~
NODIR
: restricts results to files
Default extensions¶
The extensions that are used for each dialect to produce the default file pattern matching are encoded by
the Formats
Enum:
Format |
Extensions |
---|---|
|
|
|
|
|
|
|
|
|
|
Multi-format matching¶
The default behavior consist in searching for all files matching the default *.{toml,yaml,yml,json,ini,xml}
pattern.
A parsing attempt is made for each file matching the extension pattern, in the order of the table above.
As soon as a file is able to be parsed without error and returns a dict
, the search stops and the file is used to feed the CLIâs default values.
Forcing formats¶
If you know in advance the only format youâd like to support, you can use the formats
argument on your decorator like so:
from click import command, option, echo
from click_extra import config_option
from click_extra.config import Formats
@command(context_settings={"show_default": True})
@option("--int-param", type=int, default=10)
@config_option(formats=Formats.JSON)
def cli(int_param):
echo(f"int_parameter is {int_param!r}")
Notice how the default search pattern gets limited to files with a .json
extension:
$ cli --help
Usage: cli [OPTIONS]
Options:
--int-param INTEGER [default: 10]
-C, --config CONFIG_PATH Location of the configuration file. Supports glob
pattern of local path and remote URL. [default:
~/.config/cli/*.json]
--help Show this message and exit.
This also works with a subset of formats:
from click import command, option, echo
from click_extra import config_option
from click_extra.config import Formats
@command(context_settings={"show_default": True})
@option("--int-param", type=int, default=10)
@config_option(formats=[Formats.INI, Formats.YAML])
def cli(int_param):
echo(f"int_parameter is {int_param!r}")
$ cli --help
Usage: cli [OPTIONS]
Options:
--int-param INTEGER [default: 10]
-C, --config CONFIG_PATH Location of the configuration file. Supports glob
pattern of local path and remote URL. [default:
~/.config/cli/*.{ini,yaml,yml}]
--help Show this message and exit.
Remote URL¶
Remote URL can be passed directly to the --config
option:
$ my-cli --config "https://example.com/dummy/configuration.yaml" subcommand
dummy_flag is True
my_list is ('point 1', 'point #2', 'Very Last Point!')
int_parameter is 77
click_extra.config
API¶
classDiagram Enum <|-- Formats ExtraOption <|-- ConfigOption ParamStructure <|-- ConfigOption
Utilities to load parameters and options from a configuration file.
- class click_extra.config.Formats(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]¶
Bases:
Enum
Supported configuration formats and the list of their default extensions.
The default order set the priority by which each format is searched for the default configuration file.
- TOML = ('toml',)¶
- YAML = ('yaml', 'yml')¶
- JSON = ('json',)¶
- INI = ('ini',)¶
- XML = ('xml',)¶
- class click_extra.config.ConfigOption(param_decls=None, metavar='CONFIG_PATH', type=STRING, help='Location of the configuration file. Supports glob pattern of local path and remote URL.', is_eager=True, expose_value=False, formats=(Formats.TOML, Formats.YAML, Formats.JSON, Formats.INI, Formats.XML), roaming=True, force_posix=False, excluded_params=None, strict=False, **kwargs)[source]¶
Bases:
ExtraOption
,ParamStructure
A pre-configured option adding
--config
/-C
option.Takes as input a glob pattern or an URL.
Glob patterns must follow the syntax of wcmatch.glob.
is_eager
is active by default so the config optionâscallback
gets the opportunity to set thedefault_map
values before the other options use them.formats
is the ordered list of formats that the configuration file will be tried to be read with. Can be a single one.roaming
andforce_posix
are fed to click.get_app_dir() to setup the default configuration folder.excluded_params
is a list of options to ignore by the configuration parser. Defaults toParamStructure.DEFAULT_EXCLUDED_PARAMS
.strict
If
True
, raise an error if the configuration file contain unrecognized content.If
False
, silently ignore unsupported configuration option.
- formats: Sequence[Formats]¶
- roaming: bool¶
- force_posix: bool¶
- strict: bool¶
- default_pattern()[source]¶
Returns the default pattern used to search for the configuration file.
Defaults to
/<app_dir>/*.{toml,yaml,yml,json,ini,xml}
. Where<app_dir>
is produced by the clickget_app_dir() method. The result depends on OS and is influenced by theroaming
andforce_posix
properties of this instance.In that folder, weâre looking for any file matching the extensions derived from the
self.formats
property: :rtype:str
a simple
*.ext
pattern if only one format is setan expanded
*.{ext1,ext2,...}
pattern if multiple formats are set
- get_help_record(ctx)[source]¶
Replaces the default value by the pretty version of the configuration matching pattern.
- search_and_read_conf(pattern)[source]¶
Search on local file system or remote URL files matching the provided pattern.
pattern
is considered an URL only if it is parseable as such and starts withhttp://
orhttps://
.Returns an iterator of the normalized configuration location and its textual content, for each file/URL matching the pattern.
- parse_conf(conf_text)[source]¶
Try to parse the provided content with each format in the order provided by the user.
A successful parsing in any format is supposed to return a
dict
. Any other result, including any raised exception, is considered a failure and the next format is tried.
- default: t.Union[t.Any, t.Callable[[], t.Any]]¶
- type: types.ParamType¶
- is_flag: bool¶
- is_bool_flag: bool¶
- flag_value: t.Any¶
- name: t.Optional[str]¶
- opts: t.List[str]¶
- secondary_opts: t.List[str]¶
- read_and_parse_conf(pattern)[source]¶
Search for a configuration file matching the provided pattern.
Returns the location and parsed content of the first valid configuration file that is not blank, or (None, None) if no file was found.
- load_ini_config(content)[source]¶
Utility method to parse INI configuration file.
Internal convention is to use a dot (
.
, as set byself.SEP
) in section IDs as a separator between levels. This is a workaround the limitation ofINI
format which doesnât allow for sub-sections.Returns a ready-to-use data structure.
- merge_default_map(ctx, user_conf)[source]¶
Save the user configuration into the contextâs
default_map
.Merge the user configuration into the pre-computed template structure, which will filter out all unrecognized options not supported by the command. Then cleans up blank values and update the contextâs
default_map
.- Return type:
- load_conf(ctx, param, path_pattern)[source]¶
Fetch parameters values from configuration file and sets them as defaults.
User configuration is merged to the contextâs default_map, like Click does.
By relying on Clickâs default_map, we make sure that precedence is respected. And direct CLI parameters, environment variables or interactive prompts takes precedence over any values from the config file.
- Return type: