Config Files and Environment Variables

fargv supports two sources of parameter overrides that sit between coded defaults and the command line: a config file (JSON, INI, TOML, or YAML) and environment variables. Both are injected automatically — no annotation or registration needed.


Priority order

coded default  →  config file  →  env var  →  CLI / UI

This can be changed with the override_order argument to parse().


Config file

Auto-injected parameter

fargv injects a --config / -C parameter automatically:

Parameter

Alias

Default

Description

--config

-C

~/.{appname}.config.json

Path to a JSON/YAML/TOML/INI config file

# Explicitly point to a config file
python train.py --config=/opt/shared/train.json
python train.py -C /opt/shared/train.json   # same, short form

# If the default path exists it is loaded automatically
python train.py   # reads ~/.train_py.config.json if it exists

Key naming convention

Config files use flat keys. Top-level parameters appear as-is. Subcommand branch parameters are prefixed with the branch name and a dot:

Parameter

Subcommand branch

Config key

lr (top-level)

lr

lr (inside branch train)

train

train.lr

epochs (inside branch eval)

eval

eval.epochs

The subcommand field name itself (cmd, command, etc.) is not a valid config key — only branch names are used as prefixes.

Config file formats

All four formats use the same flat-key convention.

JSON (default):

{
  "fargv_comment.lr": "Learning rate. env var: TRAIN_LR",
  "lr": 0.0005,
  "fargv_comment.train.lr": "Branch train — learning rate",
  "train.lr": 0.001,
  "train.epochs": 50
}

Keys that start with fargv_comment are silently discarded by the loader. They act as JSON pseudo-comments and are written automatically by the dump functions to describe each parameter (full help text + env var name).

INI (single [main] section, ; comments):

[main]

; Learning rate.  type: float  default: 0.01  env var: TRAIN_LR
lr = 0.0005

; --- train ---
; Learning rate inside train branch.  type: float  default: 0.01  env var: TRAIN_TRAIN_LR
train.lr = 0.001

; Epochs inside train branch.  type: int  default: 10  env var: TRAIN_TRAIN_EPOCHS
train.epochs = 50

TOML (flat, quoted keys for dotted names):

# Learning rate.  type: float  default: 0.01  env var: TRAIN_LR
lr = 0.0005

# --- train ---
# Learning rate inside train branch.  type: float  default: 0.01  env var: TRAIN_TRAIN_LR
"train.lr" = 0.001

# Epochs inside train branch.  type: int  default: 10  env var: TRAIN_TRAIN_EPOCHS
"train.epochs" = 50

Keys containing dots must be quoted in TOML to avoid being interpreted as nested table syntax.

YAML (flat, dot characters are plain in YAML key names):

# Learning rate.  type: float  default: 0.01  env var: TRAIN_LR
lr: 0.0005

# --- train ---
# Learning rate inside train branch.  type: float  default: 0.01  env var: TRAIN_TRAIN_LR
train.lr: 0.001

# Epochs inside train branch.  type: int  default: 10  env var: TRAIN_TRAIN_EPOCHS
train.epochs: 50

Generating a config file

Use the //format syntax to print a config to stdout and exit. fargv also prints the suggested save path to stderr:

# Dump as JSON (default)
python train.py --config //json
python train.py -C //json

# Dump as INI
python train.py --config //ini

# Dump as TOML
python train.py --config //toml

# Dump as YAML
python train.py --config //yaml
$ python train.py --lr=0.001 --config //json
{
  "fargv_comment.lr": "Learning rate.  type: float  default: 0.01  env var: TRAIN_LR",
  "lr": 0.001,
  ...
}
fargv: to persist, redirect to: /home/user/.train_py.config.json

Save it and it loads automatically on the next run:

python train.py --lr=0.001 --config //json > ~/.train_py.config.json
python train.py   # lr=0.001 from config

Or use a different format and a different path:

python train.py --config //ini > myconfig.ini
python train.py --config myconfig.ini

Variadic parameters in config files

FargvVariadic parameters (list defaults) are not written to config dumps because persisting a variadic default can cause stale-config bugs when the coded default is changed later. In formats that support comments (INI, TOML, YAML), variadic entries appear as comments so you can see them but they have no effect. In JSON they are omitted entirely.

Unknown keys

By default, if a config file contains any unknown key, fargv prints a warning to stderr for each bad key and then ignores the entire config dict:

fargv: config file 'run.json': unknown key 'ghost.lr' — ignoring config

This conservative default prevents a config written for one version of a script from silently half-applying to a different version.

Change the policy with the unknown_keys argument:

Value

Behaviour

"ignore_dict_and_warn" (default)

warn per bad key; discard whole dict

"ignore_key_and_warn"

warn per bad key; apply all valid keys

"raise"

raise FargvError on first bad key

p, _ = fargv.parse(definition, unknown_keys="ignore_key_and_warn")

Disabling config-file support

p, _ = fargv.parse(definition, auto_define_config=False)

Environment variables

Every user-defined parameter automatically accepts an environment variable override. The naming follows the same flat convention as config files but uses underscore (_) as the separator and adds the app-name prefix:

{APPNAME}_{KEY}   (everything uppercased)

Parameter

App name

Env var

lr (top-level)

train.py

TRAIN_PY_LR

lr (branch train)

prog.py

PROG_PY_TRAIN_LR

epochs (branch eval)

prog.py

PROG_PY_EVAL_EPOCHS

export TRAIN_PY_LR=0.0001
python train.py          # lr=0.0001 from env var

TRAIN_PY_LR=0.0001 python train.py --lr=0.001   # lr=0.001 (CLI wins)

The subcommand field name itself is not a valid env var target. Only branch parameters can be set via env vars, using the APPNAME_BRANCH_PARAM pattern.

# Set parameter 'lr' inside subcommand branch 'train'
export PROG_TRAIN_LR=0.5
python prog.py train     # train.lr=0.5

Unknown env vars (no matching parameter) are silently ignored — fargv only checks env var names that correspond to known parameters.


Sharing a config file across multiple CLI tools

When a project contains several CLI entry points that share common parameters, you can group them under a single suite class so they all read from and write to one config file.

Design

Define a @deep_dataclass whose inner classes are the individual tools. Every inner class is automatically a deep dataclass — no extra decorator needed. Use @auxiliary on any inner class you do not want automatically instantiated: shared base classes, list-element prototypes, types used for multiple fields, or any other supporting class. Non-auxiliary inner classes are the real CLI entry points.

from deep_dataclasses import deep_dataclass, auxiliary
import fargv

@deep_dataclass
class DdpMsConfigs:
    """Suite of microservice CLI tools."""

    @auxiliary          # suppresses auto-instantiation — not a CLI entry point
    class GlobalConfig:
        log_level: str = "INFO"
        "Logging level shared by all services."

    class Microservice(GlobalConfig):   # inner classes are deep_dataclasses automatically
        host: str = "localhost"
        port: int = 8080

    class MsStatic(Microservice):
        static_dir: str = "/var/static"

    class MsDetection(Microservice):
        model_path: str = "/models/det.pt"
        threshold:  float = 0.5

Each tool’s entry point calls fargv.parse with suite_root=:

# ms_static.py
cfg, _ = fargv.parse(DdpMsConfigs.MsStatic, suite_root=DdpMsConfigs)

# ms_detection.py
cfg, _ = fargv.parse(DdpMsConfigs.MsDetection, suite_root=DdpMsConfigs)

Shared config file path

The config file path is derived from the suite class name:

~/.{SuiteClassName}.json       # e.g.  ~/.DdpMsConfigs.json

All tools in the suite read from and write to this same file.

Config file structure

The file is organised into sections, one per class, each containing only the fields declared directly in that class (not inherited ones). Inheritance is expressed through the Python MRO, not in the file:

{
  "fargv_comment__section_GlobalConfig": "── GlobalConfig ──",
  "GlobalConfig": {
    "fargv_comment_log_level": "--log_level <str>  [default: 'INFO']",
    "log_level": "WARNING"
  },
  "fargv_comment__section_Microservice": "── Microservice ──",
  "Microservice": {
    "fargv_comment_host": "--host <str>  [default: 'localhost']",
    "host": "10.0.0.1",
    "fargv_comment_port": "--port <int>  [default: 8080]",
    "port": 9000
  },
  "fargv_comment__section_MsStatic": "── MsStatic ──",
  "MsStatic": {
    "fargv_comment_static_dir": "--static_dir <str>  [default: '/var/static']",
    "static_dir": "/srv/static"
  }
}

Loading order (MRO cascade)

When MsStatic starts, values are applied base-first following the MRO, so more-derived classes can override base-class defaults:

GlobalConfig section  →  Microservice section  →  MsStatic section

Keys in other tool sections (e.g. MsDetection) are silently ignored — no warnings, because they are expected sibling-tool data.

Generating the suite config

The //format syntax dumps the full suite config (all sections, all tools) in one shot:

python ms_static.py --config //json > ~/.DdpMsConfigs.json

--config= (empty string) also dumps as JSON and exits.

Environment variables

Env var names use the tool class name as the prefix, not argv[0]:

Tool

Parameter

Env var

MsStatic

static_dir

MSSTATIC_STATIC_DIR

MsDetection

threshold

MSDETECTION_THRESHOLD

MsDetection

host (inherited)

MSDETECTION_HOST

Pointing to a different config file

The --config flag still works as usual:

python ms_static.py --config /opt/shared/suite.json

override_order

The override_order list controls which sources are active and which wins. Rules: must start with "default", must end with "ui", no duplicates.

# Config only, no env vars
p, _ = fargv.parse(definition, override_order=["default", "config", "ui"])

# Env vars only, no config file
p, _ = fargv.parse(definition, override_order=["default", "envvar", "ui"])

# Env vars take priority over config (reversed from default)
p, _ = fargv.parse(definition, override_order=["default", "envvar", "config", "ui"])