Skip to content

Subcommands

In all of the examples so far, we've had a single command, with a single set of parameters. This works fine for smaller applications, but once a tool starts to grow in scope, it is useful to start breaking up it's functionality into descrete units using subcommands.

What is a Subcommand?

A subcommand is an arc command object that lives underneath another command object. Each subcommand will have it's own callback function and it's own set of parameters. When ran from the commandline it will look a little something like this

$ python example.py <subcommand> <options>

Example

There are a couple of ways to define subcommands, in this doc we'll focus on the most common

examples/subcommand.py
import arc


@arc.command
def command():
    ...


@command.subcommand
def sub1():
    arc.print("This is sub 1")


@command.subcommand
def sub2():
    arc.print("This is sub 2")


command()

We can then execute each subcommand by referring to them by name.

$ python subcommand.py sub1
This is sub 1
$ python subcommand.py sub2
This is sub 2

Documentation

Subcommands also get their own --help

$ python subcommand.py --help
USAGE
    subcommand.py [-h]
    subcommand.py <subcommand> [ARGUMENTS ...]

OPTIONS
    --help (-h)  Displays this help message

SUBCOMMANDS
    sub1
    sub2

$ python subcommand.py sub1 --help
USAGE
    subcommand.py sub1 [-h]

OPTIONS
    --help (-h)  Displays this help message

$ python subcommand.py sub2 --help
USAGE
    subcommand.py sub2 [-h]

OPTIONS
    --help (-h)  Displays this help message
They're all pretty bare-bones right now, but they will fill out as the application grows.

Nesting Subcommands

Subcommands can be arbitrarly nested

examples/nested_subcommands.py
import arc


@arc.command
def command():
    ...


@command.subcommand
def sub1():
    arc.print("This is sub 1")


@sub1.subcommand
def nested1():
    arc.print("This is nested 1")


@command.subcommand
def sub2():
    arc.print("This is sub 2")


@sub2.subcommand
def nested2():
    arc.print("This is nested 2")


command()

$ python nested_subcommands.py sub1 nested1
This is nested 1
$ python nested_subcommands.py sub2 nested2
This is nested 2

Root Command

It is important to note that when using subcommands, the root command is still executable. One must take care that subcommand names may come into collision with arguments for the root or command (or any parent command, really). If you do not want a parent command to be executable, you may use this pattern:

examples/namespace.py
import arc

ns = arc.namespace("ns")


@ns.subcommand
def sub():
    arc.print("Namespace Example")


ns()
$ python namespace.py 
USAGE
    ns [-h]
    ns <subcommand> [ARGUMENTS ...]

ns  --help for more information
$ python namespace.py sub
Namespace Example

Namespace commands do not take any arguments (besides --help), and when invoked, will print out the usage for the command, and exit with an error code.

Naming Subcommands

By default, commands are the kebab-case version of the function they decorate. You can provide an explicit name for the command:

import arc

@arc.command
def command():
    ...

@command.subcommand("some-other-name")
def sub():
    ...

command()

or provide multiple names, for a command:

import arc

@arc.command
def command():
    ...

@command.subcommand("some-other-name", "another-name", "a-third-name")
def sub():
    ...

command()

Note that when you provide multiple names, the first in the list of names will be considered the "canonical" name while the others will be considered aliases

Subcommands in other Files

Breaking up your CLI interface into multiple files in arc is a very straightforward process.

import arc

@arc.command
def sub():
    print("This is the subcommand")

# Notice, no call to sub()
import arc
from subcommand import sub


@arc.command
def cli():
    print('hello there!')

# Here we add sub as a subcommand to cli
cli.subcommand(sub) # Could add any number of aliases here if we wanted

cli()