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
Example¶
There are a couple of ways to define subcommands, in this doc we'll focus on the most common
import arc
@arc.command()
def command():
print("hello there!")
@command.subcommand()
def sub1():
print("This is sub 1")
@command.subcommand()
def sub2():
print("This is sub 2")
command()
We can then execute each subcommand by referring to them by name.
$ python subcommand.py sub1
hello there!
This is sub 1
$ python subcommand.py sub2
hello there!
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
hello there!
USAGE
subcommand.py sub1 [-h]
OPTIONS
--help (-h) Displays this help message
$ python subcommand.py sub2 --help
hello there!
USAGE
subcommand.py sub2 [-h]
OPTIONS
--help (-h) Displays this help message
Nesting Subcommands¶
Subcommands can be arbitrarly nested
import arc
@arc.command()
def command():
print("hello there!")
@command.subcommand()
def sub1():
print("This is sub 1")
@sub1.subcommand()
def nested1():
print("This is nested 1")
@command.subcommand()
def sub2():
print("This is sub 2")
@sub2.subcommand()
def nested2():
print("This is nested 2")
command()
$ python nested_subcommands.py sub1 nested1
hello there!
This is nested 1
$ python nested_subcommands.py sub2 nested2
hello there!
This is nested 2
Global Parameters¶
When you define subcommands for you application, the root @arc.command()
object becomes a callback for global parameters. This is useful for defining things that can apply to the entirety of your applcation. For example, logging configuration.
import logging
import arc
@arc.command()
def command(*, verbose: bool):
logging.basicConfig(level=logging.DEBUG if verbose else logging.INFO)
@command.subcommand()
def sub():
logging.info("This is an info message")
logging.debug("This is a debug message")
command()
$ python global_params.py sub
INFO:root:This is an info message
$ python global_params.py --verbose sub
INFO:root:This is an info message
DEBUG:root:This is a debug message
Global arguments must come before the name of the command for them to be interpreted correctly. You can see this reflected in the --help
$ python global_params.py --help
USAGE
global_params.py [-h]
global_params.py [--verbose] <subcommand> [ARGUMENTS ...]
OPTIONS
--help (-h) Displays this help message
--verbose
SUBCOMMANDS
sub
Things to be aware of¶
- The root object can no longer define argument parameters, because arc doesn't have a way of knowing what is a positional parameter and what is the name of a command. Note: This may change in the future if I have time / care to change it.
- The root callback will be executed before every subcommand, regardless of whether or not you actually provide any global arguments. If this behavior isn't desired, it can be changed with
arc.configure(global_callback_execution="args_present")
Naming Subcommands¶
By default, commands are the kebab-case version of the function they decorate. This can be modified to change the name
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()
Subcommands in other Files¶
Breaking up your CLI interface into multiple files in arc is a very straightforward process.