You should read the Typer Tutorial - User Guide before referring to this summary. Information icons ℹ️ link to the relevant sections of the Typer Tutorial.
- Documentation: https://typer.tiangolo.com
- Source Code: https://github.com/fastapi/typer
Installing ℹ️
$ python3 -m pip install typer # or typer-slim, to omit rich and shellinghamSee also Building a Package for a complete tutorial on making a standalone CLI app with Typer.
Single app ℹ️
import typer
def main():
"""Help text for the app."""
typer.run(main)Exiting ℹ️
raise typer.Exit # exit successfully (code=0)
raise typer.Abort # prints "Aborted." and exits with code 1
raise typer.Exit(code=2) # specify a custom exit codeNote: If there is an EOFError or KeyboardInterrupt inside your app, it is reraised as Abort automatically, so you do not need to handle that yourself.
Subcommands ℹ️
import typer
app = typer.Typer()
state = {"verbose": False}
@app.command()
def enable():
"""Help text for subcommand."""
@app.command()
def disable():
...
@app.callback()
def main(verbose: bool = False):
"""Help text for main app."""
state["verbose"] = verbose
if __name__ = "__main__":
app()Note:
- A Typer app
callback()could define CLI parameters and help text for the main CLI application itself.- It could contain code, or only help text, or both.
- If there is only a single
command(), Typer will automatically use it as the main CLI application.- If you do want an app with only a single command, add an app
callback(). ℹ️
- If you do want an app with only a single command, add an app
Typer() arguments:
- help -- provide help text for the app
- rich_markup_mode -- If set to "rich", enable Rich console markup in all help. Can also be set to "markdown", which includes 💥 emoji codes. (default: None) ℹ️
- callback -- provide a callback function here, instead of using
@app.callback()decorator. add_completion=False-- disable the Typer completion system, and remove the options from the help text.pretty_exceptions_enable=False-- turn off improved or Rich exceptions. ℹ️- You could also achieve the same with the environment variable
_TYPER_STANDARD_TRACEBACK=1.
- You could also achieve the same with the environment variable
pretty_exceptions_show_locals=False-- disable Rich showing local variables, maybe for security reasons.pretty_exceptions_short=False-- include Click and Typer in tracebacks.no_args_is_help=True-- add--helpas argument if no arguments are passed.- context_settings -- extra Click Context settings.
- E.g.
Typer(context_settings={"help_option_names": ["-h", "--help"]}).
- E.g.
command() arguments:
- "custom-name" -- as optional first argument, choose a different name. ℹ️
- By default the command name is the same as the function name, except any underscores in the function name will be replaced with dashes.
- help -- provide help text
- rich_help_panel -- display subcommand in a separate Rich panel ℹ️
- epilog -- provide an epilog section to the help of your command ℹ️
no_args_is_help=True-- add--helpas argument if no arguments are passed.- Unfortunately, this is not inherited from the Typer app, so you have to specify it for each command.
- context_settings -- extra Click Context settings.
app() arguments:
- prog_name -- manually set the program name to be displayed in help texts,
e.g. when you run your app through
python -m. ℹ️
Callback function parameters:
ctx: typer.Context--ctx.invoked_subcommandcontains the name of the app subcommand.- invoke_without_command -- if True, run the app callback when no command is provided. Default is False, which just prints CLI help and exits.
@app.callback(invoke_without_command=True)
def main(ctx: typer.Context):
if ctx.invoked_subcommand is None:
# This code only runs when no subcommand is provided.
print("Initializing database")Command Groups ℹ️
Subcommands can have their own subcommands, which could be defined in another file:
import items
import users
app = typer.Typer()
app.add_typer(users.app, name="users")
app.add_typer(items.app, name="items")add_typer() arguments:
- name -- set the subcommand name
- callback -- function for callbacks and help docstring
- help -- help text if not defined in a callback docstring
Inferring Name and Help Text ℹ️
The precedence to generate a command's name and help, from lowest priority to highest, is:
- Implicitly inferred from
sub_app = typer.Typer(callback=some_function) - Implicitly inferred from the callback function under
@sub_app.callback() - Implicitly inferred from
app.add_typer(sub_app, callback=some_function) - Explicitly set on
sub_app = typer.Typer(name="some-name", help="Some help.") - Explicitly set on
@sub_app.callback("some-name", help="Some help.") - Explicitly set on
app.add_typer(sub_app, name="some-name", help="Some help.")
CLI Arguments ℹ️
from typing_extensions import Annotated
from typer import Argument
# without a help text, only type hints are needed
def main(name: str): ...
def main(name: Optional[str] = "Bob"): ...
# to specify help, or other options, you need to use Annotated[]
def main(name: Annotated[str, Argument(help="Name is required")]): ...
def main(name: Annotated[Optional[str], Argument()] = None): ...
def main(name: Annotated[str, Argument(help="Name is optional")] = "Bob"): ...
def main(names: Annotated[List[str], Argument(default_factory=list)]):
# old syntax, without type hints
def main(name: str = Argument(default=...)): ...Argument() arguments:
- help -- provide help text
- metavar -- argument value's name in help text
- hidden -- hide argument from main help text (will still show up in the first line with Usage )
- rich_help_panel -- place in separate box in summary
- show_default -- show default value (bool, or custom str) (default: True) ℹ️
- envvar -- default to use value from environment variable ℹ️
- show_envvar -- hide envvar information from help summary
CLI Options ℹ️
from typing_extensions import Annotated
from typer import Option
def main(name: Annotated[str, Option(help="Required name")]): ...
def main(name: Annotated[str, Option(help="Optional name")] = "Bob"): ...
def main(name: Annotated[str, Option("--custom-name", "-n")]): ...
# old syntax
def main(name: str = Option()): ...
def main(name: str = Option("Bob")): ...Notes:
- To make a CLI option required, rather than optional, you can put
typer.Option()inside ofAnnotatedand leave the parameter without a default value. ℹ️ - Bool options get automatic
"--verbose/--no-verbose"names. You could override this with a custom name. ℹ️
Option() arguments:
"--custom-name", "-n"-- provide custom long and short option names ℹ️- help -- help text
- rich_help_panel -- place in separate box in summary
- show_default -- show default value (bool, or custom str) (default: True) ℹ️
- prompt -- ask user interactively for missing value, instead of showing an error ℹ️
- confirmation_prompt -- repeat a prompt for confirmation ℹ️
- hide_input -- for typing passwords; could be combined with confirmation
- autocompletion -- provide a callable that takes a str and returns a list of alternatives (str only); see below.
- callback -- custom logic, e.g. validation; see below
- is_eager -- process this callback before any other callbacks; good for
--version, see below ℹ️
CLI Option Callbacks ℹ️
Does custom logic, e.g. validation, and can print version number:
__version__ = "0.1.0"
def version_callback(value: bool):
if value:
print(f"Awesome CLI Version: {__version__}")
raise typer.Exit
def name_callback(value: str):
if value != "Camila":
raise typer.BadParameter("Only Camila is allowed")
return value
def main(
name: Annotated[str, typer.Option(callback=name_callback)],
version: Annotated[
Optional[bool],
typer.Option("--version", callback=version_callback, is_eager=True),
] = None,
):
print(f"Hello {name}")Callback function parameters:
- ctx: typer.Context -- access the current context
- param: typer.CallbackParam -- for access to param.name
CLI Option Autocompletion ℹ️
The autocompletion function can return an iterable of str items, or of tuples ("item", "help text"). It can have function parameters of these types:
str-- for the incomplete value.ctx: typer.Context-- for the current context.- ctx.resilient_parsing is True when handling completion ℹ️, so do not try to validate anything or print to stdout.
List[str]-- for the raw CLI parameters.
valid_completion_items = [
("Camila", "The reader of books."),
("Carlos", "The writer of scripts."),
("Sebastian", "The type hints guy."),
]
def complete_name(ctx: typer.Context, incomplete: str):
names = ctx.params.get("name") or []
for name, help_text in valid_completion_items:
if name.startswith(incomplete) and name not in names:
yield (name, help_text)
@app.command()
def main(
name: Annotated[
List[str],
typer.Option(help="The name to say hi to.", autocompletion=complete_name),
] = ["World"],
):
for n in name:
print(f"Hello {n}")Data Validation ℹ️
When you declare a CLI parameter (Argument or Option) with some type Typer will convert the data received in the command line to that data type.
- Numbers (int, float) ℹ️
- Can specify
maxandminfor validation. - With
clamp=True, auto restrict to max/min instead of erroring. count=truewill act as a counter, e.g.:
def main(verbose: Annotated[int, typer.Option("-v", count=True)] = 0): ...
- Can specify
- Boolean CLI options ℹ️
- By default Typer creates
--somethingand--no-somethingautomatically.- To avoid this, specify the name(s) for
typer.Option()
def main(accept: Annotated[bool, typer.Option("--accept/--reject", "-a/-r")] = False): ...
- To avoid this, specify the name(s) for
- By default Typer creates
- UUID ℹ️
- DateTime ℹ️
formats-- how to parse. (default:["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S"])
- Enum - Choices ℹ️
- You can make an Enum (choice) CLI parameter be case-insensitive with
case_sensitive=False. - For Python 3.11 or newer, you should use StrEnum, preferably with
auto()values.
- You can make an Enum (choice) CLI parameter be case-insensitive with
- Path ℹ️
exists-- if set to true, the file or directory needs to exist for this value to be valid. If this is not required and a file does indeed not exist, then all further checks are silently skipped.file_okay-- controls if a file is a possible value.dir_okay-- controls if a directory is a possible value.writable-- if True, a writable check is performed.readable-- if True, a readable check is performed.resolve_path-- if this is True, then the path is fully resolved before the value is passed onwards. This means that its absolute and symlinks are resolved.allow_dash-- if True, a single dash to indicate standard streams is permitted.
- File ℹ️
- You can use several configuration parameters for these types (classes) in
typer.Option()andtyper.Argument():mode: controls the "mode" to open the file with.encoding: to force a specific encoding, e.g. "utf-8".lazy: delay I/O operations. By default, it's lazy=True for writing and lazy=False for reading.atomic: if true, all writes will go to a temporary file and then moved to the final destination after completing.
- By default, Typer will configure the mode for you:
typer.FileText: mode="r", to read text.typer.FileTextWrite: mode="w", to write text.typer.FileBinaryRead: mode="rb", to read binary data.typer.FileBinaryWrite: mode="wb", to write binary data.
- You can use several configuration parameters for these types (classes) in
Custom Types ℹ️
You can have custom parameter types with a parser callable (function or class). If your conversion fails, raise a ValueError.
class CustomClass:
def __init__(self, value: str):
self.value = value
def __str__(self):
return f"<CustomClass: value={self.value}>"
def parse_custom_class(value: str):
return CustomClass(value * 2)
def main(
custom_arg: Annotated[CustomClass, typer.Argument(parser=parse_custom_class)],
custom_opt: Annotated[CustomClass, typer.Option(parser=parse_custom_class)] = "Foo",
):
print(f"custom_arg is {custom_arg}")
print(f"--custom-opt is {custom_opt}")Use a typing.List to declare a parameter as a list of any number of values, for both Options: ℹ️
def main(number: Annotated[List[float], typer.Option()] = []):
print(f"The sum is {sum(number)}")...And Arguments: ℹ️
def main(files: List[Path], celebration: str):
for path in files:
if path.is_file():
print(f"This file exists: {path.name}")
print(celebration)Use a typing.Tuple to get a fixed number of values, possibly of different types, for Options: ℹ️
def main(user: Annotated[Tuple[str, int, bool], typer.Option()] = (None, None, None)):
username, coins, is_wizard = user...Or Arguments(): ℹ️
def main(
names: Annotated[
Tuple[str, str, str], typer.Argument(help="Select 3 characters to play with")
] = ("Harry", "Hermione", "Ron")
):
for name in names:
print(f"Hello {name}")Ask with Prompt ℹ️
Prompt and confirm (it is suggested that you prefer to use the CLI Options):
person_name = typer.prompt("What's your name?")
# What's your name?:
delete = typer.confirm("Are you sure you want to delete it?", abort=True)
# Are you sure you want to delete it? [y/N]: n
# Aborted!User App Dir ℹ️
app_dir = typer.get_app_dir(APP_NAME)
config_path = Path(app_dir) / "config.json"roaming-- controls if the folder should be roaming or not on Windows (default: True)force_posix-- store dotfile directly in $HOME, instead of XDG or macOS default locations (default: False)
Launch File or URL ℹ️
typer.launch("https://typer.tiangolo.com")
typer.launch("/my/downloaded/file", locate=True)locate-- open the file browser indicating where a file is locatedwait-- wait for the program to exit before returning
Automatically Generated Documentation ℹ️
Use the typer CLI to generate Markdown documentation:
$ typer my_package.main utils docs --name awesome-cli --output README.md
Docs saved to: README.mdTesting ℹ️
Use a CliRunner:
from typer.testing import CliRunner
from .main import app
runner = CliRunner()
def test_app():
result = runner.invoke(app, ["Camila", "--city", "Berlin"])
assert result.exit_code == 0
assert "Hello Camila" in result.stdout
assert "Let's have a coffee in Berlin" in result.stdout