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.

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

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