Config File Syntax

Exact on-disk syntax for the config files argclass reads — how groups map to sections, which boolean literals are accepted, and how CLI arguments override file values. For how to load config files see Configuration Files; for the priority model see The configuration model.

Group Sections

Groups map to INI sections:

import argclass
from pathlib import Path
from tempfile import NamedTemporaryFile

class ServerGroup(argclass.Group):
    host: str = "localhost"
    port: int = 8080

class DatabaseGroup(argclass.Group):
    host: str = "localhost"
    port: int = 5432

class Parser(argclass.Parser):
    verbose: bool = False
    server = ServerGroup()
    database = DatabaseGroup()

# Config file content
CONFIG_CONTENT = """
[DEFAULT]
verbose = true

[server]
host = 0.0.0.0
port = 9000

[database]
host = db.example.com
port = 3306
"""

with NamedTemporaryFile(mode="w", suffix=".ini", delete=False) as f:
    f.write(CONFIG_CONTENT)
    config_path = f.name

parser = Parser(config_files=[config_path])
parser.parse_args([])

assert parser.verbose is True
assert parser.server.host == "0.0.0.0"
assert parser.server.port == 9000
assert parser.database.host == "db.example.com"
assert parser.database.port == 3306

Path(config_path).unlink()

Nested Groups in Config Files

A group inside a group becomes a dotted INI section, a nested JSON/TOML table, or a child object — depending on the format.

INI uses the dotted section name verbatim:

import argclass
from pathlib import Path
from tempfile import NamedTemporaryFile

class Credentials(argclass.Group):
    username: str = "admin"
    password: str = "secret"

class Endpoint(argclass.Group):
    host: str = "localhost"
    credentials: Credentials = Credentials()

class Parser(argclass.Parser):
    endpoint: Endpoint = Endpoint()

CONFIG = """
[endpoint]
host = api.example.com

[endpoint.credentials]
username = root
password = hunter2
"""

with NamedTemporaryFile(mode="w", suffix=".ini", delete=False) as f:
    f.write(CONFIG)
    config_path = f.name

parser = Parser(config_files=[config_path])
parser.parse_args([])

assert parser.endpoint.host == "api.example.com"
assert parser.endpoint.credentials.username == "root"
assert parser.endpoint.credentials.password == "hunter2"

Path(config_path).unlink()

JSON uses natural nesting — each group level is a nested object:

{
  "endpoint": {
    "host": "api.example.com",
    "credentials": {
      "username": "root",
      "password": "hunter2"
    }
  }
}

TOML uses dotted table headers, just like INI:

[endpoint]
host = "api.example.com"

[endpoint.credentials]
username = "root"
password = "hunter2"

Note

The section name in config files always follows the attribute path, even when a group has prefix= set. prefix= only renames the CLI/env segment for that group.

Subparser Sections

A subparser is addressed the same way as a group: its section is the attribute path from the root parser. A group inside a subparser, or a subparser inside a subparser, extends that path with a dot. The section name never depends on the subcommand name typed on the command line; it depends only on the attribute the subparser is bound to.

import argclass

class Database(argclass.Group):
    host: str = "localhost"
    port: int = 5432

class Worker(argclass.Parser):
    threads: int = 4

class Serve(argclass.Parser):
    port: int = 8080
    db = Database()
    worker = Worker()

class Deploy(argclass.Parser):
    target: str = "production"

class CLI(argclass.Parser):
    debug: bool = False
    serve = Serve()
    deploy = Deploy()

Attribute path

INI section

JSON path

TOML table

debug

[DEFAULT]

debug

top level

serve.port

[serve]

serve.port

[serve]

serve.db.host

[serve.db]

serve.db.host

[serve.db]

serve.worker.threads

[serve.worker]

serve.worker.threads

[serve.worker]

deploy.target

[deploy]

deploy.target

[deploy]

One file therefore describes the root parser and every subcommand. The file is read through config_files= or through the config_argument flag exactly like a file without subcommands:

import argclass
from pathlib import Path
from tempfile import NamedTemporaryFile

class Database(argclass.Group):
    host: str = "localhost"
    port: int = 5432

class Worker(argclass.Parser):
    threads: int = 4

class Serve(argclass.Parser):
    port: int = 8080
    db = Database()
    worker = Worker()

class Deploy(argclass.Parser):
    target: str = "production"

class CLI(argclass.Parser):
    debug: bool = False
    serve = Serve()
    deploy = Deploy()

CONFIG = """
[DEFAULT]
debug = true

[serve]
port = 9000

[serve.db]
host = db.example.com

[serve.worker]
threads = 8

[deploy]
target = staging
"""

with NamedTemporaryFile(mode="w", suffix=".ini", delete=False) as f:
    f.write(CONFIG)
    config_path = f.name

cli = CLI(config_files=[config_path])
cli.parse_args(["serve", "worker"])
assert cli.debug is True
assert cli.serve.port == 9000
assert cli.serve.db.host == "db.example.com"
assert cli.serve.db.port == 5432
assert cli.serve.worker.threads == 8

cli = CLI(config_argument="--config")
cli.parse_args(["--config", config_path, "deploy"])
assert cli.deploy.target == "staging"

Path(config_path).unlink()
{
  "debug": true,
  "serve": {
    "port": 9000,
    "db": {"host": "db.example.com"},
    "worker": {"threads": 8}
  },
  "deploy": {"target": "staging"}
}

A section for a subcommand that is not selected on the command line is read but has no visible effect: only the selected subparser chain is populated after parse_args().

Priority inside a subcommand, highest first: CLI arguments, env vars, the config_argument file, the parent’s config_files, the subparser’s own config_files (when the subparser instance was created with some), declared defaults.

Note

An attribute name is bound to exactly one of: an argument, a group, or a subparser. Section names therefore cannot collide between a group and a subparser of the same parser.

Boolean Values

True values

False values

true, yes, on, 1, enable, enabled, t, y

Any other value

Note

For INI files, boolean conversion is case-insensitive (TRUE, True, true all work). JSON and TOML use native boolean types (true/false).

import argclass
from pathlib import Path
from tempfile import NamedTemporaryFile

class Parser(argclass.Parser):
    flag1: bool = False
    flag2: bool = False
    flag3: bool = True
    flag4: bool = True

# Config file content
CONFIG_CONTENT = """
[DEFAULT]
flag1 = yes
flag2 = 1
flag3 = no
flag4 = off
"""

with NamedTemporaryFile(mode="w", suffix=".ini", delete=False) as f:
    f.write(CONFIG_CONTENT)
    config_path = f.name

parser = Parser(config_files=[config_path])
parser.parse_args([])

assert parser.flag1 is True
assert parser.flag2 is True
assert parser.flag3 is False
assert parser.flag4 is False

Path(config_path).unlink()

CLI Override

Command-line arguments always override config file values:

import argclass
from pathlib import Path
from tempfile import NamedTemporaryFile

class Parser(argclass.Parser):
    host: str = "localhost"
    port: int = 8080

# Config file content
CONFIG_CONTENT = """
[DEFAULT]
host = config.example.com
port = 9000
"""

with NamedTemporaryFile(mode="w", suffix=".ini", delete=False) as f:
    f.write(CONFIG_CONTENT)
    config_path = f.name

parser = Parser(config_files=[config_path])
parser.parse_args(["--port", "3000"])

assert parser.host == "config.example.com"  # From config
assert parser.port == 3000  # From CLI (override)

Path(config_path).unlink()