Configuration layers
Beyond CLI arguments, duho can pull defaults from environment variables and a TOML config file. The full precedence ladder, highest first:
CLI args > instance values > env var > config file > class default
A value supplied by any layer also un-requires that field: a field with no class default that's set in the config file no longer has to be passed on the command line.
Environment variables
Annotate a field with NS(env="VAR_NAME"):
from duho import Args, Arg, NS
class Deploy(Args):
"""Deploy the app."""
token: Arg[str, NS(env="DEPLOY_TOKEN")] = ""
"Auth token"
("--token",)
$ export DEPLOY_TOKEN=abc123
$ deploy # token == "abc123"
$ deploy --token override # token == "override" (CLI wins)
The env value is converted with the field's own type, so NS(env="PORT") on an
int field yields an int — and a bad value produces the same clear error
argparse would give.
Config files
Set _config_ on the class, or pass config= to duho.parse / duho.main
(the keyword argument wins):
class Deploy(Args):
_config_ = "~/.config/myapp/config.toml"
result = duho.parse(Deploy, config="./deploy.toml")
result = duho.main(Deploy, config="./deploy.toml")
Top-level keys map to the root command's fields. A table named after a subcommand maps to that subcommand's fields:
# deploy.toml
token = "abc123"
verbose = true
[install]
target = "prod"
Unknown keys are ignored (with a debug log line), so a config file can carry settings for several versions of your tool without breaking older ones.
TOML support
Reading TOML uses the standard library's tomllib on Python 3.11+. On 3.9 and
3.10 it falls back to the third-party tomli package:
pip install duho[config]
duho stays zero-dependency by default — you only need this extra if you actually
use _config_ / config= on an older interpreter. If neither backend is
available, duho raises a clear error telling you to install it, rather than
failing obscurely.
Where did this value come from?
duho.value_sources(parsed) reports which layer won for each field:
result = duho.parse(Deploy, [], config="./deploy.toml")
duho.value_sources(result)
# {"token": "env", "verbose": "config", "target": "default"}
Each value is one of "cli", "env", "config", or "default". This is the
fastest way to answer "why is this setting not what I expect".