If you want to add hierarchy to your command line app then you might care to introduce groups of subcommands. This is also a great way to seperate concerns in your code.
Adding Subgroups in Typer
Let's split the code up into two files now. Here is the code in our main cli.py
file.
import typer
from typing import List
from pathlib import Path
from .db import app as db_app
app = typer.Typer(name="demo", add_completion=False, help="This is a demo app.")
# Here is where we attach subcommands as a group
app.add_typer(db_app, name="db")
@app.command()
def hello_world(name):
"""Say hello"""
print(f"hello {name}!")
@app.command()
def goodbye_world(name):
"""Say goodbye"""
print(f"goodbye {name}!")
@app.command()
def add(n1: int = typer.Argument(..., help="An integer"),
n2: int = typer.Argument(1, help="An integer")):
"""Add two numbers"""
print(n1 + n2)
def check_file_exists(paths):
for p in paths:
if not p.exists():
print(f"The path you've supplied {p} does not exist.")
raise typer.Exit(code=1)
return paths
@app.command()
def word_count(paths: List[Path] = typer.Argument(...,
help="The file to count the words in.",
callback=check_file_exists)):
"""Counts the number of words in a file"""
for p in paths:
texts = p.read_text().split("\n")
n_words = len(set(w for t in texts for w in t.split(" ")))
print(f"In total there are {n_words} words in {p}.")
@app.command()
def talk(text: str = typer.Argument(..., help="The text to type."),
repeat: int = typer.Option(1, help="Number of times to repeat."),
loud: bool = typer.Option(False, is_flag=True)):
"""Talks some text below"""
if loud:
text = text.upper()
for _ in range(repeat):
print(text)
if __name__ == "__main__":
app()
The code for the "database" commands is found here:
# db.py
import typer
from typing import List
from pathlib import Path
app = typer.Typer(name="demo", add_completion=False, help="This is a demo app.")
@app.command()
def create_db(table: str = typer.Option(..., prompt="What is the name of the table?",
confirmation_prompt=True)):
"""Pretend to make a database"""
console.print(f"Creating table in database {table}", style="green")
@app.command()
def delete_db(table: str = typer.Option(..., prompt="What is the name of the table?",
confirmation_prompt=True)):
"""Pretend to delete a database"""
sure = typer.confirm("Are you really really really sure?")
if sure:
console.print(f"Deleting table in database {table}", style="red")
else:
console.print(f"Back to safety!", style="green")
You should now notice that when you run the command line, that there's a subgroup now.
> python cli.py
Usage: cli.py [OPTIONS] COMMAND [ARGS]...
This is a demo app.
Options:
--help Show this message and exit.
Commands:
add Add two numbers
db Database commands.
goodbye-world Say goodbye
hello-world Say hello
talk Talks some text below
word-count Counts the number of words in a file
That means that we're able to add hierarchy in both our command line app. But also in our code.