click_extra.config package

Configuration file loading, format parsing, and schema validation.

This package gathers the layers behind --config, --no-config, and --validate-config:

  • formats: the supported file formats and the generic, stateless helpers to read, serialize, auto-detect, and resolve them.

  • schema: the generic schema-building and validation engine, applicable to any dataclass.

  • builtin: click-extra’s own configuration schema and the validators it registers by default (one concrete application of the engine).

  • option: the --config / --no-config / --validate-config option classes.

Every public symbol is re-exported here so consumers can keep importing from click_extra.config.

class click_extra.config.ClickExtraConfig(test_suite=<factory>, prebake=<factory>)[source]

Bases: object

Schema for the [tool.click-extra] configuration section.

Wired as the config_schema of the top-level click-extra group, so every subcommand reads the same section and pulls its own sub-table through get_tool_config().

test_suite: TestSuiteConfig

The [tool.click-extra.test-suite] sub-table (file/cases/timeout).

prebake: PrebakeConfig

The [tool.click-extra.prebake] sub-table (target module).

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, schema_strict=False, fallback_sections=(), config_validators=(), **kwargs)[source]

Bases: ExtraOption, ParamStructure

A pre-configured option adding --config CONFIG_PATH.

Takes as input a path to a file or folder, a glob pattern, or an URL.

  • is_eager is active by default so the callback gets the opportunity to set the default_map of the CLI before any other parameter is processed.

  • default is set to the value returned by self.default_pattern(), which is a pattern combining the default configuration folder for the CLI (as returned by click.get_app_dir()) and all supported file formats.

    Attention

    Default search pattern must follow the syntax of wcmatch.glob.

  • excluded_params are 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 of DEFAULT_EXCLUDED_PARAMS.

  • included_params is the inverse of excluded_params: only the listed parameters will be loaded from the configuration file. Cannot be used together with excluded_params.

file_format_patterns: dict[ConfigFormat, tuple[str, ...]]

Mapping of ConfigFormat to 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 SPLIT flag is always forced, as our multi-pattern design relies on it.

force_posix

Configuration for default folder search.

roaming and force_posix are 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 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.

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 (.git or .hg) (default).

  • A Path or str: stop at that directory.

included_params: frozenset[str] | None

Allowlist of parameter IDs, mutually exclusive with excluded_params.

None disables the allowlist. It is resolved into excluded_params by build_param_trees(), once every parameter ID is known.

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), flattened (nested dicts joined with _), and passed to this callable to produce a typed configuration object.

Supports:

  • Dataclass types: detected via __dataclass_fields__. Keys are normalized, nested dicts are flattened, and the result is filtered to known fields before instantiation. This allows nested config sections (like [tool.myapp.sub-section]) to map directly to flat dataclass fields (like sub_section_key).

  • Any callable dict T: called directly with the raw dict. Works with Pydantic’s Model.model_validate, attrs, or custom factory functions. The caller is responsible for key normalization and flattening.

The resulting object is stored in ctx.meta[click_extra.context.TOOL_CONFIG] and can be retrieved via get_tool_config.

schema_strict

Strictness for schema validation (separate from strict).

  • If True, raise ValueError when the config section contains keys that do not match any dataclass field (after normalization and flattening). Only applies when config_schema is a dataclass.

  • If False, silently ignore unrecognized keys.

Note

This is distinct from strict, which controls whether merge_default_map rejects config keys not matching CLI parameters. schema_strict validates against dataclass fields instead.

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.

config_validators: tuple[ConfigValidator, ...]

Extension validators for sub-trees of the configuration file.

Each ConfigValidator targets a dotted extension_path relative to the app section. Validators run after click-extra’s built-in CLI-parameter strict check (during --validate-config) and after the schema callable produces the typed configuration object (during normal config loading).

The list is seeded with click-extra’s built-in validators (currently the one for [tool.<cli>.themes.<name>] tables, see click_extra.theme.validate_themes_config()); user-supplied validators are appended after them. App code that registers its own validator on the same extension_path simply runs alongside the built-in: both validators are called, both sets of errors surface.

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 for wcmatch brace 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 the roaming and force_posix properties.

Multiple file format patterns are wrapped with {…} brace-expansion syntax so that wcmatch.glob correctly applies the directory prefix to every sub-pattern.

Todo

Use platformdirs for more advanced configuration folder detection?

Return type:

str

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 GLOBTILDE flag is set in search_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 from BRACE or SPLIT expansion) is correctly scoped to the same directory.

root_dir is None for entirely magic patterns that will be evaluated relative to the current working directory.

Stops when reaching the root folder, the stop_at boundary, or an inaccessible directory.

Return type:

Iterable[tuple[str | None, str]]

search_and_read_file(pattern)[source]

Search filesystem or URL for files matching the pattern.

If pattern is an URL, download its content. A pattern is considered an URL only if it validates as one and starts with http:// or https://. 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_parents is True.

Raises FileNotFoundError if no file was found after searching all locations.

Return type:

Iterable[tuple[Path | URL, str]]

parse_conf(content, formats)[source]

Parse the content with the given formats.

Tries to parse the given raw content string with each of the given formats, in order. Yields the resulting data structure for each successful parse.

Attention

Formats whose parsing raises an exception or does not return a dict are considered a failure and are skipped.

This follows the parse, don’t validate principle.

Return type:

Iterable[dict[str, Any] | None]

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 FileNotFoundError if no configuration file was found matching the criteria above.

Returns (None, None) if files were found but none could be parsed.

Return type:

tuple[Path | URL, dict[str, Any]] | tuple[None, None]

load_ini_config(content)[source]

Utility method to parse INI configuration file.

Internal convention is to use a dot (., as set by PARAM_PATH_SEP) in section IDs as a separator between levels. This is a workaround the limitation of INI format which doesn’t allow for sub-sections.

Returns a ready-to-use data structure.

Return type:

dict[str, Any]

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 filters out all unrecognized options not supported by the command, then hand the result to _install_default_map().

Opaque sub-trees declared by the schema or by registered ConfigValidator instances are stripped from the conf before the CLI-parameter strict check, so user-controlled keys (like mappings whose keys are data, not flag names) don’t trip strict=True.

Note

This recomputes the filtered config that run_config_validation() already produces as merged_conf. load_conf() installs that result directly and skips this method; it stays as the standalone entry point for external callers.

Return type:

None

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.

Hint

Once loading is complete, the resolved file path and its full parsed content are stored in ctx.meta[click_extra.context.CONF_SOURCE] and ctx.meta[click_extra.context.CONF_FULL] respectively. This is the recommended way to identify which configuration file was loaded.

We intentionally do not add a custom ParameterSource.CONFIG_FILE enum member: ParameterSource is a closed enum in Click, and monkeypatching it would be fragile. Besides, config values end up in default_map, so Click already reports them as ParameterSource.DEFAULT_MAP, which is accurate.

Return type:

None

class click_extra.config.ExportConfigOption(param_decls=None, type=None, metavar='FORMAT', is_eager=True, expose_value=False, help='Export the configuration in the selected format to <stdout>, then exit.', **kwargs)[source]

Bases: ExtraOption

A pre-configured option adding --export-config FORMAT.

Resolves the CLI’s current parameter values following Click’s precedence chain (command line, then environment variables, then configuration file, then defaults), renders them as a configuration file in the requested format on <stdout>, and exits.

Hint

Combine the flag with other options or environment variables to capture them in the generated configuration. For example, mycli --verbosity DEBUG --export-config toml emits a configuration whose verbosity is already set to DEBUG.

Like ValidateConfigOption, it relies on a sibling ConfigOption to provide the parameter structure and the excluded_params / included_params filter, so the export contains exactly the parameters that can be loaded back from a configuration file.

Note

The accepted formats are those serialize_content() can write (SERIALIZABLE_FORMATS). INI and pyproject.toml have no serializer and cannot be dumped.

build_config(ctx, config_option)[source]

Resolve every config-eligible parameter into a dumpable tree.

Walks the sibling ConfigOption’s parameter structure, resolves each parameter’s effective value by replaying RAW_ARGS (falling back to defaults when the command did not capture them), drops the excluded_params, and layers the coerced values into the {cli-name: {param: value, ...}} shape a configuration file uses. Blank values are removed, mirroring the clean-up ConfigOption._install_default_map() applies on load.

Return type:

dict[str, Any]

export_config(ctx, param, value)[source]

Render the resolved configuration to <stdout> and exit.

Return type:

None

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: ExtraOption

A pre-configured option adding --no-config.

This option is supposed to be used alongside the --config option (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.

flag_value=NO_CONFIG is the Sentinel enum member that signals “skip configuration loading” to ConfigOption. Click 8.4.0 (PR pallets/click#3363) auto-detects type=UNPROCESSED for non-basic flag_value types, but click-extra still supports Click 8.3.x where that auto-detection is absent, so the type=UNPROCESSED override is kept explicit to let the sentinel pass through Option unchanged on every supported Click.

See also

An alternative implementation of this class would be to create a custom click.ParamType instead of a custom Option subclass. Here is for example.

check_sibling_config_option(ctx, param, value)[source]

Ensure that this option is used alongside a ConfigOption instance.

Return type:

None

class click_extra.config.PrebakeConfig(module=None)[source]

Bases: object

Config schema for the prebake commands, read from [tool.<cli>.prebake].

Lets a project pin the target __init__.py once for its build pipeline, instead of passing --module to every click-extra prebake command.

module: str | None = None

Path to the __init__.py to pre-bake, resolved relative to the project root. Overrides the [project.scripts] auto-discovery; leave unset to keep it.

class click_extra.config.SchemaFieldInfo(key: str, type_hint: str, default: Any, summary: str, description: str)[source]

Bases: NamedTuple

Documentation record for one option of a configuration schema.

Produced by schema_field_infos(). Consumed by the click:config Sphinx directive, and by CLIs building their own configuration reference (a show-config table, say) from the same introspection.

Create new instance of SchemaFieldInfo(key, type_hint, default, summary, description)

key: str

Dotted configuration path of the option.

Field names are kebab-cased (setup_guidesetup-guide) unless the field pins an explicit path through click_extra.config.schema.CONFIG_PATH_METADATA_KEY. Nested dataclass fields contribute one segment per nesting level (test-suite.timeout).

type_hint: str

The field’s type annotation, as written in the schema source.

default: Any

The field’s default value, taken from a pristine schema instance.

summary: str

First paragraph of the field’s attribute docstring, collapsed onto a single line. Empty when the field has no docstring or the class source is unavailable (see field_docstrings()).

description: str

Full attribute docstring of the field, paragraph breaks preserved.

class click_extra.config.TestSuiteConfig(file='./tests/cli-test-suite.toml', cases=<factory>, timeout=None)[source]

Bases: object

Config schema for a project’s test suite, read from [tool.<cli>.test-suite].

The test-suite CLI command resolves its cases from this config when no suite is given on the command line. Map it onto an app’s config section with a field carrying metadata={CONFIG_PATH_METADATA_KEY: "test-suite"}.

file: str = './tests/cli-test-suite.toml'

Path to a test suite file, resolved relative to the project root.

Its format is detected from the extension; the default is TOML, which (like JSON) parses with no optional dependency, unlike YAML and the others.

cases: list[dict]

Test cases written natively in the config format, an alternative to a file suite, taking precedence over it when both are set.

Each entry is a mapping of CLITestCase directive names, equivalent to one item of a suite list. In TOML this reads as a [[tool.<cli>.test-suite.cases]] array of tables. Declared as an extension point so the configuration engine passes the raw mappings through unprocessed; the test-suite command turns them into CLITestCase instances.

timeout: int | None = None

Default timeout (seconds) for each case that does not set its own.

None leaves cases unbounded unless --timeout is passed.

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: ExtraOption

A 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.

validate_config(ctx, param, value)[source]

Load, parse, and validate the configuration file, then exit.

Validation runs three checks in order, every one of them under the same ValidationError shape so the reported path is always rooted at the configuration file:

  1. CLI-parameter strict check on the non-opaque part of the document.

  2. Schema processing, if a config_schema is configured: catches type errors and unknown keys inside the dataclass-described section.

  3. Each registered ConfigValidator runs against its declared opaque sub-tree.

Every detected error is emitted before exiting, so a single --validate-config run surfaces the full list of fixes the user needs to apply.

Return type:

None

exception click_extra.config.ValidationError(path, message, code=None)[source]

Bases: Exception

Raised when a configuration file fails validation.

A single, structured exception type that uniformly carries the dotted path of the offending key, a human-readable message, and an optional code for programmatic handling. Used by click-extra’s built-in strict-mode check and by every user-registered ConfigValidator, so downstream apps and --validate-config see the same error shape regardless of who detected the problem.

Parameters:
  • path (str) – Dotted path to the offending key, relative to the configuration file root (like "my-cli.managers.winget.cli_searchpath"). An empty string means the error applies to the document as a whole.

  • message (str) – Human-readable description of the failure. Should be a single sentence, no trailing punctuation, no path repeated.

  • code (str | None) – Optional machine-readable error code (like "unknown_field") for callers that want to dispatch on error type without parsing the message string.

class click_extra.config.ValidationReport(schema_instance, opaque_subtrees, errors, merged_conf=None)[source]

Bases: object

Outcome of one pass through run_config_validation().

Bundles everything a caller needs after validating a parsed configuration document: the typed schema instance, the extracted opaque sub-trees, the template-filtered config ready for default_map, and every error detected across all validation stages.

Note

The report holds references to the parsed sub-trees, not copies, so building it is cheap regardless of document size.

schema_instance: Any | None

Typed object produced by the configured schema callable.

None when no schema is configured, or when the schema stage raised (in which case the failure is recorded in errors).

opaque_subtrees: dict[str, dict[str, Any]]

Extracted extension sub-trees, keyed by dotted path relative to the app section. Only paths actually present in the document appear here, so callers can re-route them to per-path validators or stash them on ctx.meta.

errors: tuple[ValidationError, ...]

Every ValidationError detected, in stage order (unknown CLI-flag keys first, then schema errors, then validator failures). Empty on success.

With collect_all=False this holds at most one error: the first failure short-circuits the remaining stages.

merged_conf: dict[str, Any] | None = None

The CLI-flag-bound configuration merged onto params_template: the payload _install_default_map() layers into the context’s default_map.

None when params_template was None (no strict check) or the strict check raised. Read it only on a successful report: it is the same value merge_default_map() would recompute, so reusing it avoids a second normalize/strip/merge pass.

property ok: bool

True when no error was detected.

click_extra.config.field_docstrings(cls)[source]

Extract attribute docstrings from a class body, keyed by field name.

Attribute docstrings are string literals immediately following an annotated assignment in a class body (the PEP 257 convention used by Sphinx’s autodoc). Python discards them at runtime, so they are recovered by parsing the class source with ast. Each docstring is cleaned up with inspect.cleandoc(), preserving paragraph breaks.

Caution

Returns an empty mapping when the class source is unavailable, as for classes defined in an exec-ed code block (an interactive session, or the body of a click:source Sphinx directive). Import the schema from a real module to get its docstrings documented.

Return type:

dict[str, str]

click_extra.config.flatten_config_keys(conf, sep='_', opaque_keys=frozenset({}), _prefix='')[source]

Flatten nested dicts into a single level by joining keys with a separator.

Useful for mapping nested configuration structures (like TOML sub-tables) to flat Python dataclass fields. After normalization with normalize_config_keys, the flattened keys match dataclass field names directly:

>>> from click_extra.config import (
...     flatten_config_keys,
...     normalize_config_keys,
... )
>>> raw = {"dependency-graph": {"all-groups": True, "output": "deps.mmd"}}
>>> flatten_config_keys(normalize_config_keys(raw))
{'dependency_graph_all_groups': True, 'dependency_graph_output': 'deps.mmd'}
Parameters:
  • conf (dict[str, Any]) – Nested dictionary to flatten.

  • sep (str) – Separator used to join parent and child keys. Defaults to "_" which produces valid Python identifiers when combined with normalize_config_keys.

  • opaque_keys (frozenset[str]) – Fully-qualified key names where flattening stops. When the accumulated key matches an entry in this set, the dict value is kept as-is instead of being recursively flattened. This is useful for fields typed as dict[str, X] where the dict keys are data (like GitHub Actions matrix axis names), not config structure.

  • _prefix (str) – Internal parameter for tracking the accumulated key path during recursion. Callers should not set this.

Return type:

dict[str, Any]

click_extra.config.format_from_path(path, formats=None)[source]

Return the configuration format whose patterns match a file name.

The name is matched against each format’s patterns, so app.toml resolves to TOML and app.yml to YAML. formats restricts and orders the candidates (the first match wins); it defaults to every ConfigFormat.

Return type:

ConfigFormat | None

click_extra.config.get_tool_config(ctx=None)[source]

Retrieve the typed tool configuration from the context.

Returns the object stored under click_extra.context.TOOL_CONFIG by ConfigOption when a config_schema is set, or None if no schema was configured or no configuration was loaded.

Parameters:

ctx (Context | None) – Click context. Defaults to the current context.

Return type:

Any

click_extra.config.make_schema_callable(schema, *, strict=False, normalize=True)[source]

Wrap a schema type into a callable that accepts a raw config dict.

  • Dataclass types (detected via dataclasses.is_dataclass) are auto-wrapped: keys are normalized (hyphens to underscores), nested dicts are flattened, and the result is filtered to known fields before instantiation. Three schema-aware features refine this process:

    1. Type-aware flattening. Fields typed as dict[str, X] are treated as opaque: flatten_config_keys stops at their boundary so the dict value is kept intact.

    2. Field metadata. Dataclass fields may carry click_extra.config_path (a dotted TOML path like "test-matrix.replace") and click_extra.normalize_keys (False to skip key normalization on the extracted value). Fields with an explicit path are extracted from the raw config before normalization and flattening.

    3. Nested dataclass support. Fields whose resolved type is itself a dataclass are recursively processed with the same logic.

  • Any other callable is returned as-is. The caller is responsible for key normalization if needed.

  • None returns None.

Parameters:
  • strict (bool) – If True, raise ValueError when the config contains keys that do not match any dataclass field (after normalization and flattening).

  • normalize (bool) – If False, skip normalize_config_keys on the remaining config dict. Used internally when recursing into nested dataclasses whose parent opted out of normalization via click_extra.normalize_keys = False.

Return type:

Callable[[dict[str, Any]], Any] | None

click_extra.config.normalize_config_keys(conf, opaque_keys=frozenset({}), _prefix='')[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 (--foo-bar becomes foo_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.

Parameters:
  • opaque_keys (frozenset[str]) – Fully-qualified key names (using "_" as separator) where recursion stops. The key itself is still normalized, but its dict value is kept as-is. Used in tandem with flatten_config_keys’s opaque_keys to protect data dicts (like GitHub Actions matrix axes) from normalization.

  • _prefix (str) – Internal parameter for tracking the accumulated key path during recursion. Callers should not set this.

Todo

Propose upstream to Click to extract the inline name.replace("-", "_") into a private _normalize_param_name helper, so downstream projects like Click Extra can reuse it instead of duplicating the transform.

Return type:

dict[str, Any]

click_extra.config.parse_content(fmt, content)[source]

Parse content with a single stateless format.

INI is excluded: it needs the CLI parameter structure for type coercion and is handled by ConfigOption.load_ini_config.

Note

Optional third-party parsers are imported lazily, at the point of use, rather than at module load. Only enabled formats reach this function (disabled ones are filtered out of ConfigOption.file_format_patterns), so the import always resolves for the formats actually parsed here.

Return type:

Any

click_extra.config.read_file(path, formats=None)[source]

Read a file and parse it, picking the format from its name.

The format is resolved with format_from_path() over formats (every ConfigFormat by default), then the content is parsed with parse_content().

Raises:
  • ValueError – the file name matches none of the candidate formats.

  • ImportError – the matched format’s optional parser is not installed.

Return type:

Any

click_extra.config.run_config_validation(user_conf, *, app_name, params_template, config_schema=None, config_validators=(), fallback_sections=(), schema_strict=False, strict=False, collect_all=True)[source]

Validate a parsed configuration document in one schema-driven pass.

This is the module-level entry point that unifies click-extra’s three historical validation paths (CLI-parameter strict check, dataclass schema, and app-registered ConfigValidator hooks) behind a single function yielding a single error type. It is deliberately not named validate_config: that name belongs to validate_config(), the callback powering the --validate-config flag.

Stages, in order:

  1. Normalize. Strip reserved keys and expand dotted keys.

  2. Partition. Split opaque sub-trees (schema extension fields plus every registered validator’s extension_path) from the CLI-flag-bound content. Extracted sub-trees land in ValidationReport.opaque_subtrees.

  3. Strict-check the CLI-flag-bound part against params_template, keeping the merged result as ValidationReport.merged_conf (skipped when params_template is None).

  4. Schema-build the app section through the configured callable, producing ValidationReport.schema_instance.

  5. Validate every opaque sub-tree through its registered validator.

Parameters:
  • user_conf (dict[str, Any]) – The full parsed configuration document.

  • app_name (str) – Name of the app’s section (used to resolve the section and to root opaque paths and error paths at the document level).

  • params_template (dict[str, Any] | None) – The CLI-parameter template the strict check runs against. Pass None to skip the strict check entirely (for example, for a schema-only validation).

  • config_schema (type | Callable[[dict[str, Any]], Any] | None) – Dataclass type or callable describing the typed configuration, or None.

  • config_validators (Sequence[ConfigValidator]) – Extension validators to run against opaque sub-trees.

  • fallback_sections (Sequence[str]) – Legacy section names to try when app_name is absent or empty.

  • schema_strict (bool) – Reject keys the dataclass schema does not recognize.

  • strict (bool) – Reject keys the CLI-parameter template does not recognize.

  • collect_all (bool) – When True (default), run every stage and collect all errors. When False, the first error short-circuits the rest.

Return type:

ValidationReport

Returns:

A ValidationReport. ValidationError is the single error type recorded by every stage; ValueError / TypeError raised by the strict check or schema callable are wrapped into it.

click_extra.config.schema_field_infos(schema)[source]

Walk a configuration schema dataclass into per-option records.

Introspects the dataclass fields, their type annotations, defaults, and attribute docstrings. Nested dataclass fields expand recursively into dotted keys (test-suite.timeout), honoring click_extra.config.schema.CONFIG_PATH_METADATA_KEY at every level. Records are sorted by key, segment-wise, so a sub-table’s options stay contiguous even when another table’s name shares their prefix (workflow.sync sorts before workflow-pins.sync).

Defaults are read off a pristine schema() instance, so every field must carry a default: configuration schemas are default-complete by construction, since make_schema_callable() instantiates them from partial user data.

Raises:

TypeError – when schema is not a dataclass type.

Return type:

list[SchemaFieldInfo]

click_extra.config.serialize_content(fmt, data, **kwargs)[source]

Serialize a Python object to a string in the given format.

The dumping counterpart to parse_content(). Per-format defaults can be overridden through kwargs (forwarded to the underlying serializer). JSON5 and JSONC are emitted as plain JSON, a valid subset of both.

Caution

Not every format round-trips: TOML and XML have no null type, and XML expects a single root mapping, so the caller is responsible for shaping data accordingly. INI and pyproject.toml have no serializer here.

Note

Optional third-party serializers are imported lazily, at the point of use. Writing TOML uses tomlkit (the [toml] extra), unlike reading which relies on the built-in tomllib.

Raises:

ValueError – the format has no serializer.

Return type:

str

Submodules

click_extra.config.builtin module

Click Extra’s own built-in configuration schema and validators.

These are concrete instances of the configuration machinery, kept apart from the reusable engine in schema and the option classes in option:

  • the dataclasses describing click-extra’s own [tool.click-extra] section (ClickExtraConfig and its test-suite and prebake sub-tables);

  • _builtin_config_validators(), the validators click-extra registers on every ConfigOption.

A downstream project defines its own equivalent of this module; click-extra just happens to ship one, built on the same generic engine.

click_extra.config.builtin.THEMES_CONFIG_KEY: str = 'themes'

Sub-key under [tool.<cli>] where user-defined themes live in config.

Used by ConfigOption to find [tool.<cli>.themes.<name>] tables, build them via HelpTheme.from_dict, and stash the result on ctx.meta[click_extra.context.THEME_OVERRIDES]. The constant is the single source of truth shared by _builtin_config_validators, ConfigOption._apply_theme_overrides, and click_extra.theme.themes_from_config().

class click_extra.config.builtin.TestSuiteConfig(file='./tests/cli-test-suite.toml', cases=<factory>, timeout=None)[source]

Bases: object

Config schema for a project’s test suite, read from [tool.<cli>.test-suite].

The test-suite CLI command resolves its cases from this config when no suite is given on the command line. Map it onto an app’s config section with a field carrying metadata={CONFIG_PATH_METADATA_KEY: "test-suite"}.

file: str = './tests/cli-test-suite.toml'

Path to a test suite file, resolved relative to the project root.

Its format is detected from the extension; the default is TOML, which (like JSON) parses with no optional dependency, unlike YAML and the others.

cases: list[dict]

Test cases written natively in the config format, an alternative to a file suite, taking precedence over it when both are set.

Each entry is a mapping of CLITestCase directive names, equivalent to one item of a suite list. In TOML this reads as a [[tool.<cli>.test-suite.cases]] array of tables. Declared as an extension point so the configuration engine passes the raw mappings through unprocessed; the test-suite command turns them into CLITestCase instances.

timeout: int | None = None

Default timeout (seconds) for each case that does not set its own.

None leaves cases unbounded unless --timeout is passed.

class click_extra.config.builtin.PrebakeConfig(module=None)[source]

Bases: object

Config schema for the prebake commands, read from [tool.<cli>.prebake].

Lets a project pin the target __init__.py once for its build pipeline, instead of passing --module to every click-extra prebake command.

module: str | None = None

Path to the __init__.py to pre-bake, resolved relative to the project root. Overrides the [project.scripts] auto-discovery; leave unset to keep it.

class click_extra.config.builtin.ClickExtraConfig(test_suite=<factory>, prebake=<factory>)[source]

Bases: object

Schema for the [tool.click-extra] configuration section.

Wired as the config_schema of the top-level click-extra group, so every subcommand reads the same section and pulls its own sub-table through get_tool_config().

test_suite: TestSuiteConfig

The [tool.click-extra.test-suite] sub-table (file/cases/timeout).

prebake: PrebakeConfig

The [tool.click-extra.prebake] sub-table (target module).

click_extra.config.formats module

Configuration file formats and their stateless content parsers.

Holds the ConfigFormat enum, the optional third-party parser probes that decide which formats are enabled, and parse_content(), the stateless dispatch used by ConfigOption for every format that does not need the CLI parameter structure.

click_extra.config.formats.PARSER_SUPPORT: dict[str, bool] = {'hjson': True, 'json5': True, 'jsonc': True, 'xml': True, 'yaml': True}

Availability of each optional parser, keyed by click-extra[extra] name.

Populated once at import time by probing each module in _OPTIONAL_PARSERS with importlib.util.find_spec(). Read by ConfigFormat to mark the matching format as enabled or disabled. The probe does not import the module, so the actual parser is loaded lazily by parse_content() only when used.

class click_extra.config.formats.ConfigFormat(*values)[source]

Bases: Enum

All 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.glob for matching, and are influenced by the flags set on the ConfigOption instance.

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.

Todo

Add support for JWCC / hujson format?

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')
property label: str

Human-friendly name of the format for display in messages.

property enabled: bool

Returns True if the format is supported, False otherwise.

property patterns: tuple[str, ...]

Returns the default file patterns associated to the format.

click_extra.config.formats.parse_content(fmt, content)[source]

Parse content with a single stateless format.

INI is excluded: it needs the CLI parameter structure for type coercion and is handled by ConfigOption.load_ini_config.

Note

Optional third-party parsers are imported lazily, at the point of use, rather than at module load. Only enabled formats reach this function (disabled ones are filtered out of ConfigOption.file_format_patterns), so the import always resolves for the formats actually parsed here.

Return type:

Any

click_extra.config.formats.SERIALIZABLE_FORMATS: tuple[ConfigFormat, ...] = (ConfigFormat.TOML, ConfigFormat.YAML, ConfigFormat.JSON, ConfigFormat.JSON5, ConfigFormat.JSONC, ConfigFormat.HJSON, ConfigFormat.XML)

Configuration formats serialize_content() can write, in priority order.

Every ConfigFormat except INI and PYPROJECT_TOML, which have no serializer. JSON, JSON5 and JSONC are emitted as plain JSON through the standard library, so they need no optional dependency; the others require their format’s extra.

Caution

Keep this in sync with the match statement in serialize_content().

click_extra.config.formats.serialize_content(fmt, data, **kwargs)[source]

Serialize a Python object to a string in the given format.

The dumping counterpart to parse_content(). Per-format defaults can be overridden through kwargs (forwarded to the underlying serializer). JSON5 and JSONC are emitted as plain JSON, a valid subset of both.

Caution

Not every format round-trips: TOML and XML have no null type, and XML expects a single root mapping, so the caller is responsible for shaping data accordingly. INI and pyproject.toml have no serializer here.

Note

Optional third-party serializers are imported lazily, at the point of use. Writing TOML uses tomlkit (the [toml] extra), unlike reading which relies on the built-in tomllib.

Raises:

ValueError – the format has no serializer.

Return type:

str

click_extra.config.formats.format_from_path(path, formats=None)[source]

Return the configuration format whose patterns match a file name.

The name is matched against each format’s patterns, so app.toml resolves to TOML and app.yml to YAML. formats restricts and orders the candidates (the first match wins); it defaults to every ConfigFormat.

Return type:

ConfigFormat | None

click_extra.config.formats.disabled_format_message(fmt)[source]

Build the “format support disabled, install the extra” message for a format.

The single source for the ImportError text raised when a format whose optional parser is not installed is requested, shared by read_file() and click_extra.test_suite.parse_test_suite(). A format’s label, lower-cased, is its click-extra[<extra>] install target.

Return type:

str

click_extra.config.formats.read_file(path, formats=None)[source]

Read a file and parse it, picking the format from its name.

The format is resolved with format_from_path() over formats (every ConfigFormat by default), then the content is parsed with parse_content().

Raises:
  • ValueError – the file name matches none of the candidate formats.

  • ImportError – the matched format’s optional parser is not installed.

Return type:

Any

click_extra.config.option module

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.

Dotted keys in configuration files (like "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.option.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

click_extra.config.option.CONFIG_OPTION_NAME = 'config'

Hardcoded name of the configuration option.

This name is going to be shared by both the --config and --no-config options 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.option.DEFAULT_EXCLUDED_PARAMS = ('config', 'export_config', 'help', 'show_params', 'version')

Default parameter IDs to exclude from the configuration file.

Defaults to:

  • --config option, which cannot be used to recursively load another configuration file.

  • --export-config flag, which like --show-params introspects the CLI and exits, so it has no place in the configuration it would export.

  • --help, as it makes no sense to have the configurable file always forces a CLI to show the help and exit.

  • --show-params flag, which is like --help and stops the CLI execution.

  • --version, which is not a configurable option per-se.

class click_extra.config.option.Sentinel(*values)[source]

Bases: Enum

Enum used to define sentinel values.

Note

This reuse the same pattern as Click._utils.Sentinel.

NO_CONFIG = <object object>
VCS = <object object>
click_extra.config.option.NO_CONFIG = Sentinel.NO_CONFIG

Sentinel used to indicate that no configuration file must be used at all.

click_extra.config.option.VCS = Sentinel.VCS

Sentinel used to stop parent directory walking at the nearest VCS root.

class click_extra.config.option.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, schema_strict=False, fallback_sections=(), config_validators=(), **kwargs)[source]

Bases: ExtraOption, ParamStructure

A pre-configured option adding --config CONFIG_PATH.

Takes as input a path to a file or folder, a glob pattern, or an URL.

  • is_eager is active by default so the callback gets the opportunity to set the default_map of the CLI before any other parameter is processed.

  • default is set to the value returned by self.default_pattern(), which is a pattern combining the default configuration folder for the CLI (as returned by click.get_app_dir()) and all supported file formats.

    Attention

    Default search pattern must follow the syntax of wcmatch.glob.

  • excluded_params are 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 of DEFAULT_EXCLUDED_PARAMS.

  • included_params is the inverse of excluded_params: only the listed parameters will be loaded from the configuration file. Cannot be used together with excluded_params.

file_format_patterns: dict[ConfigFormat, tuple[str, ...]]

Mapping of ConfigFormat to 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 SPLIT flag is always forced, as our multi-pattern design relies on it.

force_posix

Configuration for default folder search.

roaming and force_posix are 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 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.

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 (.git or .hg) (default).

  • A Path or str: stop at that directory.

included_params: frozenset[str] | None

Allowlist of parameter IDs, mutually exclusive with excluded_params.

None disables the allowlist. It is resolved into excluded_params by build_param_trees(), once every parameter ID is known.

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), flattened (nested dicts joined with _), and passed to this callable to produce a typed configuration object.

Supports:

  • Dataclass types: detected via __dataclass_fields__. Keys are normalized, nested dicts are flattened, and the result is filtered to known fields before instantiation. This allows nested config sections (like [tool.myapp.sub-section]) to map directly to flat dataclass fields (like sub_section_key).

  • Any callable dict T: called directly with the raw dict. Works with Pydantic’s Model.model_validate, attrs, or custom factory functions. The caller is responsible for key normalization and flattening.

The resulting object is stored in ctx.meta[click_extra.context.TOOL_CONFIG] and can be retrieved via get_tool_config.

schema_strict

Strictness for schema validation (separate from strict).

  • If True, raise ValueError when the config section contains keys that do not match any dataclass field (after normalization and flattening). Only applies when config_schema is a dataclass.

  • If False, silently ignore unrecognized keys.

Note

This is distinct from strict, which controls whether merge_default_map rejects config keys not matching CLI parameters. schema_strict validates against dataclass fields instead.

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.

config_validators: tuple[ConfigValidator, ...]

Extension validators for sub-trees of the configuration file.

Each ConfigValidator targets a dotted extension_path relative to the app section. Validators run after click-extra’s built-in CLI-parameter strict check (during --validate-config) and after the schema callable produces the typed configuration object (during normal config loading).

The list is seeded with click-extra’s built-in validators (currently the one for [tool.<cli>.themes.<name>] tables, see click_extra.theme.validate_themes_config()); user-supplied validators are appended after them. App code that registers its own validator on the same extension_path simply runs alongside the built-in: both validators are called, both sets of errors surface.

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 for wcmatch brace 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 the roaming and force_posix properties.

Multiple file format patterns are wrapped with {…} brace-expansion syntax so that wcmatch.glob correctly applies the directory prefix to every sub-pattern.

Todo

Use platformdirs for more advanced configuration folder detection?

Return type:

str

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 GLOBTILDE flag is set in search_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 from BRACE or SPLIT expansion) is correctly scoped to the same directory.

root_dir is None for entirely magic patterns that will be evaluated relative to the current working directory.

Stops when reaching the root folder, the stop_at boundary, or an inaccessible directory.

Return type:

Iterable[tuple[str | None, str]]

search_and_read_file(pattern)[source]

Search filesystem or URL for files matching the pattern.

If pattern is an URL, download its content. A pattern is considered an URL only if it validates as one and starts with http:// or https://. 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_parents is True.

Raises FileNotFoundError if no file was found after searching all locations.

Return type:

Iterable[tuple[Path | URL, str]]

parse_conf(content, formats)[source]

Parse the content with the given formats.

Tries to parse the given raw content string with each of the given formats, in order. Yields the resulting data structure for each successful parse.

Attention

Formats whose parsing raises an exception or does not return a dict are considered a failure and are skipped.

This follows the parse, don’t validate principle.

Return type:

Iterable[dict[str, Any] | None]

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 FileNotFoundError if no configuration file was found matching the criteria above.

Returns (None, None) if files were found but none could be parsed.

Return type:

tuple[Path | URL, dict[str, Any]] | tuple[None, None]

load_ini_config(content)[source]

Utility method to parse INI configuration file.

Internal convention is to use a dot (., as set by PARAM_PATH_SEP) in section IDs as a separator between levels. This is a workaround the limitation of INI format which doesn’t allow for sub-sections.

Returns a ready-to-use data structure.

Return type:

dict[str, Any]

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 filters out all unrecognized options not supported by the command, then hand the result to _install_default_map().

Opaque sub-trees declared by the schema or by registered ConfigValidator instances are stripped from the conf before the CLI-parameter strict check, so user-controlled keys (like mappings whose keys are data, not flag names) don’t trip strict=True.

Note

This recomputes the filtered config that run_config_validation() already produces as merged_conf. load_conf() installs that result directly and skips this method; it stays as the standalone entry point for external callers.

Return type:

None

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.

Hint

Once loading is complete, the resolved file path and its full parsed content are stored in ctx.meta[click_extra.context.CONF_SOURCE] and ctx.meta[click_extra.context.CONF_FULL] respectively. This is the recommended way to identify which configuration file was loaded.

We intentionally do not add a custom ParameterSource.CONFIG_FILE enum member: ParameterSource is a closed enum in Click, and monkeypatching it would be fragile. Besides, config values end up in default_map, so Click already reports them as ParameterSource.DEFAULT_MAP, which is accurate.

Return type:

None

class click_extra.config.option.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: ExtraOption

A pre-configured option adding --no-config.

This option is supposed to be used alongside the --config option (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.

flag_value=NO_CONFIG is the Sentinel enum member that signals “skip configuration loading” to ConfigOption. Click 8.4.0 (PR pallets/click#3363) auto-detects type=UNPROCESSED for non-basic flag_value types, but click-extra still supports Click 8.3.x where that auto-detection is absent, so the type=UNPROCESSED override is kept explicit to let the sentinel pass through Option unchanged on every supported Click.

See also

An alternative implementation of this class would be to create a custom click.ParamType instead of a custom Option subclass. Here is for example.

check_sibling_config_option(ctx, param, value)[source]

Ensure that this option is used alongside a ConfigOption instance.

Return type:

None

class click_extra.config.option.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: ExtraOption

A 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.

validate_config(ctx, param, value)[source]

Load, parse, and validate the configuration file, then exit.

Validation runs three checks in order, every one of them under the same ValidationError shape so the reported path is always rooted at the configuration file:

  1. CLI-parameter strict check on the non-opaque part of the document.

  2. Schema processing, if a config_schema is configured: catches type errors and unknown keys inside the dataclass-described section.

  3. Each registered ConfigValidator runs against its declared opaque sub-tree.

Every detected error is emitted before exiting, so a single --validate-config run surfaces the full list of fixes the user needs to apply.

Return type:

None

class click_extra.config.option.ExportConfigOption(param_decls=None, type=None, metavar='FORMAT', is_eager=True, expose_value=False, help='Export the configuration in the selected format to <stdout>, then exit.', **kwargs)[source]

Bases: ExtraOption

A pre-configured option adding --export-config FORMAT.

Resolves the CLI’s current parameter values following Click’s precedence chain (command line, then environment variables, then configuration file, then defaults), renders them as a configuration file in the requested format on <stdout>, and exits.

Hint

Combine the flag with other options or environment variables to capture them in the generated configuration. For example, mycli --verbosity DEBUG --export-config toml emits a configuration whose verbosity is already set to DEBUG.

Like ValidateConfigOption, it relies on a sibling ConfigOption to provide the parameter structure and the excluded_params / included_params filter, so the export contains exactly the parameters that can be loaded back from a configuration file.

Note

The accepted formats are those serialize_content() can write (SERIALIZABLE_FORMATS). INI and pyproject.toml have no serializer and cannot be dumped.

build_config(ctx, config_option)[source]

Resolve every config-eligible parameter into a dumpable tree.

Walks the sibling ConfigOption’s parameter structure, resolves each parameter’s effective value by replaying RAW_ARGS (falling back to defaults when the command did not capture them), drops the excluded_params, and layers the coerced values into the {cli-name: {param: value, ...}} shape a configuration file uses. Blank values are removed, mirroring the clean-up ConfigOption._install_default_map() applies on load.

Return type:

dict[str, Any]

export_config(ctx, param, value)[source]

Render the resolved configuration to <stdout> and exit.

Return type:

None

click_extra.config.schema module

Schema-building and validation engine behind config_option and –validate-config.

click_extra.config.schema.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.schema.PREPEND_SUBCOMMANDS_KEY = '_prepend_subcommands'

Reserved configuration key for prepending subcommands to every invocation.

Unlike _default_subcommands which only fires when no subcommands are given on the CLI, _prepend_subcommands always prepends the listed subcommands. This is useful for always injecting a debug subcommand on a dev machine, for example.

Only works with chain=True groups (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.schema.EXTENSION_METADATA_KEY = 'click_extra.extension'

Dataclass field metadata flag marking a field as an extension point.

Schema authors set metadata={EXTENSION_METADATA_KEY: True} on a field when its sub-tree should pass through click-extra’s CLI-parameter strict check and be validated by app-specific logic instead. Equivalent to typing the field as dict[str, X]: both forms are recognized by _collect_opaque_paths_from_schema (the internal pipeline still calls these paths “opaque” since they’re skipped by the normalize/flatten/strict machinery). The metadata form is useful when the underlying Python type is something other than a dict (for example, a nested dataclass that nonetheless represents user-extensible content).

click_extra.config.schema.CONFIG_PATH_METADATA_KEY = 'click_extra.config_path'

Dataclass field metadata key pinning a field to an explicit config sub-path.

Schema authors set metadata={CONFIG_PATH_METADATA_KEY: "test-suite"} on a field so the dataclass loader (_from_dataclass) reads its value from that dotted path under the app’s configuration section, rather than from a key named after the field. The named counterpart to EXTENSION_METADATA_KEY.

click_extra.config.schema.NORMALIZE_KEYS_METADATA_KEY = 'click_extra.normalize_keys'

Dataclass field metadata key toggling key normalization on a field’s value.

Defaults to True. Schema authors set metadata={NORMALIZE_KEYS_METADATA_KEY: False} to keep a sub-tree’s keys verbatim, so external identifiers (like python-version axis names) are not rewritten to Python-style names (python_version). Read by _from_dataclass alongside CONFIG_PATH_METADATA_KEY.

exception click_extra.config.schema.ValidationError(path, message, code=None)[source]

Bases: Exception

Raised when a configuration file fails validation.

A single, structured exception type that uniformly carries the dotted path of the offending key, a human-readable message, and an optional code for programmatic handling. Used by click-extra’s built-in strict-mode check and by every user-registered ConfigValidator, so downstream apps and --validate-config see the same error shape regardless of who detected the problem.

Parameters:
  • path (str) – Dotted path to the offending key, relative to the configuration file root (like "my-cli.managers.winget.cli_searchpath"). An empty string means the error applies to the document as a whole.

  • message (str) – Human-readable description of the failure. Should be a single sentence, no trailing punctuation, no path repeated.

  • code (str | None) – Optional machine-readable error code (like "unknown_field") for callers that want to dispatch on error type without parsing the message string.

class click_extra.config.schema.ConfigValidator(extension_path, validator, description='')[source]

Bases: object

Register an app-defined extension validator for one sub-tree of the configuration file.

Apps register validators via the config_validators= kwarg on ConfigOption (or the matching decorator) to extend click-extra’s built-in CLI-parameter strict check with custom validation logic. Each validator targets a single dotted extension_path relative to the app’s configuration section. Click-extra passes the matching sub-tree straight through to the registered validator: the strict check skips it, the schema machinery treats it as opaque, and the user’s logic owns the result. The validator runs both during --validate-config and during normal config loading.

Parameters:
  • extension_path (str) – Dotted path of the sub-tree the validator owns, relative to the app’s section in the configuration file. For example, an app named my-cli with extension_path="managers" receives the contents of the [my-cli.managers] table.

  • validator (Callable[[dict[str, Any]], None]) – Callable taking the sub-tree dict and raising ValidationError on failure. Must be a pure function: no side effects on the click context, no print statements. The caller decides how to surface the error.

  • description (str) – Optional human-readable summary of what the validator checks. Surfaces in documentation generators that introspect the decorator (like autodoc), and may be reused in --help text in a future release.

extension_path: str
validator: Callable[[dict[str, Any]], None]
description: str = ''
click_extra.config.schema.normalize_config_keys(conf, opaque_keys=frozenset({}), _prefix='')[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 (--foo-bar becomes foo_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.

Parameters:
  • opaque_keys (frozenset[str]) – Fully-qualified key names (using "_" as separator) where recursion stops. The key itself is still normalized, but its dict value is kept as-is. Used in tandem with flatten_config_keys’s opaque_keys to protect data dicts (like GitHub Actions matrix axes) from normalization.

  • _prefix (str) – Internal parameter for tracking the accumulated key path during recursion. Callers should not set this.

Todo

Propose upstream to Click to extract the inline name.replace("-", "_") into a private _normalize_param_name helper, so downstream projects like Click Extra can reuse it instead of duplicating the transform.

Return type:

dict[str, Any]

click_extra.config.schema.flatten_config_keys(conf, sep='_', opaque_keys=frozenset({}), _prefix='')[source]

Flatten nested dicts into a single level by joining keys with a separator.

Useful for mapping nested configuration structures (like TOML sub-tables) to flat Python dataclass fields. After normalization with normalize_config_keys, the flattened keys match dataclass field names directly:

>>> from click_extra.config import (
...     flatten_config_keys,
...     normalize_config_keys,
... )
>>> raw = {"dependency-graph": {"all-groups": True, "output": "deps.mmd"}}
>>> flatten_config_keys(normalize_config_keys(raw))
{'dependency_graph_all_groups': True, 'dependency_graph_output': 'deps.mmd'}
Parameters:
  • conf (dict[str, Any]) – Nested dictionary to flatten.

  • sep (str) – Separator used to join parent and child keys. Defaults to "_" which produces valid Python identifiers when combined with normalize_config_keys.

  • opaque_keys (frozenset[str]) – Fully-qualified key names where flattening stops. When the accumulated key matches an entry in this set, the dict value is kept as-is instead of being recursively flattened. This is useful for fields typed as dict[str, X] where the dict keys are data (like GitHub Actions matrix axis names), not config structure.

  • _prefix (str) – Internal parameter for tracking the accumulated key path during recursion. Callers should not set this.

Return type:

dict[str, Any]

click_extra.config.schema.get_tool_config(ctx=None)[source]

Retrieve the typed tool configuration from the context.

Returns the object stored under click_extra.context.TOOL_CONFIG by ConfigOption when a config_schema is set, or None if no schema was configured or no configuration was loaded.

Parameters:

ctx (Context | None) – Click context. Defaults to the current context.

Return type:

Any

class click_extra.config.schema.SchemaFieldInfo(key: str, type_hint: str, default: Any, summary: str, description: str)[source]

Bases: NamedTuple

Documentation record for one option of a configuration schema.

Produced by schema_field_infos(). Consumed by the click:config Sphinx directive, and by CLIs building their own configuration reference (a show-config table, say) from the same introspection.

Create new instance of SchemaFieldInfo(key, type_hint, default, summary, description)

key: str

Dotted configuration path of the option.

Field names are kebab-cased (setup_guidesetup-guide) unless the field pins an explicit path through click_extra.config.schema.CONFIG_PATH_METADATA_KEY. Nested dataclass fields contribute one segment per nesting level (test-suite.timeout).

type_hint: str

The field’s type annotation, as written in the schema source.

default: Any

The field’s default value, taken from a pristine schema instance.

summary: str

First paragraph of the field’s attribute docstring, collapsed onto a single line. Empty when the field has no docstring or the class source is unavailable (see field_docstrings()).

description: str

Full attribute docstring of the field, paragraph breaks preserved.

click_extra.config.schema.field_docstrings(cls)[source]

Extract attribute docstrings from a class body, keyed by field name.

Attribute docstrings are string literals immediately following an annotated assignment in a class body (the PEP 257 convention used by Sphinx’s autodoc). Python discards them at runtime, so they are recovered by parsing the class source with ast. Each docstring is cleaned up with inspect.cleandoc(), preserving paragraph breaks.

Caution

Returns an empty mapping when the class source is unavailable, as for classes defined in an exec-ed code block (an interactive session, or the body of a click:source Sphinx directive). Import the schema from a real module to get its docstrings documented.

Return type:

dict[str, str]

click_extra.config.schema.schema_field_infos(schema)[source]

Walk a configuration schema dataclass into per-option records.

Introspects the dataclass fields, their type annotations, defaults, and attribute docstrings. Nested dataclass fields expand recursively into dotted keys (test-suite.timeout), honoring click_extra.config.schema.CONFIG_PATH_METADATA_KEY at every level. Records are sorted by key, segment-wise, so a sub-table’s options stay contiguous even when another table’s name shares their prefix (workflow.sync sorts before workflow-pins.sync).

Defaults are read off a pristine schema() instance, so every field must carry a default: configuration schemas are default-complete by construction, since make_schema_callable() instantiates them from partial user data.

Raises:

TypeError – when schema is not a dataclass type.

Return type:

list[SchemaFieldInfo]

click_extra.config.schema.make_schema_callable(schema, *, strict=False, normalize=True)[source]

Wrap a schema type into a callable that accepts a raw config dict.

  • Dataclass types (detected via dataclasses.is_dataclass) are auto-wrapped: keys are normalized (hyphens to underscores), nested dicts are flattened, and the result is filtered to known fields before instantiation. Three schema-aware features refine this process:

    1. Type-aware flattening. Fields typed as dict[str, X] are treated as opaque: flatten_config_keys stops at their boundary so the dict value is kept intact.

    2. Field metadata. Dataclass fields may carry click_extra.config_path (a dotted TOML path like "test-matrix.replace") and click_extra.normalize_keys (False to skip key normalization on the extracted value). Fields with an explicit path are extracted from the raw config before normalization and flattening.

    3. Nested dataclass support. Fields whose resolved type is itself a dataclass are recursively processed with the same logic.

  • Any other callable is returned as-is. The caller is responsible for key normalization if needed.

  • None returns None.

Parameters:
  • strict (bool) – If True, raise ValueError when the config contains keys that do not match any dataclass field (after normalization and flattening).

  • normalize (bool) – If False, skip normalize_config_keys on the remaining config dict. Used internally when recursing into nested dataclasses whose parent opted out of normalization via click_extra.normalize_keys = False.

Return type:

Callable[[dict[str, Any]], Any] | None

class click_extra.config.schema.ValidationReport(schema_instance, opaque_subtrees, errors, merged_conf=None)[source]

Bases: object

Outcome of one pass through run_config_validation().

Bundles everything a caller needs after validating a parsed configuration document: the typed schema instance, the extracted opaque sub-trees, the template-filtered config ready for default_map, and every error detected across all validation stages.

Note

The report holds references to the parsed sub-trees, not copies, so building it is cheap regardless of document size.

schema_instance: Any | None

Typed object produced by the configured schema callable.

None when no schema is configured, or when the schema stage raised (in which case the failure is recorded in errors).

opaque_subtrees: dict[str, dict[str, Any]]

Extracted extension sub-trees, keyed by dotted path relative to the app section. Only paths actually present in the document appear here, so callers can re-route them to per-path validators or stash them on ctx.meta.

errors: tuple[ValidationError, ...]

Every ValidationError detected, in stage order (unknown CLI-flag keys first, then schema errors, then validator failures). Empty on success.

With collect_all=False this holds at most one error: the first failure short-circuits the remaining stages.

merged_conf: dict[str, Any] | None = None

The CLI-flag-bound configuration merged onto params_template: the payload _install_default_map() layers into the context’s default_map.

None when params_template was None (no strict check) or the strict check raised. Read it only on a successful report: it is the same value merge_default_map() would recompute, so reusing it avoids a second normalize/strip/merge pass.

property ok: bool

True when no error was detected.

click_extra.config.schema.run_config_validation(user_conf, *, app_name, params_template, config_schema=None, config_validators=(), fallback_sections=(), schema_strict=False, strict=False, collect_all=True)[source]

Validate a parsed configuration document in one schema-driven pass.

This is the module-level entry point that unifies click-extra’s three historical validation paths (CLI-parameter strict check, dataclass schema, and app-registered ConfigValidator hooks) behind a single function yielding a single error type. It is deliberately not named validate_config: that name belongs to validate_config(), the callback powering the --validate-config flag.

Stages, in order:

  1. Normalize. Strip reserved keys and expand dotted keys.

  2. Partition. Split opaque sub-trees (schema extension fields plus every registered validator’s extension_path) from the CLI-flag-bound content. Extracted sub-trees land in ValidationReport.opaque_subtrees.

  3. Strict-check the CLI-flag-bound part against params_template, keeping the merged result as ValidationReport.merged_conf (skipped when params_template is None).

  4. Schema-build the app section through the configured callable, producing ValidationReport.schema_instance.

  5. Validate every opaque sub-tree through its registered validator.

Parameters:
  • user_conf (dict[str, Any]) – The full parsed configuration document.

  • app_name (str) – Name of the app’s section (used to resolve the section and to root opaque paths and error paths at the document level).

  • params_template (dict[str, Any] | None) – The CLI-parameter template the strict check runs against. Pass None to skip the strict check entirely (for example, for a schema-only validation).

  • config_schema (type | Callable[[dict[str, Any]], Any] | None) – Dataclass type or callable describing the typed configuration, or None.

  • config_validators (Sequence[ConfigValidator]) – Extension validators to run against opaque sub-trees.

  • fallback_sections (Sequence[str]) – Legacy section names to try when app_name is absent or empty.

  • schema_strict (bool) – Reject keys the dataclass schema does not recognize.

  • strict (bool) – Reject keys the CLI-parameter template does not recognize.

  • collect_all (bool) – When True (default), run every stage and collect all errors. When False, the first error short-circuits the rest.

Return type:

ValidationReport

Returns:

A ValidationReport. ValidationError is the single error type recorded by every stage; ValueError / TypeError raised by the strict check or schema callable are wrapped into it.