Skip to content

CLI#

dature ships with a dature console script — installed automatically with the package, no extra dependencies. It provides two subcommands:

  • dature inspect — print the LoadReport (sources, field origins, merged data) for an existing configuration.
  • dature validate — load the schema; exit 0 on success, 1 on validation failure, 2 on usage error.

Run dature --help for the top-level summary or dature <command> --help for per-command flags.

Nothing in the CLI is invented

Every flag below is a 1-to-1 reflection of an existing Python API:

  • --schema → the schema argument of dature.load()
  • --source type=X,k=v,... → instantiating a Source subclass with k=v keyword arguments
  • All global flags → matching parameters of dature.load(), generated from its signature so they stay in sync automatically

The only CLI-specific flags are --field and --format (output filtering and rendering for inspect).

Quick start#

A schema module and a JSON config file:

from dataclasses import dataclass


@dataclass
class DB:
    host: str
    port: int


@dataclass
class Settings:
    db: DB
{"db": {"host": "localhost", "port": 5432}}

Validate the config — exit 0, prints OK:

dature validate \
    --schema myschema:Settings \
    --source 'type=dature.JsonSource,file=sources/config.json'
OK

Inspect the load report in human-readable form:

dature inspect \
    --schema myschema:Settings \
    --source 'type=dature.JsonSource,file=sources/config.json' \
    --format text
Schema: Settings (strategy: —)

Sources:
  [0] json         sources/config.json

Field origins:
  db = {"host": "localhost", "port": 5432}   <- [0] sources/config.json

Merged data:
  {
    "db": {
      "host": "localhost",
      "port": 5432
    }
  }

inspect#

Loads the schema with debug=True, retrieves the LoadReport, and prints it.

usage: dature inspect [-h] --schema MODULE:ATTR --source SPEC
                      [--strategy {last_wins,first_wins,first_found,raise_on_conflict}]
                      [--skip-broken-sources] [--skip-invalid-fields]
                      [--expand-env-vars {disabled,default,empty,strict}]
                      [--secret-field-names SECRET_FIELD_NAMES]
                      [--mask-secrets] [--field DOTTED.PATH]
                      [--format {json,text}]

options:
  -h, --help            show this help message and exit
  --schema MODULE:ATTR  Import path to dataclass schema (e.g.
                        myapp.config:Settings).
  --source SPEC         Source spec: 'type=Class,k=v,k=v'. Repeatable (order
                        preserved). Use \, and \= to escape separators in
                        values.
  --strategy {last_wins,first_wins,first_found,raise_on_conflict}
  --skip-broken-sources
  --skip-invalid-fields
  --expand-env-vars {disabled,default,empty,strict}
  --secret-field-names SECRET_FIELD_NAMES
  --mask-secrets
  --field DOTTED.PATH   Filter origins and merged data by a dotted field path.
  --format {json,text}  Output format (default: json).
Flag Maps to Description
--field PATH CLI-only Filters field_origins and merged_data by a dotted path (e.g. db.port). Origins matching the path or prefixed by it are shown; merged data is narrowed to the value at that path.
--format {json,text} CLI-only Output format. json (default) is stable and parseable; text is human-readable.

JSON output#

dature inspect \
    --schema myschema:Settings \
    --source 'type=dature.JsonSource,file=sources/config.json'
{
  "schema": "Settings",
  "strategy": null,
  "sources": [
    {
      "index": 0,
      "file_path": "sources/config.json",
      "loader_type": "json",
      "raw_data": {
        "db": {
          "host": "localhost",
          "port": 5432
        }
      }
    }
  ],
  "field_origins": [
    {
      "key": "db",
      "value": {
        "host": "localhost",
        "port": 5432
      },
      "source_index": 0,
      "source_file": "sources/config.json",
      "source_loader_type": "json"
    }
  ],
  "merged_data": {
    "db": {
      "host": "localhost",
      "port": 5432
    }
  }
}

Field filter#

Narrow the report to a single dotted path — db.port here resolves to a scalar in merged_data:

dature inspect \
    --schema myschema:Settings \
    --source 'type=dature.JsonSource,file=sources/config.json' \
    --field db.port
{
  "schema": "Settings",
  "strategy": null,
  "sources": [
    {
      "index": 0,
      "file_path": "sources/config.json",
      "loader_type": "json",
      "raw_data": {
        "db": {
          "host": "localhost",
          "port": 5432
        }
      }
    }
  ],
  "field_origins": [],
  "merged_data": 5432
}

validate#

Runs load(*sources, schema=Schema, ...) and reports the result via exit code. Use it as the final gate in CI/CD.

usage: dature validate [-h] --schema MODULE:ATTR --source SPEC
                       [--strategy {last_wins,first_wins,first_found,raise_on_conflict}]
                       [--skip-broken-sources] [--skip-invalid-fields]
                       [--expand-env-vars {disabled,default,empty,strict}]
                       [--secret-field-names SECRET_FIELD_NAMES]
                       [--mask-secrets]

options:
  -h, --help            show this help message and exit
  --schema MODULE:ATTR  Import path to dataclass schema (e.g.
                        myapp.config:Settings).
  --source SPEC         Source spec: 'type=Class,k=v,k=v'. Repeatable (order
                        preserved). Use \, and \= to escape separators in
                        values.
  --strategy {last_wins,first_wins,first_found,raise_on_conflict}
  --skip-broken-sources
  --skip-invalid-fields
  --expand-env-vars {disabled,default,empty,strict}
  --secret-field-names SECRET_FIELD_NAMES
  --mask-secrets
Exit code Meaning
0 All sources loaded, schema validated — OK printed to stdout.
1 Loading or validation failed (invalid value, missing source file, merge conflict). Full error written to stderr.
2 Usage error — bad --schema import path, malformed --source spec, or class that is not a dature.Source subclass.

Two sources merged with last_wins:

dature validate \
    --schema myschema:Settings \
    --source 'type=dature.JsonSource,file=sources/defaults.json' \
    --source 'type=dature.JsonSource,file=sources/overrides.json' \
    --strategy last_wins
OK

When validation fails, the error includes the field path, the offending value, and the source location:

dature validate \
    --schema myschema:Settings \
    --source 'type=dature.JsonSource,file=sources/bad.json'
{"db": {"host": "localhost", "port": "not_a_number"}}
  | dature.errors.exceptions.DatureConfigError: Settings loading errors (1)
  +-+---------------- 1 ----------------
    | dature.errors.exceptions.FieldLoadError:   [db.port]  invalid literal for int() with base 10: 'not_a_number'
    |    ├── {"db": {"host": "localhost", "port": "not_a_number"}}
    |    │                                         ^^^^^^^^^^^^
    |    └── FILE 'sources/bad.json', line 1
    +------------------------------------

--source spec#

Each --source is a serialised constructor call for a Source subclass. The CLI parses the spec, imports the class, and calls Class(**kwargs):

--source type=<ImportPath>[,key=value][,key=value]...
  • type (required) — import path of a class derived from Source. Both dature.JsonSource and dature.sources.json_:JsonSource work.
  • Other keys map directly to the constructor's keyword arguments — same names, same semantics. The full set is documented under Source (base), FileSource (file-based), and FlatKeySource (env-style) plus per-class extras under Source Classes. Values are parsed as JSON first (e.g. skip_if_broken=trueTrue, port=80808080); strings that aren't valid JSON pass through unchanged.

--source is repeatable — order matters and is preserved (relevant for last_wins/first_wins strategies, see strategy).

Built-in source types#

type= Class Required kwargs
dature.JsonSource JSON file=
dature.Json5Source JSON5 (extra [json5]) file=
dature.Yaml11Source, dature.Yaml12Source YAML (extra [yaml]) file=
dature.Toml10Source, dature.Toml11Source TOML (extra [toml]) file=
dature.IniSource INI file=
dature.EnvSource OS environment
dature.EnvFileSource .env-style file file= (default .env)
dature.DockerSecretsSource /run/secrets-style dir dir_=

User-defined Source subclasses work too — pass the full import path.

Escaping commas and equals signs#

Use \, and \= to keep literal , / = inside a value:

"""Escaping commas inside ``--source`` values (``\\,``)."""

from dature import EnvSource
from dature.cli.parsing import parse_source_spec

klass, kwargs = parse_source_spec(r"type=dature.EnvSource,prefix=APP\,X_")

assert klass is EnvSource
assert kwargs == {"prefix": "APP,X_"}

--schema MODULE:ATTR#

Import path to the dataclass schema. Internally the CLI passes the resolved class to the schema parameter of dature.load(). Both : and final-dot separators work:

  • myapp.config:Settings — recommended, unambiguous
  • myapp.config.Settings — convenient when the class is exported at top level

Nested attributes after : are supported: myapp:Container.Settings.

Global flags#

These flags are generated from the signature of dature.load() at startup — same names (with _-), same types, same defaults. Adding a new parameter to load() adds the flag automatically; nothing in the CLI hard-codes the list.

CLI flag load() parameter Notes
--strategy strategy Choices: last_wins, first_wins, first_found, raise_on_conflict. See Merge Rules.
--skip-broken-sources skip_broken_sources See Skipping Broken Sources.
--skip-invalid-fields skip_invalid_fields See Skipping Invalid Fields.
--expand-env-vars expand_env_vars Choices: disabled, default, empty, strict. See ENV Expansion.
--secret-field-names secret_field_names Repeatable. See Masking.
--mask-secrets mask_secrets See Masking. Also affects inspect output.

Per-source overrides for parameters that exist on both load() and the Source constructor (e.g. expand_env_vars) can be passed inside --source, e.g. --source type=...,expand_env_vars=strict. The Source-level value takes priority — same precedence rules as in code.

Limitations#

The CLI exposes the common case. load() parameters that require Python objects — field_merges, field_groups, type_loaders, callable root_validators, explicit field_mapping — must be expressed in code. For those, use dature.load(...) directly. The CLI is intentionally not a substitute for the library.

Implementation note: dogfooding#

The dature console script parses its own arguments through ArgparseSource — the very class users instantiate to feed CLI arguments into their own configurations. The dataclass schema that the CLI loads into is built at runtime from the signature of load() (via dataclasses.make_dataclass over typing.get_type_hints(load)), so adding or changing a parameter on load() is automatically reflected in the CLI without any hand-written wiring. The CLI is not special code: it is dature using itself.