Build a Command-Line To-Do App With Python and Typer (original) (raw)

Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Building a Python Command-Line To-Do App With Typer

Building an application to manage your to-do list can be an interesting project when you’re learning a new programming language or trying to take your skills to the next level. In this tutorial, you’ll build a functional to-do application for the command line using Python and Typer, which is a relatively young library for creating powerful command-line interface (CLI) applications in almost no time.

With a project like this, you’ll apply a wide set of core programming skills while building a real-world application with real features and requirements.

In this tutorial, you’ll learn how to:

Additionally, you’ll practice your skills related to processing JSON files by using Python’s json module and managing configuration files with Python’s configparser module. With this knowledge, you’ll be ready to start creating CLI applications right away.

You can download the entire code and all the additional resources for this to-do CLI application by clicking the link below and going to the source_code_final/ directory:

Demo

In this step-by-step project, you’ll build a command-line interface (CLI) application to manage a to-do list. Your application will provide a CLI based on Typer, a modern and versatile library for creating CLI applications.

Before you get started, check out this demo of how your to-do application will look and work once you get to the end of this tutorial. The first part of the demo shows how to get help on working with the app. It also shows how to initialize and configure the app. The rest of the video demonstrates how to interact with the essential features, such as adding, removing, and listing to-dos:

Nice! The application has a user-friendly CLI that allows you to set up the to-do database. Once there, you can add, remove, and complete to-dos using appropriate commands, arguments, and options. If you ever get stuck, then you can ask for help using the --help option with proper arguments.

Do you feel like kicking off this to-do app project? Cool! In the next section, you’ll plan out how to structure the layout of the project and what tools you’ll use to build it.

Project Overview

When you want to start a new application, you typically start by thinking about how you want the app to work. In this tutorial, you’ll build a to-do app for the command line. You’ll call that application rptodo.

You want your application to have a user-friendly command-line interface that allows your users to interact with the app and manage their to-do lists.

To start off, you want your CLI to provide the following global options:

You’ll see these same options in many other CLI applications out there. It’s a nice idea to provide them because most users who work with the command line expect to find them in every app.

Regarding managing a to-do list, your application will provide commands to initialize the app, add and remove to-dos, and manage the to-do completion status:

Command Description
init Initializes the application’s to-do database
add DESCRIPTION Adds a new to-do to the database with a description
list Lists all the to-dos in the database
complete TODO_ID Completes a to-do by setting it as done using its ID
remove TODO_ID Removes a to-do from the database using its ID
clear Removes all the to-dos by clearing the database

These commands provide all the functionality you need to turn your to-do application into a minimum viable product (MVP) so that you can publish it to PyPI or the platform of your choice and start getting feedback from your users.

To provide all these features in your to-do application, you’ll need to complete a few tasks:

  1. Build a command-line interface capable of taking and processing commands, options, and arguments
  2. Select an appropriate data type to represent your to-dos
  3. Implement a way to persistently store your to-do list
  4. Define a way to connect that user interface with the to-do data

These tasks relate well to what is known as the Model-View-Controller design, which is an architectural pattern. In this pattern, the model takes care of the data, the view deals with the user interface, and the controller connects both ends to make the application work.

The main reason for using this pattern in your applications and projects is to provide separation of concerns (SoC), making different parts of your code deal with specific concepts independently.

The next decision you need to make is about the tools and libraries you’ll use to tackle each of the tasks you defined further up. In other words, you need to decide your software stack. In this tutorial, you’ll use the following stack:

You’ll also use the configparser module from the Python standard library to handle the application’s initial settings in a configuration file. Within the configuration file, you’ll store the path to the to-do database in your file system. Finally, you’ll use pytest as a tool for testing your CLI application.

Prerequisites

To complete this tutorial and get the most out of it, you should be comfortable with the following topics:

That’s it! If you’re ready to get your hands dirty and start creating your to-do app, then you can begin with setting up your working environment and project layout.

Step 1: Set Up the To-Do Project

To start coding your to-do application, you need to set up a working Python environment with all the tools, libraries, and dependencies you’ll use in the process. Then you need to give the project a coherent Python application layout. That’s what you’ll do in the following subsections.

To download all the files and the project structure you’ll be creating in this section, click the link below and go to the source_code_step_1/ directory:

Set Up the Working Environment

In this section, you’ll create a Python virtual environment to work on your to-do project. Using a virtual environment for each independent project is a best practice in Python programming. It allows you to isolate your project’s dependencies without cluttering your system Python installation or breaking other projects that use different versions of the same tools and libraries.

To create a Python virtual environment, move to your favorite working directory and create a folder called rptodo_project/. Then fire up a terminal or command line and run the following commands:

Here, you first enter the rptodo_project/ directory using cd. This directory will be your project’s root directory. Then you create a Python virtual environment using venv from the standard library. The argument to venv is the path to the directory hosting your virtual environment. A common practice is to call that directory venv, .venv, or env, depending on your preferences.

The third command activates the virtual environment you just created. You know that the environment is active because your prompt changes to something like (venv) $.

Now that you have a working virtual environment, you need to install Typer to create the CLI application and pytest to test your application’s code. To install Typer with all its current optional dependencies, run the following command:

This command installs Typer and all its recommended dependencies, such as Colorama, which ensures that colors work correctly on your command line window.

To install pytest, which you’ll use later to test your to-do application, run the following command:

With this last command, you successfully installed all the tools you need to start developing your to-do application. The rest of the libraries and tools you’ll use are part of the Python standard library, so you don’t have to install anything to use them.

Define the Project Layout

The last step you’ll run to finish setting up your to-do app project is to create the packages, modules, and files that will frame the application layout. The app’s core package will live in the rptodo/ directory inside rptodo_project/.

Here’s a description of the package’s contents:

File Description
__init__.py Enables rptodo/ to be a Python package
__main__.py Provides an entry-point script to run the app from the package using the python -m rptodo command
cli.py Provides the Typer command-line interface for the application
config.py Contains code to handle the application’s configuration file
database.py Contains code to handle the application’s to-do database
rptodo.py Provides code to connect the CLI with the to-do database

You’ll also need a tests/ directory containing a __init__.py file to turn the directory into a package and a test_rptodo.py file to hold unit tests for the application.

Go ahead and create the project’s layout with the following structure:

rptodo_project/ │ ├── rptodo/ │ ├── __init__.py │ ├── __main__.py │ ├── cli.py │ ├── config.py │ ├── database.py │ └── rptodo.py │ ├── tests/ │ ├── __init__.py │ └── test_rptodo.py │ ├── README.md └── requirements.txt

The README.md file will provide the project’s description and instructions for installing and running the application. Adding a descriptive and detailed README.md file to your project is a best practice in programming, especially if you plan to release the project as open source.

The requirements.txt file will provide the list of dependencies for your to-do application. Go ahead and fill it with the following contents:

Now your users can automatically install the listed dependencies by running the following command:

Providing a requirements.txt like this ensures that your user will install the exact versions of dependencies you used to build the project, avoiding unexpected issues and behaviors.

Except for requirements.txt, all your project’s files should be empty at this point. You’ll fill each file with the necessary content moving forward through this tutorial. In the following section, you’ll code the application’s CLI with Python and Typer.

Step 2: Set Up the To-Do CLI App With Python and Typer

At this point, you should have a complete project layout for your to-do application. You should also have a working Python virtual environment with all the required tools and libraries. At the end of this step, you’ll have a functional Typer CLI application. Then you’ll be able to build on top of its minimal functionality.

You can download the code, unit tests, and resources you’ll add in this section by clicking the link below and going to the source_code_step_2/ directory:

Fire up your code editor and open the __init__.py file from the rptodo/ directory. Then add the following code to it:

Here, you start by defining two module-level names to hold the application’s name and version. Then you define a series of return and error codes and assign integer numbers to them using range(). ERROR is a dictionary that maps error codes to human-readable error messages. You’ll use these messages to tell the user what’s happening with the application.

With this code in place, you’re ready to create the skeleton of your Typer CLI application. That’s what you’ll do in the following section.

Create the Typer CLI Application

In this section, you’ll create a minimal Typer CLI application with support for --help, -v, and --version options. To do so, you’ll use an explicit Typer application. This type of application is suitable for large projects that include multiple commands with several options and arguments.

Go ahead and open rptodo/cli.py in your text editor and type in the following code:

Typer uses Python type hints extensively, so in this tutorial, you’ll use them as well. That’s why you start by importing Optional from typing. Next, you import typer. Finally, you import __app_name__ and __version__ from your rptodo package.

Here’s how the rest of the code works:

With this code in place, you’re ready to create the application’s entry-point script. That’s what you’ll do in the following section.

Create an Entry-Point Script

You’re almost ready to run your to-do application for the first time. Before doing that, you should create an entry-point script for the app. You can create this script in a few different ways. In this tutorial, you’ll do it using a __main__.py module inside the rptodo package. Including a __main__.py module in a Python package enables you to run the package as an executable program using the command python -m rptodo.

Go back to your code editor and open __main__.py from the rptodo/ directory. Then add the following code:

In __main__.py, you first import cli and __app_name__ from rptodo. Then you define main(). In this function, you call the Typer app with cli.app(), passing the application’s name to the prog_name argument. Providing a value to prog_name ensures that your users get the correct app name when running the --help option on their command line.

With this final addition, you’re ready to run your to-do application for the first time. Move to your terminal window and execute the following commands:

The first command runs the -v option, which displays the app’s version. The second command runs the --help option to show a user-friendly help message for the entire application. Typer automatically generates and displays this help message for you.

Set Up Initial CLI Tests With pytest

The final action you’ll run in this section is to set up an initial test suite for your to-do application. To this end, you’ve created the tests package with a module called test_rptodo.py. As you learned earlier, you’ll use pytest for writing and running your unit tests.

Testing a Typer application is straightforward because the library integrates pretty well with pytest. You can use a Typer class called CliRunner to test the application’s CLI. CliRunner allows you to create a runner that you can use to test how your application’s CLI responds to real-world commands.

Go back to your code editor and open test_rptodo.py from the tests/ directory. Type in the following code:

Here’s what this code does:

Typer’s CliRunner is a subclass of Click’s CliRunner. Therefore, its .invoke() method returns a Result object, which holds the result of running the CLI application with the target arguments and options. Result objects provide several useful attributes and properties, including the application’s exit code and standard output. Take a look at the class documentation for more details.

Now that you’ve set up the first unit test for your Typer CLI application, you can run the test with pytest. Go back to your command line and execute python -m pytest tests/ from your project’s root directory:

That’s it! You’ve successfully run your test suite for the first time! Yes, you only have one test so far. However, you’ll be adding more of them in upcoming sections. You can also add your own test if you want to challenge your testing skills.

With the skeleton to-do application in place, now you can think about setting up the to-do database to get it ready for use. That’s what you’ll do in the following section.

Step 3: Prepare the To-Do Database for Use

Up to this point, you’ve put together a CLI for your to-do application, created an entry-point script, and run the application for the first time. You’ve also set up and run a minimal test suite for the app. The next step is to define how your application will initialize and connect to the to-do database.

You’ll use a JSON file to store the data about your to-dos. JSON is a lightweight data-interchange format that’s human-readable and writable. Python’s standard library includes json, which is a module that provides support for the JSON file format out of the box. That’s what you’ll use to manage your to-do database.

You can download the entire code for this section by clicking the link below and going to the source_code_step_3/ directory:

At the end of this section, you’ll have written the code for creating, connecting, and initializing your to-do database so that it’s ready for use. The first step, however, is to define how your application will find the to-do database in your file system.

Set Up the Application’s Configurations

You can use different techniques to define how an application connects and opens a file on your file system. You can provide the file path dynamically, create an environment variable to hold the file path, create a configuration file in which you store the file path, and so on.

In this tutorial, you’ll provide your to-do app with a configuration file in your home directory to store the path to the database. To that end, you’ll use pathlib to work with file system paths and configparser to handle configuration files. Both packages are available for you in the Python standard library.

Now go back to your code editor and open config.py from rptodo/. Type in the following code:

Here’s a breakdown of what this code does:

With this code, you’ve finished setting up the application’s configuration file to store the path to the to-do database. You’ve also added code to create the to-do database as a JSON file. Now you can write code for initializing the database and getting it ready for use. That’s what you’ll do in the following section.

Get the To-Do Database Ready for Use

To get the to-do database ready for use, you need to perform two actions. First, you need a way to retrieve the database file path from the application’s configuration file. Second, you need to initialize the database to hold JSON content.

Open database.py from rptodo/ in your code editor and write the following code:

In this file, lines 4 to 7 perform the required imports. Here’s what the rest of the code does:

Cool! Now you have a way to retrieve the database file path from the application’s configuration file. You also have a way to initialize the database with an empty to-do list in JSON format. It’s time to implement the init command with Typer so that your users can initialize their to-do database from the CLI.

Implement the init CLI Command

The final step to put together all the code you’ve written in this section is to add the init command to your application’s CLI. This command will take an optional database file path. It’ll then create the application’s configuration file and to-do database.

Go ahead and add init() to your cli.py file:

Here’s how this new code works:

To print the messages in this code, you use typer.secho(). This function takes a foreground argument, fg, that allows you to use different colors when printing text to the screen. Typer provides several built-in colors in typer.colors. There you’ll find RED, BLUE, GREEN, and more. You can use those colors with secho() as you did here.

Nice! With all this code in place, you can now give the init command a try. Go back to your terminal and run the following:

This command presents you with a prompt for entering a database location. You can press Enter to accept the default path in square brackets, or you can type in a custom path and then press Enter. The application creates the to-do database and tells you where it’ll reside from this point on.

Alternatively, you can provide a custom database path directly by using init with the -db or --db-path options followed by the desired path. In all cases, your custom path should include the database file name.

Once you’ve run the above command, take a look at your home directory. You’ll have a JSON file named after the filename you used with init. You’ll also have a rptodo/ directory containing a config.ini file somewhere in your home folder. The specific path to this file will depend on your current operating system. On Ubuntu, for example, the file will be at /home/user/.config/rptodo/.

Step 4: Set Up the To-Do App Back End

Up to this point, you’ve made a way to create, initialize, and connect to the to-do database. Now you can start thinking of your data model. In other words, you need to think about how to represent and store data about your to-dos. You also need to define how your application will handle communication between the CLI and database.

You can download the code and all the additional resources you’ll use in this section by clicking the link below and going to the source_code_step_4/ directory:

Define a Single To-Do

First, think about the data you need for defining a single to-do. In this project, a to-do will consist of the following pieces of information:

To store this information, you can use a regular Python dictionary:

The "Description" key stores a string describing the current to-do. The "Priority" key can take three possible values: 1 for high, 2 for medium, and 3 for low priority. The "Done" key holds True when you’ve completed the to-do and False otherwise.

Communicate With the CLI

To communicate with the CLI, you’ll use two pieces of data holding the required information:

  1. todo: The dictionary holding the information for the current to-do
  2. error: The return or error code confirming if the current operation was successful or not

To store this data, you’ll use a named tuple with appropriately named fields. Open up the rptodo.py module from rptodo to create the required named tuple:

In rptodo.py, you first import some required objects from typing. On line 6, you create a subclass of typing.NamedTuple called CurrentTodo with two fields todo and error.

Subclassing NamedTuple allows you to create named tuples with type hints for their named fields. For example, the todo field above holds a dictionary with keys of type str and values of type Any. The error field holds an int value.

Communicate With the Database

Now you need another data container that allows you to send data to and retrieve data from the to-do database. In this case, you’ll use another named tuple with the following fields:

  1. todo_list: The to-do list you’ll write to and read from the database
  2. error: An integer number representing a return code related to the current database operation

Finally, you’ll create a class called DatabaseHandler to read and write data to the to-do database. Go ahead and open database.py. Once you’re there, type in the following code:

Here’s what this code does:

Wow! That was a lot! Now that you finished coding DatabaseHandler and setting up the data exchange mechanism, you can think about how to connect them to the application’s CLI.

Write the Controller Class, Todoer

To connect the DatabaseHandler logic with your application’s CLI, you’ll write a class called Todoer. This class will work similarly to a controller in the Model-View-Controller pattern.

Now go back to rptodo.py and add the following code:

This code includes some imports and the definition of Todoer. This class uses composition, so it has a DatabaseHandler component to facilitate direct communication with the to-do database. You’ll add more code to this class in upcoming sections.

In this section, you’ve put together a lot of setups that shape how your to-do application’s back end will work. You’ve decided what data structures to use for storing the to-do data. You’ve also defined what kind of database you’ll use to save the to-do information and how to operate on it.

With all those setups in place, you’re now ready to start providing value to your users by allowing them to populate their to-do lists. You’ll also implement a way to display the to-dos on the screen.

Step 5: Code the Adding and Listing To-Dos Functionalities

In this section, you’ll code one of the main features of your to-do application. You’ll provide your users with a command to add new to-dos to their current list. You’ll also allow the users to list their to-dos on the screen in a tabular format.

Before working on these features, you’ll set up a minimal test suite for your code. Writing a test suite before writing the code will help you understand what test-driven development (TDD) is about.

To download the code, unit tests, and all the additional resources you’ll add in this section, just click the link below and go to the source_code_step_5/ directory:

Define Unit Tests for Todoer.add()

In this section, you’ll use pytest to write and run a minimal test suite for Todoer.add(). This method will take care of adding new to-dos to the database. With the test suite in place, you’ll write the required code to pass the tests, which is a fundamental idea behind TDD.

Before writing tests for .add(), think of what this method needs to do:

  1. Get a to-do description and priority
  2. Create a dictionary to hold the to-do information
  3. Read the to-do list from the database
  4. Append the new to-do to the current to-do list
  5. Write the updated to-do list back to the database
  6. Return the newly added to-do along with a return code back to the caller

A common practice in code testing is to start with the main functionality of a given method or function. You’ll start by creating test cases to check if .add() properly adds new to-dos to the database.

To test .add(), you must create a Todoer instance with a proper JSON file as the target database. To provide that file, you’ll use a pytest fixture.

Go back to your code editor and open test_rptodo.py from the tests/ directory. Add the following code to it:

Here, you first update your imports to complete some requirements. The fixture, mock_json_file(), creates and returns a temporary JSON file, db_file, with a single-item to-do list in it. In this fixture, you use tmp_path, which is a pathlib.Path object that pytest uses to provide a temporary directory for testing purposes.

You already have a temporary to-do database to use. Now you need some data to create your test cases:

These two dictionaries provide data to test Todoer.add(). The first two keys represent the data you’ll use as arguments to .add(), while the third key holds the expected return value of the method.

Now it’s time to write your first test function for .add(). With pytest, you can use parametrization to provide multiple sets of arguments and expected results to a single test function. This is a pretty neat feature. It makes a single test function behave like several test functions that run different test cases.

Here’s how you can create your test function using parametrization in pytest:

The @pytest.mark.parametrize() decorator marks test_add() for parametrization. When pytest runs this test, it calls test_add() two times. Each call uses one of the parameter sets from lines 7 to 11 and lines 12 to 16.

The string on line 5 holds descriptive names for the two required parameters and also a descriptive return value name. Note that test_add() has those same parameters. Additionally, the first parameter of test_add() has the same name as the fixture you just defined.

Inside test_add(), the code does the following actions:

Cool! You have a test that covers the main functionality of .add(). Now it’s time to run your test suite again. Go back to your command line and run python -m pytest tests/. You’ll get an output similar to the following:

The F letters in the highlighted line mean that two of your test cases have failed. Having failing tests is the first step for TDD. The second step is to write code for passing those tests. That’s what you’re going to do next.

Implement the add CLI Command

In this section, you’ll code .add() in the Todoer class. You’ll also code the add command in your Typer CLI. With these two pieces of code in place, your users will be able to add new items to their to-dos lists.

Every time the to-do application runs, it needs to access the Todoer class and connect the CLI with the database. To satisfy this requirement, you’ll implement a function called get_todoer().

Go back to your code editor and open cli.py. Type in the following code:

After updating the imports, you define get_todoer() on line 18. Line 19 defines a conditional that checks if the application’s configuration file exists. To do so, it uses Path.exists().

If the configuration file exists, then line 20 gets the path to the database from it. The else clause runs if the file doesn’t exist. This clause prints an error message to the screen and exits the application with an exit code of 1 to signal an error.

Line 27 checks if the path to the database exists. If so, then line 28 creates an instance of Todoer with the path as an argument. Otherwise, the else clause that starts on line 29 prints an error message and exits the application.

Now that you have an instance of Todoer with a valid database path, you can write .add(). Go back to the rptodo.py module and update Todoer:

Here’s how .add() works line by line:

Now you can run your test suite again to check if .add() works correctly. Go ahead and run python -m pytest tests/. You’ll get an output similar to the following:

The three green dots mean that you have three passing tests. If you downloaded the code from the project’s repo on GitHub, then you get an output with a few more successful tests.

Once you’ve finished writing .add(), you can head to cli.py and write the add command for your application’s CLI:

Here’s a breakdown of what the add command does:

Now you can go back to your terminal and give your add command a try:

In the first example, you execute the add command with the description "Get some milk" and a priority of 1. To set the priority, you use the -p option. Once you press Enter, the application adds the to-do and informs you about the successful addition. The second example works pretty similarly. This time you use --priority to set the to-do priority to 3.

In the third example, you provide a to-do description without supplying a priority. In this situation, the app uses the default priority value, which is 2.

In the fourth example, you try to add a new to-do with a priority of 5. Since this priority value is out of the allowed range, Typer displays a usage message along with an error message. Note that Typer automatically displays these messages for you. You don’t need to add extra code for this to happen.

Great! Your to-do application already has some cool functionality. Now you need a way to list all your to-dos to get an idea of how much work you have on your plate. In the following section, you’ll implement the list command to help you out with this task.

Implement the list Command

In this section, you’ll add the list command to your application’s CLI. This command will allow your users to list all their current to-dos. Before adding any code to your CLI, you need a way to retrieve the entire to-do list from the database. To accomplish this task, you’ll add .get_todo_list() to the Todoer class.

Open up rptodo.py in your code editor or IDE and add the following code:

Inside .get_todo_list(), you first get the entire to-do list from the database by calling .read_todos() on the database handler. The call to .read_todos() returns a named tuple, DBResponse, containing the to-do list and a return code. However, you just need the to-do list, so .get_todo_list() returns the .todo_list field only.

With .get_todo_list() in place, you can now implement the list command in the application’s CLI. Go ahead and add list_all() to cli.py:

Here’s how list_all() works:

If you run the application with the list command, then you get the following output:

This output shows all the current to-dos in a nicely formatted table. This way, your user can track the state of their list of tasks. Note that the output should display in blue font on your terminal window.

Step 6: Code the To-Do Completion Functionality

The next feature you’ll add to your to-do application is a Typer command that allows your users to set a given to-do as complete. This way, your users can track their progress and know how much work is left.

Again, you can download the code and all the resources for this section, including additional unit tests, by clicking the link below and going to the source_code_step_6/ directory:

As usual, you’ll start by coding the required functionality back in Todoer. In this case, you need a method that takes a to-do ID and marks the corresponding to-do as done. Go back to rptodo.py in your code editor and add the following code:

Your new .set_done() method does the required job. Here’s how:

With .set_done() in place, you can move to cli.py and write the complete command. Here’s the required code:

Take a look at how this code works line by line:

That’s it! Now you can give your new complete command a try. Back in your terminal window, run the following commands:

First, you list all your to-dos to visualize the ID that corresponds to each of them. Then you use complete to set the to-do with an ID of 1 as done. When you list the to-dos again, you see that the first to-do is now marked as True in the Done column.

An important detail to note about the complete command and the underlying Todoer.set_done() method is that the to-do ID is not a fixed value. If you remove one or more to-dos from the list, then the IDs of some remaining to-dos will change. Speaking of removing to-dos, that’s what you’ll do in the following section.

Step 7: Code the Remove To-Dos Functionality

Removing to-dos from the list is another useful feature you can add to your to-do application. In this section, you’ll add two new Typer commands to the app’s CLI using Python. The first command will be remove. It’ll allow your users to remove a to-do by its ID. The second command will be clear and will enable the users to remove all the current to-dos from the database.

You can download the code, unit tests, and other additional resources for this section by clicking the link below and going to the source_code_step_7/ directory:

Implement the remove CLI Command

To implement the remove command in your application’s CLI, you first need to code the underlying .remove() method in Todoer. This method will provide all the functionality to remove a single to-do from the list using the to-do ID. Remember that you set up the to-do ID to be an integer number associated with a specific to-do. To display the to-do IDs, run the list command.

Here’s how you can code .remove() in Todoer:

Here, your code does the following:

Now that you finished coding .remove() in Todoer, you can go to cli.py and add the remove command:

Wow! That’s a lot of code. Here’s how it works:

You can try out the remove command by running the following on your command line:

In this group of commands, you first list all the current to-dos with the list command. Then you try to remove the to-do with the ID number 1. This presents you with a yes (y) or no (N) confirmation prompt. If you press Enter, then the application runs the default option, N, and cancels the remove action.

In the third command, you explicitly supply a y answer, so the application removes the to-do with ID number 1. If you list all the to-dos again, then you see that the to-do "Get some milk." is no longer in the list. As an experiment, go ahead and try to use the --force or -f option or try to remove a to-do that’s not in the list.

Implement the clear CLI Command

In this section, you’ll implement the clear command. This command will allow your users to remove all the to-dos from the database. Underneath the clear command is the .remove_all() method from Todoer, which provides the back-end functionality.

Go back to rptodo.py and add .remove_all() at the end of Todoer:

Inside .remove_all(), you remove all the to-dos from the database by replacing the current to-do list with an empty list. For consistency, the method returns a CurrentTodo tuple with an empty dictionary and an appropriate return or error code.

Now you can implement the clear command in the application’s CLI:

Here’s how this code works:

To give this new clear command a try, go ahead and run the following on your terminal:

In the first example, you run clear. Once you press Enter, you get a prompt asking for yes (y) or no (N) confirmation. The uppercase N means that no is the default answer, so if you press Enter, you effectively cancel the clear operation.

In the second example, you run clear again. This time, you explicitly enter y as the answer to the prompt. This answer makes the application remove the entire to-do list from the database. When you run the list command, you get a message communicating that there are no tasks in the current to-do list.

That’s it! Now you have a functional CLI to-do application built with Python and Typer. Your application provides commands and options to create new to-dos, list all your to-dos, manage the to-do completion, and remove to-dos as needed. Isn’t that cool?

Conclusion

Building user-friendly command-line interface (CLI) applications is a fundamental skill to have as a Python developer. In the Python ecosystem, you’ll find several tools for creating this kind of application. Libraries such as argparse, Click, and Typer are good examples of those tools in Python. Here, you built a CLI application to manage a list of to-dos using Python and Typer.

In this tutorial, you learned how to:

You also practiced some additional skills, such as working with JSON files using Python’s json module and managing configuration files with Python’s configparser module. Now you’re ready to build command-line applications.

You can download the entire code and all the resources for this project by clicking the link below and going to the source_code_final/ directory:

Next Steps

In this tutorial, you’ve built a functional to-do application for your command line using Python and Typer. Even though the application provides only a minimal set of features, it’s a good starting point for you to continue adding features and keep learning in the process. This will help you take your Python skills to the next level.

Here are a few ideas you can implement to continue extending your to-do application:

These are just a few ideas. Take the challenge and build something cool on top of this project! You’ll learn a lot in the process.

Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Building a Python Command-Line To-Do App With Typer