When writing a command line tool in Python using Typer one can create a CLI with commands and even subcommands. If you define a CLI with only one command the CLI will be optimized such that you do not have to provide the command - let's call this modul cli_a.py
:
#!env python
import typer
app = typer.Typer()
@app.command()
def main():
print('This is the output of main')
if __name__ == '__main__':
app()
Now, you can call this CLI like so
$ ./cli_a.py --help
Usage: cli_a.py [OPTIONS]
╭─ Options ───────────────────────────────────────────────────────────────────╮
│ --install-completion Install completion for the current shell. │
│ --show-completion Show completion for the current shell, to │
│ copy it or customize the installation. │
│ --help Show this message and exit. │
╰─────────────────────────────────────────────────────────────────────────────╯
and
$ ./cli_a.py
This is the output of main
Notice, that there has to be no command called main
!
On the other hand you can have a CLI with multiple commands - let's call this cli_b.py
:
#!env python
import typer
app = typer.Typer()
@app.command()
def cmd1():
print('This is the output of cmd1')
@app.command()
def cmd2():
print('This is the output of cmd2')
if __name__ == '__main__':
app()
With the following output:
$ ./cli_b.py --help
Usage: cli_b.py [OPTIONS] COMMAND [ARGS]...
╭─ Options ───────────────────────────────────────────────────────────────────╮
│ --install-completion Install completion for the current shell. │
│ --show-completion Show completion for the current shell, to │
│ copy it or customize the installation. │
│ --help Show this message and exit. │
╰─────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ──────────────────────────────────────────────────────────────────╮
│ cmd1 │
│ cmd2 │
╰─────────────────────────────────────────────────────────────────────────────╯
In this case you have to provide the command to be called:
$ ./cli_b.py cmd1
This is the output of cmd1
I want to combine these two CLI into one - let's call this super_cli.py
:
#!env python
import typer
import cli_a
import cli_b
app = typer.Typer()
app.add_typer(cli_a.app, name='cli_a')
app.add_typer(cli_b.app, name='cli_b')
if __name__ == '__main__':
app()
This behaves as expected for cli_b:
$ ./super_cli.py cli_b cmd1
This is the output of cmd1
But requires an unwanted additional command main
on cli_a:
$ ./super_cli.py cli_a main
This is the output of main
How can it be achieved that cli_a
is callable without specifying the additional command main
?
I was expecting to get
$ ./super_cli.py cli_a
This is the output of main
but I do get
$ ./super_cli.py cli_a
Usage: super_cli.py cli_a [OPTIONS] COMMAND [ARGS]...
Try 'super_cli.py cli_a --help' for help.
╭─ Error ─────────────────────────────────────────────────────────────────────╮
│ Missing command. │
╰─────────────────────────────────────────────────────────────────────────────╯
instead.
Searching typer default command
in Google I found:
Set the default command in Python Typer CLI
You need to change command()
into callback(invoke_without_command=True)
in cli_a.py
and it will treat main()
as default command.
import typer
app = typer.Typer()
@app.callback(invoke_without_command=True)
def main():
print('This is the output of main')
if __name__ == '__main__':
app()
And now ./super_cli cli_a
runs cli_a.main()
Executing directly ./cli_a.py
still work as before.
I used callback()
on cmd1
in cli_b
import typer
app = typer.Typer()
@app.callback(invoke_without_command=True)
def cmd1():
print('This is the output of cmd1')
@app.command()
def cmd2():
print('This is the output of cmd2')
if __name__ == '__main__':
app()
and it was running cmd1
automatically for super_cli.py cli_b
BUT it was running it autmatically also for super_cli.py cli_b cmd2
:)
$ ./super_cli.py cli_b
This is the output of cmd1
$ ./super_cli.py cli_b cmd2
This is the output of cmd1
This is the output of cmd2
Some links to documentation: