click-configfile
click-configfile extends Click commands with support for configuration files. It enables parsing of various formats (INI, JSON, YAML) through a defined schema, seamlessly integrating configuration values into Click's default parameter map. It is currently at version 0.2.3 and has a stable, low-cadence release cycle.
Common errors
-
ModuleNotFoundError: No module named 'json5'
cause Attempting to read a JSON config file without installing the optional `json5` dependency.fixInstall `click-configfile` with JSON support: `pip install click-configfile[json]`. -
ModuleNotFoundError: No module named 'PyYAML'
cause Attempting to read a YAML config file without installing the optional `PyYAML` dependency.fixInstall `click-configfile` with YAML support: `pip install click-configfile[yaml]`. -
ValueError: Invalid type for parameter 'value'. Expected <class 'int'>, got 'a_string'
cause A value in the configuration file does not match the expected type defined in the `Param` in your `SectionSchema`.fixEnsure that values in your config file (e.g., `value = a_string`) match the `type` specified in your `Param` definition (e.g., `value = Param(type=int)`). -
Config values are not being applied, even though the file exists.
cause The section name in the config file does not match the `@matches_section` decorator, or the parameter names don't match the `Param` definitions, or the file path is incorrect.fixVerify that `ConfigFileReader.config_files` contains the correct path, the `[section]` name in your config file matches `@matches_section`, and parameter keys (e.g., `name = ...`) match `Param(name='name', ...)`.
Warnings
- gotcha To use JSON or YAML configuration files, you must install the respective optional dependencies (json5 or PyYAML). Without them, `ConfigFileReader` will fail if it encounters these file types.
- gotcha Configuration values from files are populated into Click's `default_map`. Command-line arguments explicitly provided by the user will always override values from the configuration file.
- gotcha If `ConfigFileReader.config_files` contains multiple paths, files are searched in the order specified. The first file found and successfully parsed will be used. Later files are ignored.
- gotcha Sections in the config file must exactly match the section names specified by the `@matches_section` decorator in your `SectionSchema` classes. Mismatches will result in those sections not being parsed.
Install
-
pip install click-configfile -
pip install click-configfile[json] -
pip install click-configfile[yaml]
Imports
- ConfigFileReader
from click_configfile import ConfigFileReader
- Param
from click_configfile import Param
- SectionSchema
from click_configfile import SectionSchema
- matches_section
from click_configfile import matches_section
- assign_param_names
from click_configfile import assign_param_names
Quickstart
import click
from click_configfile import ConfigFileReader, Param, SectionSchema, matches_section
import os
# Create a dummy config file for demonstration
config_content = """
[main]
name = RegistryUser
value = 42
"""
config_file_path = "myconfig.ini"
with open(config_file_path, "w") as f:
f.write(config_content)
# Define your config file schema
@matches_section("main")
class ConfigSectionSchema(SectionSchema):
name = Param(type=str, default="world")
value = Param(type=int, default=10)
# Create a config file reader instance
class MyConfigFileReader(ConfigFileReader):
# Prioritize 'myconfig.ini' in the current directory
config_files = [f"./{config_file_path}"]
config_section_schemas = [ConfigSectionSchema]
# Decorator to apply config file processing
@click.command(context_settings=dict(default_map=MyConfigFileReader.read_config()))
@click.option("--name", default=None, help="Name to greet.")
@click.option("--value", default=None, type=int, help="A numeric value.")
def cli(name, value):
"""A simple CLI that uses a config file."""
# If 'name' or 'value' were not provided via CLI, they'll come from default_map (config or Param default)
name_to_use = name if name is not None else ConfigSectionSchema.name.default
value_to_use = value if value is not None else ConfigSectionSchema.value.default
click.echo(f"Hello, {name_to_use}! Your value is {value_to_use}.")
click.echo(f"Config files searched: {MyConfigFileReader.config_files}")
click.echo(f"Default map from config: {MyConfigFileReader.read_config()}")
# Clean up the dummy config file
os.remove(config_file_path)
if __name__ == "__main__":
cli()