typer Documentation

Typer, build great CLIs. Easy to code. Based on Python type hints.

19,893 stars Python Python 1 file · ~9,187 tokens #cli#click#python#python3#shell#terminal#typehints#typer
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy typer on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name typer --restart always fastapi/typer:latest
Configured Services & Ports
⚙️ typer
image: build locally
docker-compose.yml
yaml (docker-compose.yml)
services:
  typer:
    build:
      context: ../../
      dockerfile: scripts/docker/Dockerfile
    volumes:
      - ../../:/code
    command: sleep infinity

1. Project Overview & Quickstart (fastapi/typer)

File: README.md


Documentation: https://typer.tiangolo.com

Source Code: https://github.com/fastapi/typer


Typer is a library for building CLI applications that users will love using and developers will love creating. Based on Python type hints.

It's also a command line tool to run scripts, automatically converting them to CLI applications.

The key features are:

  • Intuitive to write: Great editor support. Completion everywhere. Less time debugging. Designed to be easy to use and learn. Less time reading docs.
  • Easy to use: It's easy to use for the final users. Automatic help, and automatic completion for all shells.
  • Short: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
  • Start simple: The simplest example adds only 2 lines of code to your app: 1 import, 1 function call.
  • Grow large: Grow in complexity as much as you want, create arbitrarily complex trees of commands and groups of subcommands, with options and arguments.
  • Run scripts: Typer includes a typer command/program that you can use to run scripts, automatically converting them to CLIs, even if they don't use Typer internally.

FastAPI of CLIs

Typer is FastAPI's little sibling, it's the FastAPI of CLIs.

Installation

First, install uv, and then add Typer to your project:

console
$ uv add typer
---> 100%

This installs both the Typer library and the typer command in the project's virtual environment. To use typer directly with shell completion, activate the project environment and install completion.

If you prefer to use pip, install typer inside a virtual environment. See the installation guide for the alternative steps.

Example

The absolute minimum

  • Create a file main.py with:
python
def main(name: str):
    print(f"Hello {name}")

This script doesn't even use Typer internally. But you can use the typer command to run it as a CLI application.

Run it

Run your application with the typer command:

console
// Run your application
$ typer main.py run

// You get a nice error, you are missing 'name'
Usage: typer [PATH_OR_MODULE] run [OPTIONS] {name}
Try 'typer [PATH_OR_MODULE] run --help' for help.
╭─ Error ───────────────────────────────────────────╮
│ Missing argument 'name'.                          │
╰───────────────────────────────────────────────────╯


// You get a --help for free
$ typer main.py run --help

Usage: typer [PATH_OR_MODULE] run [OPTIONS] {name}

Run the provided Typer app.

╭─ Arguments ───────────────────────────────────────╮
│ *    name      <str>  [required]                  │
╰───────────────────────────────────────────────────╯
╭─ Options ─────────────────────────────────────────╮
│ --help          Show this message and exit.       │
╰───────────────────────────────────────────────────╯

// Now pass the 'name' argument
$ typer main.py run Camila

Hello Camila

// It works! 🎉

This is the simplest use case, not even using Typer internally, but it can already be quite useful for simple scripts.

Note: auto-completion works when you create a Python package and run it with --install-completion or when you use the typer command.

Use Typer in your code

Now let's start using Typer in your own code, update main.py with:

python
import typer


def main(name: str):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

Now you could run it with Python directly:

console
// Run your application
$ uv run python main.py

// You get a nice error, you are missing 'name'
Usage: main.py [OPTIONS] {name}
Try 'main.py --help' for help.
╭─ Error ───────────────────────────────────────────╮
│ Missing argument 'name'.                          │
╰───────────────────────────────────────────────────╯


// You get a --help for free
$ uv run python main.py --help

Usage: main.py [OPTIONS] {name}

╭─ Arguments ───────────────────────────────────────╮
│ *    name      <str>  [required]                  │
╰───────────────────────────────────────────────────╯
╭─ Options ─────────────────────────────────────────╮
│ --help          Show this message and exit.       │
╰───────────────────────────────────────────────────╯

// Now pass the 'name' argument
$ uv run python main.py Camila

Hello Camila

// It works! 🎉

Note: you can also call this same script with the typer command, but you don't need to.

Example upgrade

This was the simplest example possible.

Now let's see one a bit more complex.

An example with two subcommands

Modify the file main.py.

Create a typer.Typer() app, and create two subcommands with their parameters.

python hl_lines="3 6 11 20"
import typer

app = typer.Typer()


@app.command()
def hello(name: str):
    print(f"Hello {name}")


@app.command()
def goodbye(name: str, formal: bool = False):
    if formal:
        print(f"Goodbye Ms. {name}. Have a good day.")
    else:
        print(f"Bye {name}!")


if __name__ == "__main__":
    app()

And that will:

  • Explicitly create a typer.Typer app.
    • The previous typer.run actually creates one implicitly for you.
  • Add two subcommands with @app.command().
  • Execute the app() itself, as if it was a function (instead of typer.run).

Run the upgraded example

Check the new help:

console
$ uv run python main.py --help

 Usage: main.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 ────────────────────────────────────────╮
│ hello                                             │
│ goodbye                                           │
╰───────────────────────────────────────────────────╯

// When you create a package you get ✨ auto-completion ✨ for free, installed with --install-completion

// You have 2 subcommands (the 2 functions): hello and goodbye

Now check the help for the hello command:

console
$ uv run python main.py hello --help

 Usage: main.py hello [OPTIONS] {name}

╭─ Arguments ───────────────────────────────────────╮
│ *    name      <str>  [required]                  │
╰───────────────────────────────────────────────────╯
╭─ Options ─────────────────────────────────────────╮
│ --help          Show this message and exit.       │
╰───────────────────────────────────────────────────╯

And now check the help for the goodbye command:

console
$ uv run python main.py goodbye --help

 Usage: main.py goodbye [OPTIONS] {name}

╭─ Arguments ───────────────────────────────────────╮
│ *    name      <str>  [required]                  │
╰───────────────────────────────────────────────────╯
╭─ Options ─────────────────────────────────────────╮
│ --formal    --no-formal      [default: no-formal] │
│ --help                       Show this message    │
│                              and exit.            │
╰───────────────────────────────────────────────────╯

// Automatic --formal and --no-formal for the bool option 🎉

Now you can try out the new command line application:

console
// Use it with the hello command

$ uv run python main.py hello Camila

Hello Camila

// And with the goodbye command

$ uv run python main.py goodbye Camila

Bye Camila!

// And with --formal

$ uv run python main.py goodbye --formal Camila

Goodbye Ms. Camila. Have a good day.

Note: If your app only has one command, by default the command name is omitted in usage: python main.py Camila. However, when there are multiple commands, you must explicitly include the command name: python main.py hello Camila. See One or Multiple Commands for more details.

Recap

In summary, you declare once the types of parameters (CLI arguments and CLI options) as function parameters.

You do that with standard modern Python types.

You don't have to learn a new syntax, the methods or classes of a specific library, etc.

Just standard Python.

For example, for an int:

python
total: int

or for a bool flag:

python
force: bool

And similarly for files, paths, enums (choices), etc. And there are tools to create groups of subcommands, add metadata, extra validation, etc.

You get: great editor support, including completion and type checks everywhere.

Your users get: automatic --help, auto-completion in their terminal (Bash, Zsh, Fish, PowerShell) when they install your package or when using the typer command.

For a more complete example including more features, see the Tutorial - User Guide.

Dependencies

Typer requires only a few dependencies (most are tiny):

  • rich: to show nicely formatted errors automatically.
  • shellingham: to automatically detect the current shell when installing completion.
  • annotated-doc: to generate documentation from Python type annotations.
  • colorama (only on Windows): for producing colored terminal text on Windows.

Click code

Typer used to depend on Click as well, a popular tool for building CLIs in Python.

Since version 0.26.0, Typer has vendored Click (included Click's source code internally, instead of installing it as a third party package) and has unified the code interactions between Typer and the embedded Click source code for easier maintainability in the future.

Note that some Click functionality will not be available anymore in the future, as we continue to improve and extend Typer's codebase.

`typer-slim`

There used to be a slimmed-down version of Typer called typer-slim, which didn't include the dependencies rich and shellingham, nor the typer command.

However, since version 0.22.0, we have stopped supporting this, and typer-slim now simply installs (all of) Typer.

If you want to disable Rich globally, you can set an environmental variable TYPER_USE_RICH to False or 0.

License

This project is licensed under the terms of the MIT license.


File: docs/resources/index.md

Resources

Additional resources, how to help and get help, how to contribute, and more. ✈️


File: docs/tutorial/arguments/default.md

CLI Arguments with Default

We can also use the same typer.Argument() to set a default value.

That way the CLI argument will be optional and also have a default value.

An optional *CLI argument* with a default

We can also use typer.Argument() to make a CLI argument have a default value other than None:

{* docs_src/arguments/default/tutorial001_an_py310.py hl[9] *}

/// tip

Because now the value will be a str passed by the user or the default value of "Wade Wilson" which is also a str, we know the value will never be None, so we don't have to (and shouldn't) use Optional[str].

Have in mind that the Optional[something] tells Python that a value "could be None". But the use of Optional doesn't affect Typer in any way, e.g. it doesn't tell Typer if a value is required or not.

///

Check it:

console
// Check the help
$ uv run python main.py --help

// Notice the [default: Wade Wilson] ✨
Usage: main.py [OPTIONS] [name]

Arguments:
  name  [default: Wade Wilson]

Options:
  --help                Show this message and exit.

// With no optional CLI argument
$ uv run python main.py

Hello Wade Wilson

// With one CLI argument
$ uv run python main.py Camila

Hello Camila

Dynamic default value

And we can even make the default value be dynamically generated by passing a function as the default_factory argument:

{* docs_src/arguments/default/tutorial002_an_py310.py hl[9:10,14] *}

In this case, we created the function get_name that will just return a random str each time.

And we pass it as the first function argument to typer.Argument().

/// tip

The word "factory" in default_factory is just a fancy way of saying "function that will create the default value".

///

Check it:

console
// Check the help
$ uv run python main.py --help

Usage: main.py [OPTIONS] [name]

Arguments:
  name  [default: (dynamic)]

Options:
  --help                Show this message and exit.

// Try it several times, it will use a random default each time
$ uv run python main.py

Hello Deadpool

$ uv run python main.py

Hello Hiro

$ uv run python main.py

Hello Rick

// Now pass a value for the CLI argument
$ uv run python main.py Camila

Hello Camila

File: docs/tutorial/arguments/envvar.md

CLI Arguments with Environment Variables

You can also configure a CLI argument to read a value from an environment variable if it is not provided in the command line as a CLI argument.

An environment variable (also known as an env var) is a value that lives outside of your Python code, in the operating system, and can be read by your application and other programs.

/// tip

Read the Environment Variables guide for a detailed, cross-platform explanation.

///

To do that, use the envvar parameter for typer.Argument():

{* docs_src/arguments/envvar/tutorial001_an_py310.py hl[9] *}

In this case, the CLI argument name will have a default value of "World", but will also read any value passed to the environment variable AWESOME_NAME if no value is provided in the command line:

console
// Check the help
$ uv run python main.py --help

Usage: main.py [OPTIONS] [name]

Arguments:
  name  [env var: AWESOME_NAME; default: World]

Options:
  --help                Show this message and exit.

// Call it without a CLI argument
$ uv run python main.py

Hello Mr. World

// Now pass a value for the CLI argument
$ uv run python main.py Czernobog

Hello Mr. Czernobog

// And now use the environment variable
$ AWESOME_NAME=Wednesday uv run python main.py

Hello Mr. Wednesday

// CLI arguments take precedence over env vars
$ AWESOME_NAME=Wednesday uv run python main.py Czernobog

Hello Mr. Czernobog

/// tip | Windows PowerShell

In PowerShell, set the environment variable first and then run the command:

console
$ $Env:AWESOME_NAME = "Wednesday"
$ uv run python main.py

///

Multiple environment variables

You are not restricted to a single environment variable, you can declare a list of environment variables that could be used to get a value if it was not passed in the command line:

{* docs_src/arguments/envvar/tutorial002_an_py310.py hl[10] *}

Check it:

console
// Check the help
$ uv run python main.py --help

Usage: main.py [OPTIONS] [name]

Arguments:
  name  [env var: AWESOME_NAME, GOD_NAME; default: World]

Options:
  --help                Show this message and exit.

// Try the first env var
$ AWESOME_NAME=Wednesday uv run python main.py

Hello Mr. Wednesday

// Try the second env var
$ GOD_NAME=Anubis uv run python main.py

Hello Mr. Anubis

Hide an env var from the help text

By default, environment variables used will be shown in the help text, but you can disable them with show_envvar=False:

{* docs_src/arguments/envvar/tutorial003_an_py310.py hl[11] *}

Check it:

console
//Check the help
$ uv run python main.py --help

// It won't show the env var
Usage: main.py [OPTIONS] [name]

Arguments:
  name  [default: World]

Options:
  --help                Show this message and exit.

// But it will still be able to use it
$ AWESOME_NAME=Wednesday uv run python main.py

Hello Mr. Wednesday

File: docs/tutorial/arguments/help.md

CLI Arguments with Help

In the First Steps section you saw how to add help for a CLI app/command by adding it to a function's docstring.

Here's how that last example looked like:

{* docs_src/first_steps/tutorial006_py310.py *}

Now that you also know how to use typer.Argument(), let's use it to add documentation specific for a CLI argument.

Add a `help` text for a *CLI argument*

You can use the help parameter to add a help text for a CLI argument:

{* docs_src/arguments/help/tutorial001_an_py310.py hl[9] *}

And it will be used in the automatic --help option:

console
$ uv run python main.py --help

// Check the section with Arguments below 🚀
Usage: main.py [OPTIONS] {name}

Arguments:
  name  The name of the user to greet  [required]

Options:
  --help                Show this message and exit.

Combine help text and docstrings

And of course, you can also combine that help with the docstring:

{* docs_src/arguments/help/tutorial002_an_py310.py hl[9:12] *}

And the --help option will combine all the information:

console
$ uv run python main.py --help

// Notice that we have the help text from the docstring and also the Arguments 📝
Usage: main.py [OPTIONS] {name}

  Say hi to 'name' very gently, like Dirk.

Arguments:
  name  The name of the user to greet  [required]

Options:
  --help                Show this message and exit.

Help with defaults

If you have a CLI argument with a default value, like "World":

{* docs_src/arguments/help/tutorial003_an_py310.py hl[9] *}

It will show that default value in the help text:

console
$ uv run python main.py --help

// Notice the [default: World] 🔍
Usage: main.py [OPTIONS] [name]

  Say hi to 'name' very gently, like Dirk.

Arguments:
  name  Who to greet  [default: World]

Options:
  --help                Show this message and exit.

But you can disable that if you want to, with show_default=False:

{* docs_src/arguments/help/tutorial004_an_py310.py hl[11] *}

And then it won't show the default value:

console
$ uv run python main.py --help

// Notice the there's no [default: World] now 🔥
Usage: main.py [OPTIONS] [name]

  Say hi to 'name' very gently, like Dirk.

Arguments:
  name  Who to greet

Options:
  --help                Show this message and exit.

Custom default string

You can use the same show_default to pass a custom string (instead of a bool) to customize the default value to be shown in the help text:

{* docs_src/arguments/help/tutorial005_an_py310.py hl[13] *}

And it will be used in the help text:

console
$ uv run python main.py --help

Usage: main.py [OPTIONS] [name]

Arguments:
  name  Who to greet  [default: (Deadpoolio the amazing's name)]


Options:
  --help                Show this message and exit.

// See it shows "(Deadpoolio the amazing's name)" instead of the actual default of "Wade Wilson"

Custom help name (`metavar`)

You can also customize the text used in the generated help text to represent a CLI argument.

By default, it will be the same name you declared, with casing preserved.

So, if you declare it as:

python
name: str

It will be shown as:

text
name

But you can customize it with the metavar parameter for typer.Argument().

For example, let's say you don't want to have the default of name, you want to have username, and you really want ✨ emojis ✨ everywhere:

{* docs_src/arguments/help/tutorial006_an_py310.py hl[9] *}

Now the generated help text will have ✨username✨ instead of name:

console
$ uv run python main.py --help

Usage: main.py [OPTIONS] [✨username✨]

Arguments:
  ✨username✨  [default: World]

Options:
  --help                Show this message and exit.

*CLI Argument* help panels

You might want to show the help information for CLI arguments in different panels when using the --help option.

If you have installed Rich as described in the docs for Printing and Colors, you can set the rich_help_panel parameter to the name of the panel where you want this CLI argument to be shown:

{* docs_src/arguments/help/tutorial007_an_py310.py hl[12,16] *}

Then, if you check the --help option, you will see a default panel named "Arguments" for the CLI arguments that don't have a custom rich_help_panel.

And next you will see other panels for the CLI arguments that have a custom panel set in the rich_help_panel parameter:

console
$ uv run python main.py --help

** ****Usage: ****main.py [OPTIONS] {name} [lastname] [age]               **
**                                                                     **
 Say hi to 'name' very gently, like Dirk.

╭─ Arguments ───────────────────────────────────────────────────────╮
│ *    name      **str**  Who to greet [default: None] [required]       │
╰───────────────────────────────────────────────────────────────────╯
╭─ Secondary Arguments ─────────────────────────────────────────────╮
│   lastname      **lastname**  The last name                           │
│   age           **age     **  The user&apos;s age                          │
╰───────────────────────────────────────────────────────────────────╯
╭─ Options ─────────────────────────────────────────────────────────╮
│ **--help**                        Show this message and exit.         │
╰───────────────────────────────────────────────────────────────────╯

In this example we have a custom CLI arguments panel named "Secondary Arguments".

Help with style using Rich

In a future section you will see how to use custom markup in the help for CLI arguments when reading about Commands - Command Help.

If you are in a hurry you can jump there, but otherwise, it would be better to continue reading here and following the tutorial in order.

Hide a *CLI argument* from the help text

If you want, you can make a CLI argument not show up in the Arguments section in the help text.

You will probably not want to do this normally, but it's possible:

{* docs_src/arguments/help/tutorial008_an_py310.py hl[9] *}

Check it:

console
$ uv run python main.py --help

// Notice there's no Arguments section at all 🔥
Usage: main.py [OPTIONS] [name]

  Say hi to 'name' very gently, like Dirk.

Options:
  --help                Show this message and exit.

/// note

Have in mind that the CLI argument will still show up in the first line with Usage.

But it won't show up in the main help text under the Arguments section.

///

Help text for *CLI arguments*

Typer supports help for CLI arguments to make it easier to have consistent help texts with a consistent format for your CLI programs. 🎨

This is also to help you create CLI programs that are ✨ awesome ✨ by default. With very little code.

If you don't want the CLI Argument to be shown in help outputs, you can set the hidden parameter to True.


File: docs/tutorial/arguments/index.md

CLI Arguments

In the next few sections we'll see some ways to modify how CLI arguments work.

We'll create optional CLI arguments, we'll add integrated help for CLI arguments, etc.


File: docs/tutorial/arguments/optional.md

Optional CLI Arguments

We said before that by default:

  • CLI options are optional
  • CLI arguments are required

Again, that's how they work by default, and that's the convention in many CLI programs and systems.

But you can change that.

In fact, it's very common to have optional CLI arguments, it's way more common than having required CLI options.

As an example of how it could be useful, let's see how the ls CLI program works.

console
// If you just type
$ ls

// ls will "list" the files and directories in the current directory
typer  tests  README.md  LICENSE

// But it also receives an optional CLI argument
$ ls ./tests/

// And then ls will list the files and directories inside of that directory from the CLI argument
__init__.py  test_tutorial

An alternative *CLI argument* declaration

In the First Steps you saw how to add a CLI argument:

{* docs_src/first_steps/tutorial002_py310.py hl[4] *}

Now let's see an alternative way to create the same CLI argument:

{* docs_src/arguments/optional/tutorial000_an_py310.py hl[6] *}

Or, using an explicit Typer() instance creation:

{* docs_src/arguments/optional/tutorial001_an_py310.py hl[9] *}

/// note

Typer added support for Annotated (and started recommending it) in version 0.9.0.

If you have an older version, you would get errors when trying to use Annotated.

Make sure you upgrade the Typer version to at least 0.9.0 before using Annotated.

///

Before, you had this function parameter:

python
name: str

And now we wrap it with Annotated:

python
name: Annotated[str]

Both of these versions mean the same thing, Annotated is part of standard Python and is there for this.

But the second version using Annotated allows us to pass additional metadata that can be used by Typer:

python
name: Annotated[str, typer.Argument()]

Now we are being explicit that name is a CLI argument. It's still a str and it's still required (it doesn't have a default value).

All we did there achieves the same thing as before, a required CLI argument:

console
$ uv run python main.py

Usage: main.py [OPTIONS] {name}
Try "main.py --help" for help.

Error: Missing argument 'name'.

It's still not very useful, but it works correctly.

And being able to declare a required CLI argument using

python
name: Annotated[str, typer.Argument()]

...that works exactly the same as

python
name: str

...will come handy later.

Make an optional *CLI argument*

Now, finally what we came for, an optional CLI argument.

To make a CLI argument optional, use typer.Argument() and make sure to provide a "default" value, for example "World":

{* docs_src/arguments/optional/tutorial002_an_py310.py hl[9] *}

Now we have:

python
name: Annotated[str, typer.Argument()] = "World"

Because we are using typer.Argument() Typer will know that this is a CLI argument (no matter if required or optional).

Check the help:

console
// First check the help
$ uv run python main.py --help

Usage: main.py [OPTIONS] [name]

Arguments:
  name  [default: World]

Options:
  --help                Show this message and exit.

/// tip

Notice that name is still a CLI argument, it's shown up there in the "Usage: main.py ...".

Also notice that now [name] has rectangular brackets ("[" and "]") around it to denote that it's optional, not required. If it would have been required, curly brackets ("{" and "}") would be used instead.

///

Now run it and test it:

console
// With no CLI argument
$ uv run python main.py

Hello World!

// With one optional CLI argument
$ uv run python main.py Camila

Hello Camila

/// tip

Notice that "Camila" here is an optional CLI argument, not a CLI option, because we didn't use something like "--name Camila", we just passed "Camila" directly to the program.

///

Alternative (old) `typer.Argument()` as the default value

Typer also supports another older alternative syntax for declaring CLI arguments with additional metadata.

Instead of using Annotated, you can use typer.Argument() as the default value:

{* docs_src/arguments/optional/tutorial001_py310.py hl[7] *}

/// tip

Prefer to use the Annotated version if possible.

///

Before, because name didn't have any default value it would be a required parameter for the Python function, in Python terms.

When using typer.Argument() as the default value Typer does the same and makes it a required CLI argument.

We changed it to:

python
name: str = typer.Argument()

But now as typer.Argument() is the "default value" of the function's parameter, it would mean that "it is no longer required" (in Python terms).

As we no longer have the Python function default value (or its absence) to tell if something is required or not and what is the default value, typer.Argument() receives a first parameter default that serves the same purpose of defining that default value, or making it required.

Not passing any value to the default argument is the same as marking it as required. But you can also explicitly mark it as required by passing ... as the default argument, passed to typer.Argument(default=...).

python
name: str = typer.Argument(default=...)

/// note

If you hadn't seen that ... before: it is a special single value, it is part of Python and is called "Ellipsis".

///

{* docs_src/arguments/optional/tutorial003_py310.py hl[7] *}

And the same way, you can make it optional by passing a different default value, for example "World":

{* docs_src/arguments/optional/tutorial002_py310.py hl[7] *}

Because the first parameter passed to typer.Argument(default="World") (the new "default" value) is "World", Typer knows that this is an optional CLI argument, if no value is provided when calling it in the command line, it will have that default value of "World".

The default argument is the first one, so it's possible that you see code that passes the value without explicitly using default=, like:

python
name: str = typer.Argument(...)

...or like:

python
name: str = typer.Argument("World")

...but again, try to use Annotated if possible, that way your code in terms of Python will mean the same thing as with Typer and you won't have to remember any of these details.


File: docs/tutorial/arguments/other-uses.md

Other uses

typer.Argument() has several other use cases. Such as for data validation, to enable other features, etc.

You will see about these use cases later in the docs.


File: docs/tutorial/commands/arguments.md

Command CLI Arguments

The same way as with a CLI application with a single command, subcommands (or just "commands") can also have their own CLI arguments:

{* docs_src/commands/arguments/tutorial001_py310.py hl[7,12] *}

console
// Check the help for create
$ uv run python main.py create --help

Usage: main.py create [OPTIONS] {username}

Arguments:
  username  [required]

Options:
  --help  Show this message and exit.

// Call it with a CLI argument
$ uv run python main.py create Camila

Creating user: Camila

// The same for delete
$ uv run python main.py delete Camila

Deleting user: Camila

/// tip

Everything to the right of the command are CLI parameters (CLI arguments and CLI options) for that command.

///

/// note | Technical Details

Actually, it's everything to the right of that command, before any subcommand.

It's possible to have groups of subcommands, it's like if one command also had subcommands. And then those subcommands could have their own CLI parameters, taking their own CLI parameters.

You will see about them later in another section.

///

2. Official Technical Reference & Guides (fastapi/annotated-doc)

Annotated Doc

Document parameters, class attributes, return types, and variables inline, with Annotated.

Installation

bash
pip install annotated-doc

Or with uv:

python
uv add annotated-doc

Usage

Import Doc and pass a single literal string with the documentation for the specific parameter, class attribute, return type, or variable.

For example, to document a parameter name in a function hi you could do:

python
from typing import Annotated

from annotated_doc import Doc

def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None:
    print(f"Hi, {name}!")

You can also use it to document class attributes:

python
from typing import Annotated

from annotated_doc import Doc

class User:
    name: Annotated[str, Doc("The user's name")]
    age: Annotated[int, Doc("The user's age")]

The same way, you could document return types and variables, or anything that could have a type annotation with Annotated.

Who Uses This

annotated-doc was made for:

annotated-doc is supported by griffe-typingdoc, which powers reference documentation like the one in the FastAPI Reference.

Reasons not to use `annotated-doc`

You are already comfortable with one of the existing docstring formats, like:

  • Sphinx
  • numpydoc
  • Google
  • Keras

Your team is already comfortable using them.

You prefer having the documentation about parameters all together in a docstring, separated from the code defining them.

You care about a specific set of users, using one specific editor, and that editor already has support for the specific docstring format you use.

Reasons to use `annotated-doc`

  • No micro-syntax to learn for newcomers, it’s just Python syntax.
  • Editing would be already fully supported by default by any editor (current or future) supporting Python syntax, including syntax errors, syntax highlighting, etc.
  • Rendering would be relatively straightforward to implement by static tools (tools that don't need runtime execution), as the information can be extracted from the AST they normally already create.
  • Deduplication of information: the name of a parameter would be defined in a single place, not duplicated inside of a docstring.
  • Elimination of the possibility of having inconsistencies when removing a parameter or class variable and forgetting to remove its documentation.
  • Minimization of the probability of adding a new parameter or class variable and forgetting to add its documentation.
  • Elimination of the possibility of having inconsistencies between the name of a parameter in the signature and the name in the docstring when it is renamed.
  • Access to the documentation string for each symbol at runtime, including existing (older) Python versions.
  • A more formalized way to document other symbols, like type aliases, that could use Annotated.
  • Support for apps using FastAPI, Typer and others.
  • AI Accessibility: AI tools will have an easier way understanding each parameter as the distance from documentation to parameter is much closer.

History

I (@tiangolo) originally wanted for this to be part of the Python standard library (in PEP 727), but the proposal was withdrawn as there was a fair amount of negative feedback and opposition.

The conclusion was that this was better done as an external effort, in a third-party library.

So, here it is, with a simpler approach, as a third-party library, in a way that can be used by others, starting with FastAPI and friends.

License

This project is licensed under the terms of the MIT license.