Subcommands

Subcommands let a single script expose multiple distinct operations — similar to git commit, git push, git log. fargv implements them via a nested dict whose values are themselves definitions.


Defining subcommands

Pass a dict whose values are all dicts (or callables / ArgumentParser instances). fargv detects this pattern and creates a FargvSubcommand parameter automatically.

import fargv

p, _ = fargv.parse({
    "verbose": False,
    "command": {
        "commit": {
            "message": ("", "Commit message"),
            "all":     (False, "Stage all tracked files"),
        },
        "push": {
            "remote": ("origin", "Remote name"),
            "force":  (False,    "Force push"),
        },
        "log": {
            "n":      (10,    "Number of commits to show"),
            "oneline": (False, "Compact one-line format"),
        },
    },
})

print(p.command)   # selected subcommand name, e.g. "commit"
print(p.message)   # subcommand parameter (flat mode, default)
print(p.verbose)   # parent parameter
python git_like.py commit --message="fix typo" --all
python git_like.py push --remote=upstream --force
python git_like.py log --n=20 --oneline

Per-subcommand help

Every subcommand automatically gets its own --help / -h flag:

python git_like.py commit --help
python git_like.py push -h

Return shapes

The subcommand_return_type argument controls how the selected subcommand’s parameters appear in the result.

"flat" (default)

The subcommand key holds the selected name as a string. All subcommand parameters are merged into the top-level namespace.

p, _ = fargv.parse(definition, subcommand_return_type="flat")
print(p.command)  # "commit"
print(p.message)  # "fix typo"
print(p.all)      # True

Best for: simple scripts where parameter names don’t conflict across subcommands.

"nested"

The subcommand key holds a SimpleNamespace(name=..., **params).

p, _ = fargv.parse(definition, subcommand_return_type="nested")
print(p.command.name)    # "commit"
print(p.command.message) # "fix typo"
print(p.verbose)         # parent param still at top level

Best for: scripts where subcommands have overlapping parameter names.

"tuple"

Returns a three-element tuple (name, sub_ns, parent_ns) instead of the usual (result, help_str) pair.

(name, sub_ns, parent_ns), help_str = fargv.parse(
    definition, subcommand_return_type="tuple"
)
print(name)           # "commit"
print(sub_ns.message) # "fix typo"
print(parent_ns.verbose)

Best for: dispatch tables — handlers[name](sub_ns, parent_ns).


Dispatch pattern

def do_commit(sub, parent):
    print(f"Committing: {sub.message!r}  all={sub.all}  verbose={parent.verbose}")

def do_push(sub, parent):
    print(f"Pushing to {sub.remote}  force={sub.force}")

def do_log(sub, parent):
    print(f"Showing {sub.n} commits  oneline={sub.oneline}")

handlers = {"commit": do_commit, "push": do_push, "log": do_log}

(name, sub_ns, parent_ns), _ = fargv.parse(
    definition, subcommand_return_type="tuple"
)
handlers[name](sub_ns, parent_ns)

Subcommands defined by callables

Any definition that is a callable (function, class) can be used as a subcommand value. fargv will introspect the signature:

def commit(message: str = "", all: bool = False):
    ...

def push(remote: str = "origin", force: bool = False):
    ...

p, _ = fargv.parse({
    "verbose": False,
    "command": {"commit": commit, "push": push},
})

Notes

  • Only one subcommand parameter is supported per parser.

  • The subcommand name is always a positional token — the first bare word on the command line that matches a known subcommand name.

  • Unknown subcommand names raise a FargvError.