Configuration files¶
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
--config CONFIG_PATH Location of the configuration file. Supports local
path with glob patterns or remote URL. [default:
~/.config/my-cli/{*.toml,*.yaml,*.yml,*.json,*.json5
,*.jsonc,*.hjson,*.ini,*.xml,pyproject.toml}]
--help Show this message and exit.
Commands:
subcommand
See in the result above, there is an explicit mention of the default location of the configuration file ([default: ~/.config/my-cli/{*.toml,*.yaml,*.yml,*.json,*.json5,*.jsonc,*.hjson,*.ini,*.xml,pyproject.toml}]). 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:
~/.config/my-cli/config.toml¶# 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, which is OS-dependant (the
~/.config/my-cli/path here is for Linux) ;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
Dotted keys¶
Configuration files support dotted keys as a shorthand for nested structures. Instead of writing:
[my-cli.subcommand]
int_param = 3
You can write:
[my-cli]
"subcommand.int_param" = 3
Both forms are equivalent. You can also freely mix them in the same file:
{
"my-cli": {
"dummy_flag": true,
"subcommand.int_param": 3,
"subcommand": {
"other_param": "value"
}
}
}
Dotted keys are expanded into nested dicts and deep-merged before the configuration is applied. This works across all supported formats, and at any nesting depth (e.g. "subcommand.nested.option" expands to three levels).
Hint
This is especially handy in formats like JSON that have no native section syntax, letting you keep a flat structure when the nesting would be excessive.
Merge rules¶
When dotted keys and nested structures target the same leaf, the last one in file order wins:
{
"my-cli": {
"subcommand": {"int_param": 3},
"subcommand.int_param": 77
}
}
Here int_param resolves to 77 because the dotted key appears after the nested one.
Conflicts¶
A conflict occurs when the same key is used as both a scalar and a namespace. For example:
{
"my-cli": {
"subcommand": "some_value",
"subcommand.int_param": 3
}
}
Here subcommand is a plain string, but subcommand.int_param requires it to be a dict. By default, Click Extra logs a warning and the last value wins â in this case, subcommand becomes {"int_param": 3}, silently dropping "some_value".
In strict mode, conflicts and invalid dotted keys raise a ValueError instead of being silently resolved.
The same conflict detection applies at deeper levels:
{
"my-cli": {
"subcommand.int_param.nested": 1,
"subcommand.int_param": 2
}
}
Here int_param is set to both {"nested": 1} (via the first key) and 2 (via the second). A warning is logged and int_param resolves to 2.
Note
Most formats prevent these conflicts at parse time â TOML rejects a key used as both a scalar and a table, YAML forbids duplicate keys â so in practice this mainly affects JSON.
Invalid dotted keys¶
Dotted keys with empty segments (leading, trailing, or consecutive dots) are skipped with a warning:
{
"my-cli": {
".option": 1,
"option.": 2,
"sub..option": 3
}
}
All three keys above are ignored. Use --verbosity WARNING or higher to see the warnings. In strict mode, they raise a ValueError.
Precedence¶
The configuration loader fetch values according the following precedence:
Interactive promptâ
CLI parametersâ
Environment variablesâ
Configuration fileâ
Defaults
The parameter will take the first value set in that chain.
Configuration file values are loaded into Clickâs default_map, so they are reported as DEFAULT_MAP and sit below environment variables in the hierarchy.
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}")
./conf.toml¶[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_sourceis either a normalizedPathorURLobjectclick_extra.conf_fullis adictwhose values are eitherstror 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.toml¶[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.
Tip
If you want to check a configuration file for unrecognized keys without running the CLI, see the --validate-config option below.
Validating configuration files¶
The @validate_config_option decorator adds a --validate-config CONFIG_PATH option that checks whether a configuration file is well-formed and contains only recognized parameters, then exits. This is useful for CI pipelines, editor integrations, or simply verifying a configuration file before deploying it.
Reusing the standalone option example above:
from click import group, option, echo
from click_extra import config_option, validate_config_option
@group
@option("--dummy-flag/--no-flag")
@option("--my-list", multiple=True)
@config_option
@validate_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}")
A valid configuration file:
good.toml¶[my-cli]
dummy_flag = true
my_list = ["pip", "npm"]
[my-cli.subcommand]
int_param = 3
$ my-cli --validate-config good.toml
Configuration file good.toml is valid.
$ echo $?
0
A configuration file with unrecognized keys:
bad.toml¶[my-cli]
dummy_flag = true
unknown_key = "oops"
$ my-cli --validate-config bad.toml
Configuration validation error: Parameter 'unknown_key' found in second dict but not in first.
$ echo $?
1
An unparsable file produces exit code 2:
$ my-cli --validate-config garbage.txt
Error parsing garbage.txt as TOML, YAML, JSON, INI, XML or pyproject.toml.
$ echo $?
2
The exit codes are:
Exit code |
Meaning |
|---|---|
|
Configuration file is valid |
|
Validation error (unrecognized keys) |
|
File not found or cannot be parsed |
Note
--validate-config always validates in strict mode, regardless of the strict setting on @config_option. It requires a sibling @config_option decorator to be present on the same command.
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 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}")
Hint
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.
Including parameters¶
The included_params argument is the inverse of excluded_params: only the listed parameters will be loaded from the configuration file. All other parameters found in the configuration will be ignored.
from click import command, option, echo
from click_extra import config_option
@command
@option("--flag-a/--no-flag-a")
@option("--flag-b/--no-flag-b")
@config_option(included_params=("my-cli.flag_a",))
def my_cli(flag_a, flag_b):
echo(f"flag_a={flag_a!r}")
echo(f"flag_b={flag_b!r}")
In the example above, only flag_a will be loaded from configuration. flag_b will keep its CLI default even if it is present in the configuration file.
Caution
included_params and excluded_params are mutually exclusive. Providing both will raise a ValueError.
Hint
Like excluded_params, you need to provide the fully-qualified ID of the option. Run your CLI with the --show-params option to discover parameter IDs.
Disabling autodiscovery¶
By default, @config_option automatically searches for configuration files in the default application folder. If you want to disable this autodiscovery and only load a configuration file when the user explicitly passes --config <path>, use the NO_CONFIG sentinel as the default:
from click import group, option, echo
from click_extra import config_option, NO_CONFIG
@group(context_settings={"show_default": True})
@option("--dummy-flag/--no-flag")
@config_option(default=NO_CONFIG)
def my_cli(dummy_flag):
echo(f"dummy_flag is {dummy_flag!r}")
With this setup:
The
--helpoutput shows[default: disabled]instead of a filesystem path.Running the CLI without
--configproduces no configuration-related output on stderr.Users can still explicitly pass
--config <path>to load a specific configuration file.The
--no-configflag (if added via@no_config_option) still prints the âSkip configuration file loading altogether.â message when used explicitly.
This is useful for CLIs where configuration files are opt-in rather than opt-out, or when you want to avoid side effects from automatically discovered configuration files during development or testing.
Default subcommands¶
You can specify which subcommands run by default when a group is invoked without any explicit subcommands on the CLI. This is done via the _default_subcommands reserved configuration key.
Given this CLI:
from click_extra import group, command, echo, config_option, option
@group
@config_option
def my_cli():
pass
@my_cli.command()
@option("--path", default="/tmp")
def backup(path):
echo(f"Backing up {path}")
@my_cli.command()
def sync():
echo("Syncing")
And this TOML configuration:
[my-cli]
_default_subcommands = ["backup"]
[my-cli.backup]
path = "/home"
Running my-cli alone will automatically invoke the backup subcommand:
$ my-cli
Backing up /home
Chained commands¶
For groups created with chain=True, you can list multiple default subcommands. They run in the order specified:
[my-cli]
_default_subcommands = ["backup", "sync"]
$ my-cli
Backing up /home
Syncing
Note
Non-chained groups only accept a single default subcommand. Listing more than one will produce an error.
CLI precedence¶
If the user names subcommands explicitly on the command line, the _default_subcommands configuration is ignored:
$ my-cli sync
Syncing
Prepend subcommands¶
The _prepend_subcommands key always prepends subcommands to every invocation, regardless of whether CLI subcommands are provided. This is useful for always injecting a subcommand (e.g. debug) on a dev machine.
Important
_prepend_subcommands only works with chain=True groups. Non-chained groups resolve exactly one subcommand, so prepending would break the userâs intended command.
[my-cli]
_prepend_subcommands = ["debug"]
Running my-cli sync effectively becomes my-cli debug sync:
$ my-cli sync
Debug mode activated
Syncing
_default_subcommands with _prepend_subcommands¶
When both keys are set and no CLI subcommands are given, _default_subcommands fires first, then _prepend_subcommands is prepended. The result is [*prepend, *defaults]:
[my-cli]
_default_subcommands = ["sync"]
_prepend_subcommands = ["debug"]
$ my-cli
Debug mode activated
Syncing
When CLI subcommands are given explicitly, _default_subcommands is ignored but _prepend_subcommands still applies:
$ my-cli backup
Debug mode activated
Backing up /tmp
Formats¶
Several dialects are supported:
Format |
Extensions |
Description |
Enabled by default |
|---|---|---|---|
|
- |
â |
|
|
- |
â |
|
|
- |
â |
|
|
â |
||
|
Like JSON, but with comments and trailing commas |
â |
|
|
Another flavor of a user-friendly JSON |
â |
|
|
With extended interpolation, multi-level sections and non-native types ( |
â |
|
|
- |
â |
|
|
Reads |
â |
Formats depending on third-party packages are not enabled by default. You need to install Click Extra with the corresponding extra dependency group to enable them.
TOML¶
See the example in the top of this page.
YAML¶
Important
YAML support requires additional packages. You need to install click-extra[yaml] extra dependency group to enable it.
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:
~/.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:
~/.config/my-cli/config.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
JSON5¶
Important
JSON5 support requires additional packages. You need to install click-extra[json5] extra dependency group to enable it.
Todo
Write example.
JSONC¶
Important
JSONC support requires additional packages. You need to install click-extra[jsonc] extra dependency group to enable it.
Todo
Write example.
HJSON¶
Important
HJSON support requires additional packages. You need to install click-extra[hjson] extra dependency group to enable it.
Todo
Write example.
INI¶
INI configuration files are allowed to use ExtendedInterpolation by default.
Todo
Write example.
XML¶
Important
XML support requires additional packages. You need to install click-extra[xml] extra dependency group to enable it.
Todo
Write example.
pyproject.toml¶
The PYPROJECT_TOML format reads [tool.<cli-name>] sections from a pyproject.toml file, following PEP 518. This is useful for any CLI tool that wants to store its configuration alongside project metadata â not just Python projects. Tools like ruff and typos, which are not Python projects, all use this convention, to play nice with other communities and increase adoption.
Tip
pyproject.toml is becoming the standard place to centralize tool configuration for Python projects. Instead of scattering dedicated config files at the root of your repository (ruff.toml, typos.toml, mypy.ini, âŠ), you can consolidate them all under [tool.*] sections in a single pyproject.toml. This keeps the repository root clean, makes it easy to review and coordinate tool configurations in one place, and reduces the number of files contributors need to discover.
PYPROJECT_TOML is included in the default format patterns, so it is automatically discovered alongside other formats. The [tool] wrapper is automatically unwrapped: merge_default_map sees {"cli": {"int_param": 3}} â exactly the same structure as a regular TOML config file.
Given a pyproject.toml in the search path:
pyproject.toml¶[build-system]
requires = ["setuptools"]
[tool.cli]
int_param = 3
This is especially powerful combined with search_parents to walk up from a project directory:
from click import command, option, echo
from click_extra import config_option
@command
@option("--int-param", type=int, default=10)
@config_option(search_parents=True)
def cli(int_param):
echo(f"int_parameter is {int_param!r}")
Running cli from anywhere inside the project tree will find pyproject.toml at the repository root and apply [tool.cli] values. The walk automatically stops at the VCS root.
Dedicated file wins, no merging¶
When both a dedicated configuration file (e.g., my-cli.toml) and a pyproject.toml with a [tool.my-cli] section exist, Click Extra uses the first parseable file it finds and ignores all others. There is no merging across files.
This is the de facto standard across the ecosystem. Every major tool that supports both a dedicated config file and pyproject.toml follows the same strict precedence â dedicated file wins, pyproject.toml is ignored entirely:
Tool |
Precedence rule |
|---|---|
|
|
|
|
|
The rationale:
No merging surprises. Merging two config sources creates ambiguity: which key wins when both files define it? Are arrays concatenated or replaced? Every tool above chose âfirst match wins, full stopâ to avoid this class of problems entirely.
Explicit intent. A dedicated file at the repository root, named after the tool, is the most visible and explicit signal. If someone creates one alongside a
[tool.*]section, the dedicated file represents a deliberate override.Clean migration path. Users moving from a dedicated file to
pyproject.tomlsimply delete the dedicated file. Users who need the dedicated file (e.g., sharing it across non-Python repos) keep it andpyproject.tomlis silently ignored.
See also
Other non-Python tools that support [tool.*] in pyproject.toml:
uv,
ty,
maturin,
Pyright,
Tombi,
and rumdl.
Other tools are following suit: lychee#1930, zizmor#322, taplo#603, actionlint#623, biome#9239, sh#1268.
Search pattern¶
The configuration file is searched with a wildcard-based glob pattern.
There is multiple stages to locate and parse the configuration file:
Locate all files matching the search pattern
Match each file against the supported formats, in order, until one is successfully parsed
Use the first successfully parsed file as the configuration source
By default, the pattern is <app_dir>/{*.toml,*.json,*.ini}, where:
<app_dir>is the default application folder{*.toml,*.json,*.ini}are the extensions of formats enabled by default, wrapped in brace-expansion syntax
Hint
Depending on the formats you enabled in your installation of Click Extra, the default extensions may vary. For example, if you installed Click Extra with all extra dependencies, the default extensions would be extended to {*.toml,*.yaml,*.yml,*.json,*.json5,*.jsonc,*.hjson,*.ini,*.xml,pyproject.toml}.
Tip
The search process can be hard to follow. To help you see clearly, you can enable debug logging for the click_extra logger to see which files are located, matched, parsed, skipped, and finally used.
Or better, just pass the --verbosity DEBUG option to your CLI if it is powered by Click Extra.
Default folder¶
The configuration file is searched in the default application path, as defined by click.get_app_dir().
To mirror the latter, the @config_option decorator 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 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/:
$ cli --help
Usage: cli [OPTIONS]
Options:
--config CONFIG_PATH Location of the configuration file. Supports local path
with glob patterns or remote URL. [default: ~/.cli/{*.t
oml,*.yaml,*.yml,*.json,*.json5,*.jsonc,*.hjson,*.ini,*.
xml,pyproject.toml}]
--help Show this message and exit.
See also
The default application folder concept has a long and complicated history in the Unix world.
The oldest reference I can track is from the Where Configurations Live chapter from The Art of Unix Programming.
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.
Custom pattern¶
You can also provide a custom path to the configuration file via the --config option added to your CLI by the @config_option decorator.
To change the default search pattern, pass a customized value to the default argument of the decorator:
from click import command
from click_extra import config_option
@command(context_settings={"show_default": True})
@config_option(default="~/my_special_folder/*.toml")
def cli():
pass
$ cli --help
Usage: cli [OPTIONS]
Options:
--config CONFIG_PATH Location of the configuration file. Supports local path
with glob patterns or remote URL. [default:
~/my_special_folder/*.toml]
--help Show this message and exit.
The rules for the pattern are described in the next section.
Search pattern specifications¶
Patterns provided to @config_optionâs default argument:
Should be written with Unix separators (
/), even for Windows: the pattern will be normalized to the local platform dialect.Can be absolute or relative paths.
Have their default case-sensitivity aligned with the local platform:
Windows is insensitive to case,
Unix and macOS are case-sensitive.
Are setup with the following default flags:
Flag
Description
Recursive directory search via
**glob notation.Traverse symlink directories.
Include file or directory starting with a literal dot (
.).Expand
{pat1,pat2,...}brace expressions into multiple patterns.Allow multiple patterns separated by
|.Allow userâs home path
~to be expanded.Restricts results to files.
Important
The BRACE flag is always forced, so that multi-format default patterns using {pat1,pat2,...} syntax expand correctly. The NODIR flag is always forced, to optimize the search for files only.
The flags above can be changed via the search_pattern_flags argument of the decorator. So to make the matching case-insensitive, add the IGNORECASE flag:
from wcmatch.glob import (
GLOBSTAR,
FOLLOW,
DOTGLOB,
BRACE,
SPLIT,
GLOBTILDE,
NODIR,
IGNORECASE
)
@config_option(
search_pattern_flags=(
GLOBSTAR | FOLLOW | DOTGLOB | BRACE | SPLIT | GLOBTILDE | NODIR | IGNORECASE
)
)
But because of the way flags works, you have to re-specify all flags you want to keep, including the default ones.
See also
This is the same pinciple as file pattern flags.
Multi-format matching¶
The default behavior consist in searching for all files matching the default {*.toml,*.json,*.ini} pattern. Or more, depending on the extra dependencies installed with Click Extra.
As soon as files are located, they are matched against each supported format, in order, until one is successfully parsed.
The first successfully parsed file is used to feed the CLIâs default values.
The search will only consider matches that:
exists,
are a file,
are not empty,
matches file format patterns,
can be parsed successfully, and
produce a non-empty data structure.
All others are skipped. And the search continues with the next matching file.
To influence which formats are supported, see the next section.
Format selection¶
If you want to limit the formats supported by your CLI, you can use the file_format_patterns argument to specify which formats are allowed:
from click import command, option, echo
from click_extra import config_option, ConfigFormat
@command(context_settings={"show_default": True})
@option("--int-param", type=int, default=10)
@config_option(file_format_patterns=[ConfigFormat.JSON, ConfigFormat.TOML])
def cli(int_param):
echo(f"int_parameter is {int_param!r}")
Notice how the default search pattern has been restricted to only *.json and *.toml files, and also that the order is reflected in the help:
$ cli --help
Usage: cli [OPTIONS]
Options:
--int-param INTEGER [default: 10]
--config CONFIG_PATH Location of the configuration file. Supports local path
with glob patterns or remote URL. [default:
~/.config/cli/{*.json,*.toml}]
--help Show this message and exit.
You can also specify a single format:
from click import command, option, echo
from click_extra import config_option, ConfigFormat
@command(context_settings={"show_default": True})
@option("--int-param", type=int, default=10)
@config_option(file_format_patterns=ConfigFormat.XML)
def cli(int_param):
echo(f"int_parameter is {int_param!r}")
$ cli --help
Usage: cli [OPTIONS]
Options:
--int-param INTEGER [default: 10]
--config CONFIG_PATH Location of the configuration file. Supports local path
with glob patterns or remote URL. [default:
~/.config/cli/*.xml]
--help Show this message and exit.
Custom file format patterns¶
Each format is associated with default file patterns. But you can also change these with the same file_format_patterns argument:
from click import command, option, echo
from click_extra import config_option, ConfigFormat
@command(context_settings={"show_default": True})
@option("--int-param", type=int, default=10)
@config_option(
file_format_patterns={
ConfigFormat.TOML: ["*.toml", "my_app.conf"],
ConfigFormat.JSON: ["settings*.js", "*.json"],
}
)
def cli(int_param):
echo(f"int_parameter is {int_param!r}")
Again, this is reflected in the help:
$ cli --help
Usage: cli [OPTIONS]
Options:
--int-param INTEGER [default: 10]
--config CONFIG_PATH Location of the configuration file. Supports local path
with glob patterns or remote URL. [default:
~/.config/cli/{*.toml,my_app.conf,settings*.js,*.json}]
--help Show this message and exit.
Parsing priority¶
The syntax of file_format_patterns argument allows you to specify either a list of formats, a single format, or a mapping of formats to patterns. And we can even have multiple formats share the same pattern:
from click import command, option, echo
from click_extra import config_option, ConfigFormat
@command(context_settings={"show_default": True})
@option("--int-param", type=int, default=10)
@config_option(
file_format_patterns={
ConfigFormat.TOML: "*.toml",
ConfigFormat.JSON5: "config*.js",
ConfigFormat.JSON: ["config*.js", "*.js"],
}
)
def cli(int_param):
echo(f"int_parameter is {int_param!r}")
Notice how all formats are merged into the same pattern:
$ cli --help
Usage: cli [OPTIONS]
Options:
--int-param INTEGER [default: 10]
--config CONFIG_PATH Location of the configuration file. Supports local path
with glob patterns or remote URL. [default:
~/.config/cli/{*.toml,config*.js,*.js}]
--help Show this message and exit.
What will happen in this case is that the search will try to parse matching files first as JSON5, then as JSON. The first format that successfully parses the file will be used.
So a file named config123.js containing valid JSON5 syntax will be parsed as such, even if it also contains valid JSON syntax and match the *.js pattern. But if for any reason the JSON5 parsing fails, the search will try to parse it as JSON next, which is the second-best match.
On the other hand, a file named settings.js will only be tried as JSON, since it doesnât match the JSON5 pattern.
This illustrates the flexibility of this approach, but how the order of formats matter.
File pattern flags¶
The file_pattern_flags argument controls the matching behavior of file patterns.
These flags are defined in wcmatch.fnmatch and default to:
Flag |
Description |
|---|---|
Adds support of |
|
Allow multiple patterns separated by ` |
Important
The SPLIT flag is always forced, as our multi-pattern design relies on it.
If for example, you want to make the matching case-insensitive, you do that by adding the IGNORECASE flag:
from wcmatch.fnmatch import NEGATE, SPLIT, IGNORECASE
@config_option(file_pattern_flags=NEGATE | SPLIT | IGNORECASE)
But because of the way flags works, you have to re-specify all flags you want to keep, including the default ones.
See also
This is the same pinciple as search pattern specifications.
Excluding files¶
Negation is active by default, which is useful when you want to exclude some files from being considered during the search.
To ignore, for example, all your template files residing alongside real configuration files. Then, to exclude all files starting with template_ in their name, you can do:
@config_option(
file_format_patterns={
ConfigFormat.TOML: ["*.toml", "!template_*.toml"],
}
)
Extension-less files¶
This demonstrate the popular case on Unix-like systems, where the configuration file is an extension-less dotfile in the home directory.
Here is how to set up @config_option for a pre-defined .commandrc file in YAML:
from click import command
from click_extra import config_option, ConfigFormat
@command(context_settings={"show_default": True})
@config_option(
default="~/.commandrc",
file_format_patterns={ConfigFormat.YAML: ".commandrc"}
)
def cli():
pass
$ cli --help
Usage: cli [OPTIONS]
Options:
--config CONFIG_PATH Location of the configuration file. Supports local path
with glob patterns or remote URL. [default:
~/.commandrc]
--help Show this message and exit.
Caution
Depending on how you set up your patterns, files starting with a dot (.) may not be matched by default. Make sure to include the DOTMATCH flag in file_pattern_flags if needed.
Parent folder search¶
By default, configuration files are only searched in the default application folder. With search_parents=True, Click Extra also walks up the directory tree from the search location to the filesystem root, looking for matching files at each level:
from click import command
from click_extra import config_option
@command
@config_option(search_parents=True)
def cli():
pass
For a CLI named cli on a Unix system, this searches for configuration files in:
~/.config/cli/{*.toml,*.yaml,âŠ}(the default location)~/.config/{*.toml,*.yaml,âŠ}~/{*.toml,*.yaml,âŠ}/{*.toml,*.yaml,âŠ}
The first successfully parsed file wins. This is useful for monorepo or project-local configuration, where a config file placed higher in the tree acts as a fallback.
Note
Parent search works with both plain paths and glob patterns. For glob patterns, the non-magic directory prefix is identified and the file pattern is searched at each parent level via root_dir. Entirely magic patterns like *.toml have no directory prefix to walk up, so only the original pattern is searched.
Walk boundaries¶
The parent directory walk stops as soon as it hits any of the following boundaries:
Filesystem root â the walk always stops at
/(or the drive root on Windows).Inaccessible directory â if a parent directory exists but is not readable, the walk stops immediately.
VCS root (
stop_at=VCS, the default) â the walk stops at the nearest repository root (a directory containing.gitor.hg). If no VCS root is found, the walk continues to the filesystem root.Explicit path (
stop_at="/some/path") â the walk stops as soon as it leaves the given directory.No boundary (
stop_at=None) â the walk continues all the way to the filesystem root.
from click import command
from click_extra import config_option
@command
@config_option(search_parents=True, stop_at="/home/user/projects")
def cli():
pass
from click import command
from click_extra import config_option
@command
@config_option(search_parents=True, stop_at=None)
def cli():
pass
Tip
The default stop_at=VCS mirrors the behavior of tools like bump-my-version and prevents the walk from escaping the repository into unrelated parent directories.
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
Warning
URLs do not support multi-format matching. You need to provide a direct link to the configuration file, including its extension.
Glob patterns are also not supported for URLs. Unless you want to let your users download the whole internetâŠ
Typed configuration schema¶
By default, ConfigOption only feeds configuration values that match CLI options into the contextâs default_map. Any other keys in the configuration file are silently ignored. This works well when the configuration file mirrors the CLI structure, but some applications need access to additional configuration that doesnât correspond to any CLI option.
The config_schema parameter solves this by extracting the appâs configuration section, normalizing its keys, and producing a typed object available to all commands via ctx.meta["click_extra.tool_config"].
Dataclass schema¶
The most common pattern is a Python dataclass. Click Extra auto-detects dataclass types, normalizes hyphenated keys to underscores, and filters to known fields:
from dataclasses import dataclass, field
from click_extra import command, echo, group, option, pass_context
from click_extra.config import get_tool_config
@dataclass
class AppConfig:
"""Typed configuration for my-app."""
extra_categories: list[str] = field(default_factory=list)
output_format: str = "text"
@group(config_schema=AppConfig)
@option("--verbose/--no-verbose")
@pass_context
def my_app(ctx, verbose):
"""An app with typed configuration."""
config = get_tool_config(ctx)
if config is not None:
echo(f"output_format: {config.output_format}")
echo(f"extra_categories: {config.extra_categories}")
@my_app.command()
@option("--name", default="World")
def greet(name):
"""Say hello."""
echo(f"Hello, {name}!")
With a TOML configuration file:
~/.config/my-app/config.toml¶[my-app]
verbose = true
extra-categories = ["docs", "tests"]
output-format = "json"
[my-app.greet]
name = "Alice"
The CLI options (verbose, name) are fed into default_map as before. The additional keys (extra-categories, output-format) are normalized (hyphens to underscores) and passed to the AppConfig dataclass. Fields not present in the file get their dataclass defaults.
$ my-app --help
Usage: my-app [OPTIONS] COMMAND [ARGS]...
An app with typed configuration.
Options:
--verbose / --no-verbose [default: no-verbose]
--time / --no-time Measure and print elapsed execution time. [default:
no-time]
--color, --ansi / --no-color, --no-ansi
Strip out all colors and all ANSI codes from output.
[default: color]
--config CONFIG_PATH Location of the configuration file. Supports local
path with glob patterns or remote URL. [default:
~/.config/my-app/{*.toml,*.yaml,*.yml,*.json,*.json5
,*.jsonc,*.hjson,*.ini,*.xml,pyproject.toml}]
--no-config Ignore all configuration files and only use command
line parameters and environment variables.
--validate-config FILE Validate the configuration file and exit.
--show-params Show all CLI parameters, their provenance, defaults
and value, then exit.
--table-format [aligned|asciidoc|colon-grid|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|hjson|html|jira|json|json5|jsonc|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|toml|tsv|unsafehtml|vertical|xml|yaml|youtrack]
Rendering style of tables. [default: rounded-
outline]
--verbosity LEVEL Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
[default: WARNING]
-v, --verbose Increase the default WARNING verbosity by one level
for each additional repetition of the option.
[default: 0]
--version Show the version and exit.
-h, --help Show this message and exit.
Commands:
greet Say hello.
/home/runner/work/click-extra/click-extra/.venv/lib/python3.14/site-packages/click/core.py:1223: UserWarning: The parameter --verbose is used more than once. Remove its duplicate as parameters should be unique.
parser = self.make_parser(ctx)
/home/runner/work/click-extra/click-extra/.venv/lib/python3.14/site-packages/click/core.py:1829: UserWarning: The parameter --verbose is used more than once. Remove its duplicate as parameters should be unique.
rest = super().parse_args(ctx, args)
/home/runner/work/click-extra/click-extra/.venv/lib/python3.14/site-packages/click/core.py:1789: UserWarning: The parameter --verbose is used more than once. Remove its duplicate as parameters should be unique.
rv = super().collect_usage_pieces(ctx)
/home/runner/work/click-extra/click-extra/click_extra/colorize.py:461: UserWarning: The parameter --verbose is used more than once. Remove its duplicate as parameters should be unique.
) = self._collect_keywords(ctx)
Callable schema¶
Any callable that accepts a dict and returns an object can be used as config_schema. This supports Pydantic models, attrs classes, or custom factories:
from types import SimpleNamespace
from click_extra import echo, group, pass_context
from click_extra.config import get_tool_config, normalize_config_keys
def parse_config(raw):
"""Custom config parser that normalizes keys."""
return SimpleNamespace(**normalize_config_keys(raw))
@group(config_schema=parse_config)
@pass_context
def callable_app(ctx):
"""An app with a callable schema."""
config = get_tool_config(ctx)
if config is not None:
echo(f"value: {config.custom_value}")
@callable_app.command()
def run():
"""Run the app."""
echo("done")
$ callable-app --help
Usage: callable-app [OPTIONS] COMMAND [ARGS]...
An app with a callable schema.
Options:
--time / --no-time Measure and print elapsed execution time. [default:
no-time]
--color, --ansi / --no-color, --no-ansi
Strip out all colors and all ANSI codes from output.
[default: color]
--config CONFIG_PATH Location of the configuration file. Supports local
path with glob patterns or remote URL. [default:
~/.config/callable-app/{*.toml,*.yaml,*.yml,*.json,*.j
son5,*.jsonc,*.hjson,*.ini,*.xml,pyproject.toml}]
--no-config Ignore all configuration files and only use command
line parameters and environment variables.
--validate-config FILE Validate the configuration file and exit.
--show-params Show all CLI parameters, their provenance, defaults
and value, then exit.
--table-format [aligned|asciidoc|colon-grid|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|hjson|html|jira|json|json5|jsonc|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|toml|tsv|unsafehtml|vertical|xml|yaml|youtrack]
Rendering style of tables. [default: rounded-outline]
--verbosity LEVEL Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
[default: WARNING]
-v, --verbose Increase the default WARNING verbosity by one level
for each additional repetition of the option.
[default: 0]
--version Show the version and exit.
-h, --help Show this message and exit.
Commands:
run Run the app.
Retrieving the config object¶
The typed configuration is stored in ctx.meta["click_extra.tool_config"] and can be accessed in two ways:
# Via the convenience helper (uses current context by default):
from click_extra.config import get_tool_config
config = get_tool_config()
# Or directly from the context:
config = ctx.find_root().meta.get("click_extra.tool_config")
If no configuration file was found or no config_schema was set, get_tool_config() returns None.
Format-agnostic¶
The config_schema feature works with all configuration formats supported by ConfigOption â TOML, YAML, JSON, JSON5, JSONC, Hjson, INI, and XML. The parsed configuration is normalized into a Python dict before the schema is applied, so the same schema works regardless of the source format.
For example, the same AppConfig dataclass works with YAML:
~/.config/my-app/config.yaml¶my-app:
extra-categories:
- docs
- tests
output-format: json
Or JSON:
~/.config/my-app/config.json¶{
"my-app": {
"extra-categories": ["docs", "tests"],
"output-format": "json"
}
}
Key normalization¶
Configuration formats commonly use kebab-case (extra-categories), while Python identifiers use snake_case (extra_categories). The normalize_config_keys utility handles this conversion recursively:
from click_extra.config import normalize_config_keys
raw = {"extra-categories": ["a", "b"], "nested-section": {"sub-key": 1}}
normalized = normalize_config_keys(raw)
# {"extra_categories": ["a", "b"], "nested_section": {"sub_key": 1}}
For dataclass schemas, this normalization is applied automatically. For callable schemas, call normalize_config_keys explicitly if needed.
Fallback sections¶
When a CLI tool is renamed, existing configuration files may still use the old section name. The fallback_sections parameter lets you accept legacy names with a deprecation warning:
from dataclasses import dataclass
from click_extra import echo, group, pass_context
from click_extra.config import get_tool_config
@dataclass
class ToolConfig:
value: str = "default"
@group(
config_schema=ToolConfig,
fallback_sections=("old-tool-name", "even-older-name"),
)
@pass_context
def new_tool(ctx):
"""A tool that was renamed."""
config = get_tool_config(ctx)
if config is not None:
echo(f"value: {config.value}")
@new_tool.command()
def run():
"""Run the tool."""
echo("done")
With the following TOML:
[old-tool-name]
value = "from-legacy"
The CLI loads the [old-tool-name] section and logs a deprecation warning to stderr:
Config section [old-tool-name] is deprecated, migrate to [new-tool].
If both [new-tool] and [old-tool-name] exist, the current name always wins, and a warning is emitted about the leftover legacy section.
$ new-tool --help
Usage: new-tool [OPTIONS] COMMAND [ARGS]...
A tool that was renamed.
Options:
--time / --no-time Measure and print elapsed execution time. [default:
no-time]
--color, --ansi / --no-color, --no-ansi
Strip out all colors and all ANSI codes from output.
[default: color]
--config CONFIG_PATH Location of the configuration file. Supports local
path with glob patterns or remote URL. [default:
~/.config/new-tool/{*.toml,*.yaml,*.yml,*.json,*.json5
,*.jsonc,*.hjson,*.ini,*.xml,pyproject.toml}]
--no-config Ignore all configuration files and only use command
line parameters and environment variables.
--validate-config FILE Validate the configuration file and exit.
--show-params Show all CLI parameters, their provenance, defaults
and value, then exit.
--table-format [aligned|asciidoc|colon-grid|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|hjson|html|jira|json|json5|jsonc|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|toml|tsv|unsafehtml|vertical|xml|yaml|youtrack]
Rendering style of tables. [default: rounded-outline]
--verbosity LEVEL Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
[default: WARNING]
-v, --verbose Increase the default WARNING verbosity by one level
for each additional repetition of the option.
[default: 0]
--version Show the version and exit.
-h, --help Show this message and exit.
Commands:
run Run the tool.
This works identically across all configuration formats (TOML, YAML, JSON, INI, etc.), since the section lookup operates on the normalized dict structure after parsing.
click_extra.config API¶
classDiagram
Enum <|-- ConfigFormat
Enum <|-- Sentinel
ExtraOption <|-- ConfigOption
ExtraOption <|-- NoConfigOption
ExtraOption <|-- ValidateConfigOption
ParamStructure <|-- ConfigOption
Utilities to load parameters and options from a configuration file.
Hint
Why config?
That whole namespace is using the common config short-name to designate
configuration files.
Not conf, not cfg, not configuration, not settings. Just config.
A quick survey of existing practices, and poll to my friends informed me that
config is more explicit and less likely to be misunderstood.
After all, is there a chance for it to be misunderstood, in the context of a CLI, for something else? Confirm? Conference? Conflict Confuse?âŠ
So yes, config is good enough.
Todo
Add a --dump-config or --export-config option to write down the current
configuration (or a template) into a file or <stdout>.
Help message would be: you can use this option with other options or environment variables to have them set in the generated configuration.
Dotted keys in configuration files (e.g. "subcommand.option": value) are
automatically expanded into nested dicts before merging, so users can freely mix
flat dot-notation and nested structures in any supported format.
- click_extra.config.VCS_DIRS = ('.git', '.hg', '.svn', '.bzr', 'CVS', '.darcs')¶
VCS directory names used to identify version control system roots.
Includes: -
.gitâ Git -.hgâ Mercurial -.svnâ Subversion -.bzrâ Bazaar -CVSâ CVS (note: uppercase, no leading dot) -.darcsâ Darcs
- class click_extra.config.ConfigFormat(*values)[source]¶
Bases:
EnumAll configuration formats, associated to their support status.
The first element of the tuple is a sequence of file extensions associated to the format. Patterns are fed to
wcmatch.globfor matching, and are influenced by the flags set on theConfigOptioninstance.The second element indicates whether the format is supported or not, depending on the availability of the required third-party packages. This evaluation is performed at runtime when this module is imported.
Caution
The order is important for both format members and file patterns. It defines the priority order in which formats are tried when multiple candidate files are found.
- TOML = (('*.toml',), True, 'TOML')¶
- YAML = (('*.yaml', '*.yml'), True, 'YAML')¶
- JSON = (('*.json',), True, 'JSON')¶
- JSON5 = (('*.json5',), True, 'JSON5')¶
- JSONC = (('*.jsonc',), True, 'JSONC')¶
- HJSON = (('*.hjson',), True, 'Hjson')¶
- INI = (('*.ini',), True, 'INI')¶
- XML = (('*.xml',), True, 'XML')¶
- PYPROJECT_TOML = (('pyproject.toml',), True, 'pyproject.toml')¶
- click_extra.config.CONFIG_OPTION_NAME = 'config'¶
Hardcoded name of the configuration option.
This name is going to be shared by both the
--configand--no-configoptions below, so they can compete with each other to either set a path pattern or disable the use of any configuration file at all.
- click_extra.config.DEFAULT_EXCLUDED_PARAMS = ('config', 'help', 'show_params', 'version')¶
Default parameter IDs to exclude from the configuration file.
Defaults to:
--configoption, which cannot be used to recursively load another configuration file.--help, as it makes no sense to have the configurable file always forces a CLI to show the help and exit.--show-paramsflag, which is like--helpand stops the CLI execution.--version, which is not a configurable option per-se.
- click_extra.config.DEFAULT_SUBCOMMANDS_KEY = '_default_subcommands'¶
Reserved configuration key for specifying default subcommands.
When a group is invoked without explicit subcommands on the CLI, the subcommands listed under this key execute automatically in order. CLI always wins: if the user names subcommands explicitly, the config is ignored.
Example TOML configuration:
[my-cli] _default_subcommands = ["backup", "sync"] [my-cli.backup] path = "/home"
- click_extra.config.PREPEND_SUBCOMMANDS_KEY = '_prepend_subcommands'¶
Reserved configuration key for prepending subcommands to every invocation.
Unlike
_default_subcommandswhich only fires when no subcommands are given on the CLI,_prepend_subcommandsalways prepends the listed subcommands. This is useful for always injecting adebugsubcommand on a dev machine, for example.Only works with
chain=Truegroups (non-chained groups resolve exactly one subcommand, so prepending would break the userâs intended command).Example TOML configuration:
[my-cli] _prepend_subcommands = ["debug"]
- click_extra.config.normalize_config_keys(conf)[source]¶
Normalize configuration keys to valid Python identifiers.
Recursively replaces hyphens with underscores in all dict keys, using the same
str.replace("-", "_")transform that Click applies internally when deriving parameter names from option declarations (e.g.--foo-barbecomesfoo_bar). Click does not expose this as a public function, so we replicate the one-liner here.Handles the convention mismatch between configuration formats (TOML, YAML, JSON all commonly use kebab-case) and Python identifiers. Works with all configuration formats supported by
ConfigOption.Todo
Propose upstream to Click to extract the inline
name.replace("-", "_")into a private_normalize_param_namehelper, so downstream projects like Click Extra can reuse it instead of duplicating the transform.
- click_extra.config.get_tool_config(ctx=None)[source]¶
Retrieve the typed tool configuration from the context.
Returns the object stored in
ctx.meta["click_extra.tool_config"]byConfigOptionwhen aconfig_schemais set, orNoneif no schema was configured or no configuration was loaded.
- class click_extra.config.Sentinel(*values)[source]¶
Bases:
EnumEnum used to define sentinel values.
Note
This reuse the same pattern as
Click._utils.Sentinel.See also
- NO_CONFIG = <object object>¶
- VCS = <object object>¶
- click_extra.config.NO_CONFIG = Sentinel.NO_CONFIG¶
Sentinel used to indicate that no configuration file must be used at all.
- click_extra.config.VCS = Sentinel.VCS¶
Sentinel used to stop parent directory walking at the nearest VCS root.
- class click_extra.config.ConfigOption(param_decls=None, metavar='CONFIG_PATH', type=UNPROCESSED, help='Location of the configuration file. Supports local path with glob patterns or remote URL.', is_eager=True, expose_value=False, file_format_patterns=None, file_pattern_flags=4104, roaming=True, force_posix=False, search_pattern_flags=285504, search_parents=False, stop_at=Sentinel.VCS, excluded_params=None, included_params=None, strict=False, config_schema=None, fallback_sections=(), **kwargs)[source]¶
Bases:
ExtraOption,ParamStructureA pre-configured option adding
--config CONFIG_PATH.Takes as input a path to a file or folder, a glob pattern, or an URL.
is_eageris active by default so thecallbackgets the opportunity to set thedefault_mapof the CLI before any other parameter is processed.defaultis set to the value returned byself.default_pattern(), which is a pattern combining the default configuration folder for the CLI (as returned byclick.get_app_dir()) and all supported file formats.Attention
Default search pattern must follow the syntax of wcmatch.glob.
excluded_paramsare parameters which, if present in the configuration file, will be ignored and not applied to the CLI. Items are expected to be the fully-qualified ID of the parameter, as produced in the output of--show-params. Will default to the value ofDEFAULT_EXCLUDED_PARAMS.included_paramsis the inverse ofexcluded_params: only the listed parameters will be loaded from the configuration file. Cannot be used together withexcluded_params.
- file_format_patterns: dict[ConfigFormat, tuple[str, ...]]¶
Mapping of
ConfigFormatto their associated file patterns.Can be a string or a sequence of strings. This defines which configuration file formats are supported, and which file patterns are used to search for them.
Note
All formats depending on third-party dependencies that are not installed will be ignored.
Attention
File patterns must follow the syntax of wcmatch.fnmatch.
- file_pattern_flags¶
Flags provided to all calls of
wcmatch.fnmatch.Applies to the matching of file names against supported format patterns specified in
file_format_patterns.Important
The
SPLITflag is always forced, as our multi-pattern design relies on it.
- force_posix¶
Configuration for default folder search.
roamingandforce_posixare fed to click.get_app_dir() to determine the location of the default configuration folder.
- search_pattern_flags¶
Flags provided to all calls of
wcmatch.glob.Applies to both the default pattern and any user-provided pattern.
Important
The
BRACEflag is always forced, so that multi-format default patterns using{pat1,pat2,...}syntax expand correctly.The
NODIRflag is always forced, to optimize the search for files only.
- search_parents¶
Indicates whether to walk back the tree of parent folders when searching for configuration files.
- stop_at¶
Boundary for parent directory walking.
Noneâ walk up to filesystem root.VCSâ stop at the nearest VCS root (.gitor.hg) (default).A
Pathorstrâ stop at that directory.
- strict¶
Defines the strictness of the configuration loading.
If
True, raise an error if the configuration file contain parameters not recognized by the CLI.If
False, silently ignore unrecognized parameters.
- config_schema¶
Optional schema for structured access to configuration values.
When set, the appâs configuration section is extracted from the parsed config file, normalized (hyphens replaced with underscores), and passed to this callable to produce a typed configuration object.
Supports:
Dataclass types â detected via
__dataclass_fields__and instantiated with normalized keys filtered to known fields.Any callable
dict â Tâ called directly with the normalized dict. Works with PydanticâsModel.model_validate, attrs, or custom factory functions.
The resulting object is stored in
ctx.meta["click_extra.tool_config"]and can be retrieved via get_tool_config.
- fallback_sections: Sequence[str]¶
Legacy section names to try when the appâs own section is empty.
Useful when a CLI tool has been renamed: old configuration files that still use
[tool.old-name](TOML),old-name:(YAML), or{"old-name": âŠ}(JSON) are recognized with a deprecation warning. Works with all configuration formats.
- property excluded_params: frozenset[str][source]¶
Generates the default list of fully-qualified IDs to exclude.
Danger
It is only called once to produce the default exclusion list if the user did not provided its own.
It was not implemented in the constructor but made as a property, to allow for a just-in-time call within the current context. Without this trick we could not have fetched the CLI name.
- property file_pattern: str[source]¶
Compile all file patterns from the supported formats.
Uses
,(comma) notation to combine multiple patterns, suitable forwcmatchbrace expansion ({pat1,pat2,...}).Returns a single pattern string.
- default_pattern()[source]¶
Returns the default pattern used to search for the configuration file.
Defaults to
<app_dir>/{*.toml,*.json,*.ini}. Where<app_dir>is produced by the click.get_app_dir() method. The result depends on OS and is influenced by theroamingandforce_posixproperties.Multiple file format patterns are wrapped with
{âŠ}brace-expansion syntax so thatwcmatch.globcorrectly applies the directory prefix to every sub-pattern.Todo
Use platformdirs for more advanced configuration folder detection?
- Return type:
- get_help_extra(ctx)[source]¶
Replaces the default value of the configuration option.
Display a pretty path that is relative to the userâs home directory:
~/folder/my_cli/{*.toml,*.json,*.ini}Instead of the full absolute path:
/home/user/folder/my_cli/{*.toml,*.json,*.ini}Caution
This only applies when the
GLOBTILDEflag is set insearch_pattern_flags.- Return type:
OptionHelpExtra
- parent_patterns(pattern)[source]¶
Generate
(root_dir, file_pattern)pairs for searching.Each yielded pair can be passed directly to
glob.iglob(file_pattern, root_dir=root_dir)so that every sub-pattern (whether fromBRACEorSPLITexpansion) is correctly scoped to the same directory.root_dirisNonefor entirely magic patterns that will be evaluated relative to the current working directory.Stops when reaching the root folder, the
stop_atboundary, or an inaccessible directory.
- search_and_read_file(pattern)[source]¶
Search filesystem or URL for files matching the
pattern.If
patternis an URL, download its content. A pattern is considered an URL only if it validates as one and starts withhttp://orhttps://. All other patterns are considered glob patterns for local filesystem search.Returns an iterator of the normalized location and its raw content, for each one matching the pattern. Only files are returned, directories are silently skipped.
This method returns the raw content of all matching patterns, without trying to parse them. If the content is empty, it is still returned as-is.
Also includes lookups into parents directories if
self.search_parentsisTrue.Raises
FileNotFoundErrorif no file was found after searching all locations.
- parse_conf(content, formats)[source]¶
Parse the
contentwith the givenformats.Tries to parse the given raw
contentstring with each of the givenformats, in order. Yields the resulting data structure for each successful parse.Attention
Formats whose parsing raises an exception or does not return a
dictare considered a failure and are skipped.This follows the parse, donât validate principle.
- read_and_parse_conf(pattern)[source]¶
Search for a parseable configuration file.
Returns the location and data structure of the first configuration matching the
pattern.Only return the first match that:
exists,
is a file,
is not empty,
match file format patterns,
can be parsed successfully, and
produce a non-empty data structure.
Raises
FileNotFoundErrorif no configuration file was found matching the criteria above.Returns
(None, None)if files were found but none could be parsed.
- 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 ofINIformat 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.Uses a ~collections.ChainMap so each config source keeps its own layer. The first layer wins on key lookup, which makes parameter-source precedence explicit and future-proofs for multi-file config loading.
- Return type:
- load_conf(ctx, param, path_pattern)[source]¶
Fetch parameter values from a configuration file and set 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. Direct CLI parameters, environment variables or interactive prompts take precedence over any values from the config file.- Return type:
- ..hint::
Once loading is complete, the resolved file path and its full parsed content are stored in
ctx.meta["click_extra.conf_source"]andctx.meta["click_extra.conf_full"]respectively. This is the recommended way to identify which configuration file was loaded.We intentionally do not add a custom
ParameterSource.CONFIG_FILEenum member:ParameterSourceis a closed enum in Click, and monkeypatching it would be fragile. Besides, config values end up indefault_map, so Click already reports them asParameterSource.DEFAULT_MAP, which is accurate.
- class click_extra.config.NoConfigOption(param_decls=None, type=UNPROCESSED, help='Ignore all configuration files and only use command line parameters and environment variables.', is_flag=True, flag_value=Sentinel.NO_CONFIG, is_eager=True, expose_value=False, **kwargs)[source]¶
Bases:
ExtraOptionA pre-configured option adding
--no-config.This option is supposed to be used alongside the
--configoption (ConfigOption) to allow users to explicitly disable the use of any configuration file.This is especially useful to debug side-effects caused by autodetection of configuration files.
See also
An alternative implementation of this class would be to create a custom click.ParamType instead of a custom
Optionsubclass. Here is for example.
- class click_extra.config.ValidateConfigOption(param_decls=None, type=<click.types.Path object>, is_eager=True, expose_value=False, help='Validate the configuration file and exit.', **kwargs)[source]¶
Bases:
ExtraOptionA pre-configured option adding
--validate-config CONFIG_PATH.Loads the config file at the given path, validates it against the CLIâs parameter structure in strict mode, reports results, and exits.