Skip to content

How to create a CLI with Typer?

Learning Objectives

By the end of this section, you should be able to:

  • Explain why Typer is an optimal choice to build your CLI
  • Navigate the official documentation of Typer
  • Build your own CLI from scratch
  • Automatically generate documentation for your CLI
  • Explain how quizli's CLI was built

What is Typer and why should I use it?

Quote

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

Maybe you have heard of or even used Click before - a popular Python package for creating command line interfaces in Python. Since Typer is based on Click, by using it you get all of Clicks's features, plus everything Typer adds on top of it.

For me the most striking features/advantages of Typer are that it:

  • Utilizes Python's type hints
  • Provides automatic help, and automatic completion (for most/all shells)
  • Allow to generate Markdown documentation for your CLI's build with Typer (see below)
  • Has sensible defaults (allows to write less code)
  • Is easy to use both for developers and users
  • Contains a comprehensive documentation with lot's of examples and detailed tutorials

There is also a particular section in Typer's docs on how it compares to alternative libraries.

Recommended Reading

Using Typer for quizli

Creating a CLI for quizli

Below you see the source code of quizli's CLI. We added comments, prepended with (1) to (4) and highlighted in yellow, to explain how the source code relates to the resulting CLI app.

quizli's CLI
"""
This module contains the entrypoint to quizli.

We use the `typer` package to create a CLI.

For more information see the [CLI reference](pwenker.github.io/quizli/user_guide/cli.html)
"""


from pathlib import Path
from typing import Optional

import typer
from rich import print

from quizli import Quiz, QuizConfig, QuizMode, examples
from quizli.examples import QuizKind
from quizli.session import QuizSession

# (1) Here, we define our CLI app and give it a short but informative description
# This will be shown on top of the help text when the app is launched with
# `quizli --help`.

app = typer.Typer(
    help="""Welcome to quizli

    An educational project teaching how to open-source an interactive Python quiz app

    Check out the project at: https://github.com/pwenker/quizli/
    """
)

# (2) The `@app.command` decorator turns the function into a command for our CLI.
# This way, the user can launch the `demo`-function with `quizli demo`
# Here, as a result, the demo section of the docs will be opened in a new browser tab.
@app.command()
def demo():
    """Open the documentation page in the browser"""
    import webbrowser

    webbrowser.open("https://pwenker.github.io/quizli/demos.html", new=2)


# (3) Again, we define a command for our CLI: `quizli start`.
# This time, we add a lot of parameters to the function, which will be translated
# into CLI options. Here, Typer will make use of the type hints we provide.
# For example, by supplying the `QuizKind` enumeration type hint to the
# `quiz_name` parameter, Typer will automatically check that the user supplies a
# valid variant when running the command: `quizli start --quiz-name <quiz-name>`
# and it will show all existing variants in the help message `quizli start --help`.
@app.command()
def start(
    from_csv: Optional[Path] = typer.Option(
        None, exists=True, help="Read a quiz from a csv-file"
    ),
    mode: QuizMode = typer.Option(
        QuizMode.COMPLETE,
        help="Select the condition for the quiz to end",
    ),
    quiz_name: QuizKind = typer.Option(
        QuizKind.PYTHON_QUIZ, help="Select a built-in quiz"
    ),
    randomize: bool = typer.Option(
        True,
        "--randomize/--in-order",
        help="Shuffle the quiz before starting it",
    ),
):
    # (4) The docstring below will automatically be inserted into the help message
    # and can be shown with `quizli start --help`. The same is true for the help
    # strings we defined above for the parameters of the `start` function.
    """
    Start the quiz with the configuration of your choice.

    ### Select Quiz Content

    There are 2 ways to create a quiz with `quizli`:

    1. From a csv file containing question-answer pairs: e.g. `quizli start --from-csv examples/quiz.csv`
    2. From a function in the `example` module: e.g. `quizli start --quiz-name python_quiz`

    ### Select Quiz Settings

    You can choose to

    - Shuffle the quiz with the `--randomize` flag (default), or keep it `--in-order`.

    - Terminate the quiz with the first wrong answer with `mode=sudden_death`, or continue on failure with `mode=complete` (default).
    """

    config = QuizConfig(mode=mode, randomize=randomize)

    if from_csv is not None:
        quiz = Quiz.from_csv(Path(from_csv))
    else:
        quiz = getattr(examples, quiz_name)()

    quiz_session = QuizSession(quiz, config)
    quiz_session.start()


if __name__ == "__main__":
    app()

Generating Markdown documentation

Take a look at the CLI reference page in quizli's user guide section.

What you see there was actually automatically generated with Typer, using a single, simple command:

typer quizli/main.py utils docs --name quizli --output docs/user_guide/cli.md

This feature allows you to write all the documentation of your CLI directly within the source file(s) simply using Python docstrings. This way, following the DRY principle1, it is easy to keep your docs up-to-date and in-sync with the source code.

Final remarks

With our example we only scratch the surface of what is possible with Typer, and what is needed for a CLI to be useful in a real-world scenario. But it should get you going! :)

A note about Best Practices

As soon as you are building a larger "real-world" CLI, it pays of for you and your users to adhere to some best practices:

A comprehensive and elucidating guideline can be found here: https://clig.dev/.


  1. The DRY principle is stated as "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system" 


Last update: March 11, 2022