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.
"""
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:
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/.
-
The DRY principle is stated as "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system" ↩