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 |
|---|---|---|---|
|
|
|
top level |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|---|---|
|
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()