Argument Groups¶
Groups organize related arguments together and enable reuse across parsers.
They provide logical structure to your CLI, make --help output more readable,
and allow you to define common argument sets once and reuse them in multiple parsers.
What a group is¶
A Group() assigned in a parser class body is a declaration — it names a
slot in the parsed result (parser.db) and the arguments that belong to it. It
is not a runtime parser: you never call parse_args() on a group. At parse time
argclass writes the parsed values back into the instance so you read them as
parser.db.host.
See also
Why groups are declarations rather than parsers, how class-body instances become per-parser copies, and when to reach for a group vs. a subparser: Parsers, Groups & Subparsers.
Basic Groups¶
Create a group by inheriting from argclass.Group. When you add a group to a
parser, its arguments are prefixed with the attribute name. Here, database
becomes the prefix, so arguments become --database-host, --database-port, etc.
import argclass
class DatabaseGroup(argclass.Group):
host: str = "localhost"
port: int = 5432
user: str = "admin"
class Parser(argclass.Parser):
verbose: bool = False
database = DatabaseGroup()
parser = Parser()
parser.parse_args(["--database-host", "db.example.com", "--database-port", "3306"])
assert parser.verbose is False
assert parser.database.host == "db.example.com"
assert parser.database.port == 3306
assert parser.database.user == "admin"
Group Titles¶
Add a descriptive title that appears in --help output. This makes the help
more readable by clearly labeling each section of related arguments.
import argclass
class DatabaseGroup(argclass.Group):
host: str = "localhost"
port: int = 5432
class Parser(argclass.Parser):
database = DatabaseGroup(title="Database connection")
parser = Parser()
parser.parse_args(["--database-host", "db.example.com"])
assert parser.database.host == "db.example.com"
assert parser.database.port == 5432
Custom Prefixes¶
Override the default prefix with prefix=. Use an empty string to add
arguments without any prefix. This is useful when you want short argument
names or when the group represents the main configuration.
import argclass
class ConnectionGroup(argclass.Group):
host: str = "localhost"
port: int = 8080
class Parser(argclass.Parser):
# Custom prefix: --api-host, --api-port
api = ConnectionGroup(prefix="api")
# No prefix: --host, --port
server = ConnectionGroup(prefix="")
parser = Parser()
parser.parse_args([
"--api-host", "api.example.com",
"--api-port", "9000",
"--host", "server.example.com",
"--port", "3000"
])
assert parser.api.host == "api.example.com"
assert parser.api.port == 9000
assert parser.server.host == "server.example.com"
assert parser.server.port == 3000
Reusing Groups¶
The same group class can be instantiated multiple times with different
settings. Use defaults= to override default values for each instance.
This avoids duplicating group definitions for similar configurations.
import argclass
class HostPort(argclass.Group):
host: str = "localhost"
port: int
class Parser(argclass.Parser):
api = HostPort(title="API Server", defaults={"port": 8080})
metrics = HostPort(title="Metrics Server", defaults={"port": 9090})
database = HostPort(title="Database", defaults={"port": 5432})
parser = Parser()
parser.parse_args([
"--api-host", "0.0.0.0",
"--metrics-port", "9999"
])
assert parser.api.host == "0.0.0.0"
assert parser.api.port == 8080
assert parser.metrics.host == "localhost"
assert parser.metrics.port == 9999
assert parser.database.port == 5432
Group Defaults¶
Use defaults= to provide instance-specific default values. This is useful
for deployment presets like production vs development configurations, where
the same group structure needs different default values.
import argclass
class ServerGroup(argclass.Group):
host: str = "localhost"
port: int = 8080
ssl: bool = False
class Parser(argclass.Parser):
prod = ServerGroup(defaults={
"host": "0.0.0.0",
"port": 443,
"ssl": True,
})
parser = Parser()
parser.parse_args([])
assert parser.prod.host == "0.0.0.0"
assert parser.prod.port == 443
assert parser.prod.ssl is True
Inheriting from Groups¶
Parsers can inherit from groups as mixins to include arguments directly at the top level (without a prefix). This is useful for common arguments like logging or verbosity that you want available in multiple parsers.
import argclass
class LoggingMixin(argclass.Group):
log_level: str = "info"
log_file: str | None = None
class VerboseMixin(argclass.Group):
verbose: bool = False
quiet: bool = False
class Parser(argclass.Parser, LoggingMixin, VerboseMixin):
name: str
parser = Parser()
parser.parse_args(["--name", "test", "--log-level", "debug", "--verbose"])
assert parser.name == "test"
assert parser.log_level == "debug"
assert parser.verbose is True
assert parser.quiet is False
Accessing Group Values¶
After parsing, access group values through the group attribute. Groups behave like regular Python objects - use dot notation to read the parsed values.
import argclass
class DatabaseGroup(argclass.Group):
host: str = "localhost"
port: int = 5432
class Parser(argclass.Parser):
database = DatabaseGroup()
parser = Parser()
parser.parse_args(["--database-host", "db.example.com"])
# Access via group
assert parser.database.host == "db.example.com"
assert parser.database.port == 5432
Nested Groups¶
Groups can contain other Groups as fields. This works for any depth and
keeps Parser → Group → Group → ... cleanly modelled in code. Names are
built by joining the attribute path with the appropriate separator for
each source:
Source |
Separator |
Example |
|---|---|---|
CLI |
|
|
ENV |
|
|
INI |
|
|
JSON |
nested |
|
TOML |
|
|
import argclass
class Credentials(argclass.Group):
username: str = "admin"
password: str = "secret"
class Endpoint(argclass.Group):
host: str = "localhost"
port: int = 8080
credentials: Credentials = Credentials()
class Parser(argclass.Parser):
endpoint: Endpoint = Endpoint()
parser = Parser()
parser.parse_args([
"--endpoint-host", "api.example.com",
"--endpoint-credentials-username", "root",
"--endpoint-credentials-password", "hunter2",
])
assert parser.endpoint.host == "api.example.com"
assert parser.endpoint.credentials.username == "root"
assert parser.endpoint.credentials.password == "hunter2"
Nested groups appear as separate sections in --help, titled with their
dotted attribute path (e.g. endpoint.credentials). Set title= on a
group to override the default title for that one level.
Nested groups in config files¶
INI sections use a dotted section name; JSON/TOML use natural nesting.
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()
prefix= and nested groups¶
Group(prefix=...) overrides only the CLI/ENV segment for that group. It
does not affect the INI/TOML section name — config sections always
follow the attribute path. This keeps section names predictable and
prevents CLI prefixes from silently desyncing from config layout.
Group fields with type annotations¶
A group attribute can be declared with a type annotation. When the
annotation refers to a Group subclass, argclass enforces these rules
at class definition time:
Form |
Behaviour |
|---|---|
|
Auto-instantiated as |
|
Uses the provided instance |
|
Auto-instantiated as |
|
Uses the provided instance |
|
Rejected (Group can’t be None) |
|
Rejected (Group can’t be None) |
|
Rejected |
|
Rejected |
import argclass
class DatabaseGroup(argclass.Group):
host: str = "localhost"
port: int = 5432
class Parser(argclass.Parser):
# No explicit default — argclass instantiates DatabaseGroup() for you
database: DatabaseGroup
parser = Parser()
parser.parse_args(["--database-host", "db.example.com"])
assert parser.database.host == "db.example.com"
assert parser.database.port == 5432
Rejected forms raise ArgumentDefinitionError immediately when the
parser class is defined, with a hint suggesting the correct form.
Reusing a group instance¶
Group instances in a class body are prototypes — every parser
instance works on its own copies, so the same Group instance may be
bound to several attributes and each binding keeps independent parsed
state. Separate instances per attribute remain the clearest style
(and are required when the attributes need different title/prefix
options):
import argclass
class Credentials(argclass.Group):
username: str = "admin"
class Auth(argclass.Group):
primary: Credentials = Credentials() # separate instance
secondary: Credentials = Credentials() # separate instance
class Parser(argclass.Parser):
auth: Auth = Auth()
parser = Parser()
parser.parse_args([
"--auth-primary-username", "alice",
"--auth-secondary-username", "bob",
])
assert parser.auth.primary.username == "alice"
assert parser.auth.secondary.username == "bob"
Groups in Config Files¶
Groups map to INI sections. The section name matches the group attribute name.
Top-level parser arguments go in [DEFAULT], while each group gets its own
section named after the attribute.
import argclass
from pathlib import Path
from tempfile import NamedTemporaryFile
class ConnectionGroup(argclass.Group):
host: str = "localhost"
port: int = 8080
class Parser(argclass.Parser):
verbose: bool = False
database = ConnectionGroup()
cache = ConnectionGroup()
CONFIG = """
[DEFAULT]
verbose = true
[database]
host = db.example.com
port = 5432
[cache]
host = redis.example.com
port = 6379
"""
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.verbose is True
assert parser.database.host == "db.example.com"
assert parser.database.port == 5432
assert parser.cache.host == "redis.example.com"
assert parser.cache.port == 6379
Path(config_path).unlink()