Skip to main content

confargs

⚠️ Early development. APIs may change.

confargs is a small, declarative CLI argument parser for Python 3.10+ that merges configuration from three sources into one result:

  1. Command line arguments (--log out.html, -l NONE)
  2. Environment variables (per-option or auto-generated)
  3. TOML config files (discovered by walking up from the current directory, pyproject.toml-style)

You describe options as methods on a class. Each method receives the raw value from whichever source supplied it, performs any parsing/validation you like, and returns the final value. confargs handles discovery, precedence and basic type coercion; your code owns the domain logic.

import confargs
from confargs import ArgConfig


class MyArgs(ArgConfig):
    """My CLI tool.

    Longer description shown in --help.
    """

    name = "mytool"

    # Declarative option: no method needed when there's nothing to parse.
    title = confargs.option(name="title", default="report", help="Report title.")

    @confargs.option
    def log(self, value: str | None = "log.html") -> str | None:
        """HTML log file. Disable with the special value 'NONE'."""
        if value == "NONE":
            return None
        return value

    @confargs.option(name="console", short="c")
    def console(self, value: str = "verbose") -> str:
        choices = ["verbose", "dotted", "quiet", "none"]
        if value not in choices:
            raise confargs.OptionValueError(f"console must be one of {choices}")
        return value


config = confargs.ConfigurationProcessor(MyArgs).process()
print(config.title, config.log, config.console)

Declaring options

Options come in two flavours:

  • Method-based (@confargs.option): the decorated method receives the raw value and returns the parsed/validated result. Use this whenever you need to transform or validate the value.
  • Declarative (attr = confargs.option(name=..., help=...)): a plain class attribute with no method, for simple values that need no custom handling. The value passes straight through coercion. Set default= (a bool makes it a flag, None makes it optional) and type= to control the value type.

Precedence

Highest wins: CLI > environment variables > nearest TOML > user-directory TOML > option default.

Configuration sources

TOML files

Config is read from a table named after your tool. By default that is [tool.<name>] (e.g. [tool.mytool]); override it with default_config_section = "tool.custom". The file names searched are set with config_names (default ["pyproject.toml"]). Both dashed-keys and snake_case_keys are accepted.

[tool.mytool]
log = "results.html"
console = "dotted"
tags = ["ci", "nightly"]

Discovery walks up from the current directory looking for those files and stops at the project root (a directory containing .git). If nothing is found, a per-user config directory is consulted (%APPDATA%\<name> on Windows, $XDG_CONFIG_HOME/<name> otherwise). Discovery is controlled by built-in, CLI-only options:

  • --config PATH — use only this file, skip discovery.
  • --no-config — ignore config files entirely.
  • --ignore-git — keep searching above the .git project root.

By default (strict_config = True) unknown keys — and any option declared with config=False — found in the config section raise an error, which catches typos early. Set strict_config = False on your class to silently ignore them instead.

Environment variables

Reading from the environment is opt-in per option. Pass env=True to use a generated name, or env="MY_NAME" for an explicit one:

@option(env=True)  # reads $MYTOOL_LOG (from the class template)
def log(self, value: str = "log.html") -> str: ...


@option(env="LOG_FILE")  # reads $LOG_FILE
def log2(self, value: str = "log.html") -> str: ...

The generated name comes from the class env_var_template (default "{name}_{option}"), formatted with the tool name and the option attribute name and upper-cased — e.g. MYTOOL_LOG. Override it per class:

class Args(ArgConfig):
    name = "mytool"
    env_var_template = "MYTOOL_CFG_{option}"  # -> MYTOOL_CFG_LOG

Restricting where an option is read from

Two independent toggles control which sources feed an option:

  • @option(cli=False) hides the option from the command line (no CLI names, not shown in --help) — use for options that should only come from config files or the environment.
  • @option(config=False) stops the option being loaded from TOML config files — use for switches that control the tool run itself (the built-in discovery options above are defined this way).

Combine them as needed, e.g. a CLI-only switch is @option(config=False) with env left off.

Options in depth

  • Long names come from the method name (dry_run--dry-run); a short name is derived from the first letter when it is still free. Override either with name="console" and/or short="c" (passing name opts out of the implicit short — add short= to keep one).
  • The value type is taken from the value parameter annotation. bool becomes a flag; list[...] becomes a repeatable option; int/float/str are coerced from strings. Your method receives the coerced value and returns the final one — raise confargs.OptionValueError to reject it.
  • Boolean options can be negated on the command line: --verbose sets it to True, --no-verbose sets it to False.

Eager options and argument files

Mark an option is_eager=True to resolve it before every other source, directly against argv. The method's return value — an iterable of tokens or None — replaces the option's own arguments, so it can inject more options. This is how an --argumentfile option expands a file (Robot Framework style) into extra arguments, including nested argument files:

from confargs import ArgConfig, option, read_argument_file


class Args(ArgConfig):
    @option(name="argumentfile", short="A", config=False, is_eager=True)
    def argumentfile(self, value: str | None = None) -> list[str] | None:
        return read_argument_file(value) if value else None

ConfigurationProcessor(Args, argv=[...]) accepts an explicit argument list; when omitted it falls back to sys.argv[1:].

Example

A complete, self-contained example lives in examples/demo.py (with a sample examples/example.args and examples/README.md). It's a single copy-pasteable file showing value options, --no- flag negation, environment variables and an eager --argumentfile. Run it from a checkout without installing anything:

uv run python examples/demo.py --who Ada --repeat 3
uv run python examples/demo.py -A examples/example.args
uv run python examples/demo.py --help

Separately, the packaged confargs.demo module is installed as the confargs-demo console script via [project.scripts]:

uv run confargs-demo --console quiet --retries 5
uv run confargs-demo --help

To ship your own tool, point a console script at a main() that runs the processor, for example in pyproject.toml:

[project.scripts]
mytool = "mytool.cli:main"

Development

This project uses uv.

uv sync                 # create the environment
uv run pytest           # run the tests
uv run ruff check       # lint
uv run ruff format      # format
uv run mypy             # type-check
pre-commit install      # enable git hooks

Publishing

Releases are published to PyPI by .github/workflows/publish.yml when a GitHub Release is published. It uses PyPI Trusted Publishing (OIDC), so no API token is stored in the repository — configure the project as a trusted publisher on PyPI (workflow publish.yml, environment pypi) once.

Versioning

confargs follows Semantic Versioning. The version is single-sourced from __version__ in src/confargs/__init__.py (hatchling reads it at build time). While the project is 0.x.y the API is still stabilising, so minor releases may include breaking changes. Notable changes are recorded in CHANGELOG.md.

Releases are automated with release-please: merging Conventional Commits to main keeps an open release PR that bumps __version__, updates the changelog and, once merged, tags the release and publishes to PyPI. Pre-1.0, breaking changes bump the minor version (bump-minor-pre-major).

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

confargs-0.3.0.tar.gz (94.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

confargs-0.3.0-py3-none-any.whl (27.3 kB view details)

Uploaded Python 3

File details

Details for the file confargs-0.3.0.tar.gz.

File metadata

  • Download URL: confargs-0.3.0.tar.gz
  • Upload date:
  • Size: 94.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for confargs-0.3.0.tar.gz
Algorithm Hash digest
SHA256 bf1a085ec0ec070b8b1b60e2a25e2110096122373a07b759cd10d2a2c1c7c3cb
MD5 482e36d3b1c92128bb008200d4317c1e
BLAKE2b-256 3a6ae3592af65d72387564a90d21aba568f3cc49a8d7f810577769d65b2078a5

See more details on using hashes here.

Provenance

The following attestation bundles were made for confargs-0.3.0.tar.gz:

Publisher: release-please.yml on MarketSquare/confargs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file confargs-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: confargs-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 27.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for confargs-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4dce2ee8391862669cbb8012e208b0580e635ef6a05ccf8576d924ebe8b6e81d
MD5 cfc642cda55d3e68eb4fab843c3c302e
BLAKE2b-256 4e88526caca15ddf53562210c2608f91561084d43fdfe9dd8e2f11a003a9325a

See more details on using hashes here.

Provenance

The following attestation bundles were made for confargs-0.3.0-py3-none-any.whl:

Publisher: release-please.yml on MarketSquare/confargs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.9.0

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

This release

0.3.0 This release

2 files

0.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page