Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Building the Initial To-Do App

00:00 In the previous lessons, you’ve created your development environment, which is a virtual environment that contains all the dependencies needed to build a type of CLI application.

00:10 You’ve also created a project structure with all the basic project files and packages needed. Now you’ll get started building your CLI to-do application.

00:20 You can open up the __init__.py file in your rptodo package. This is the main initializer file for your rptodo package and that means this file gets executed every time the package is called and read.

00:34 And in here you’ll be defining some settings and project variables. I have here a prefilled file-level documentation. You don’t have to have this in yours.

00:43 This is just to let me know what this file is about and what it does. Well, for you, you’ll be starting with a blank file. The first thing you’re going to do is define two package level variable names.

00:54 The first, the application name. To define this, you’ll have __app_name__ and the value in quotes rptodo. Next, the version __version__ and the value quotes 0.1.0.

01:14 This information will be useful if you decide to publish your package for others to use on, say, PyPI, Python’s Package Index. Next, you’ll be defining some operations, execution, or return code.

01:28 This is how you as a developer and the user will know when an execution succeeds or fails during your application’s operation. To do that, you can use Python’s range() function to get integer values that will represent these execution errors or response code range(7), which gives you integer values zero to six.

01:50 And to this, you’ll assign seven variables. The first will be SUCCESS. This will have an integer value zero and this is for when things work as expected.

02:03 Next, DIR_ERROR. This is for when you have directory manipulation operation failure. Next, FILE_ERROR for when any file manipulation operation fails.

02:16 Next to that, you have DB_WRITE_ERROR and DB_READ_ERROR for database read and write operations. And next, you have JSON_ERROR for errors relating to the manipulation of JSON files.

02:28 Finally, ID_ERROR for when an invalid to-do item ID is passed for any operation. Now having these error codes 0, 1, 2, 3, 4, 5, 6 might not be very descriptive if a user or even you, the developer, encounter them somewhere along the lines in the execution of your program.

02:47 So to enable better descriptive error messages, you’re going to take this code and map them using a dictionary to human-readable error messages. And to do that, you define an errors dictionary called ERRORS and the keys will be these variables you’ve defined up here and their values will be what each of them mean.

03:08 As SUCCESS is not an error, you can skip that and move over to the next one, which is DIR_ERROR. And this will stand for “Config directory error.” And next to that, FILE_ERROR, this will be “Config file error.” And next, DB_WRITE_ERROR.

03:32 And this will be “Database write error.” Next, DB_READ_ERROR. And this will be “Database read error.”

03:48 Next, JSON_ERROR. And this is “JSON operation error.” And finally, ID_ERROR, to-do id error.

04:01 The order of definition for the errors key-value items doesn’t matter because with Python dictionaries, you access the values using the key, unlike with Python tuples where each value is identified or accessed using the index value.

04:16 With all these defined, next, you’ll now proceed to create a minimal type of CLI application. And to do that, go ahead and open the cli.py module.

04:27 This is where you’ll create your main CLI Typer application and you will be creating an explicit Typer CLI application. First, you’ll be importing some important libraries and one of them is for type hinting or annotation from typing import Optional, and then you import the main Typer library.

04:49 And then from the just created rptodo package import those variables, ERRORS, APP_NAME, and VERSION.

05:01 And next, you initialize your main Typer application.

05:07 This app gives you an instance of a Typer application.

05:11 Now that you have a main Typer app, you’ll create what is called a main callback function. As you have seen, if you follow the Typer crash course, you can have several functions decorated to be Typer commands, but using a callback decorator is how you define the main CLI function.

05:28 To do this, just define the main function annotated to show that None is returned because you’re not expecting any return value from this function and in it just return.

05:40 This is just a regular function for now. To make this a Typer callback function, you’ll use a decorator, an app decorator with the Typer application you’ve just instantiated.

05:50 To do that, you just use @app.callback. Adding this app callback decorator to this main function makes this a main Typer callback function and the use of this main Typer callback function will be to print the version of your Typer application.

06:06 You can add a little docstring, which is always a good idea.

06:14 Then the one and only argument this main function will take is the version argument and once passed will enable Typer via another helper function you’ll be creating shortly, to print and display the version of the Typer application to the user.

06:31 Typer as a library depends heavily on type annotation. That’s how you interact with most of the functionalities. And to make this version a Typer command-line option, you would use the Annotated from typing_extensions,

06:46 which is a sub-dependency that gets installed if you install Typer from version 0.9 and above, which you should have. And this allows you to associate extra metadata with type hinting.

07:00 These metadata can be used by tools like linters, type checkers, and other frameworks and in your case, the Typer library to provide additional information about a specific type.

07:10 Annotated lets you describe additional context or constraints about a type without affecting the type itself. And here to use this, first of all, you specify that the data type for this version argument is going to be an optional Boolean. Optional, which means the program knows you can either pass it or not.

07:31 Next, you include the metadata, which is what tells Typer application that this is going to be a Typer CLI option. And to do so, you have typer.Option(

07:44 and in here you pass the various flags used by Typer to collect the value for the CLI option, the first of which would be a string --version.

07:55 And additionally you can also use -v. This is saying that to collect this option from the CLI, users can either use the --version flag or the -v flag.

08:09 Next, you have help or text to show users what this CLI option requires and what it does. And this just says it shows the application’s version and exits.

08:22 Next, you’re going to use what is known as a callback to specify what function gets executed when this function runs and the callback function to be executed you’ll create shortly and you’ll call _version_callback.

08:37 This doesn’t exist just yet. There’s a single underscore _version_callback. So now above the main callback function, create this helper callback.

08:51 This takes a parameter value: bool and returns.

08:57 Now can you check if value is passed and using typer.echo() or what f-strings, you’ll return the APP_NAME and the VERSION.

09:10 These are variables you’ve defined in your package initializer. Now the intended functionality is that after this is printed, the program just exits and to exit the program, you’d raise a typer.Exit() method. Now that you have a version callback, you can include that as the value of the callback.

09:30 And finally you’d add one more thing. It’s eager=True. Setting this is_eager argument tells Typer this version command line option has precedence over other commands in the current application.

09:46 As this is a Typer option, you can set a default value to None.

09:52 With all this code in place, you are now ready to create the application’s main entry point script. To do that, you can open the __main__.py file.

10:03 First, you import a few things from rptodo, import the cli module, and also the APP_NAME variable. With these imported, define a main function.

10:15 Then from your cli module, you can call the Typer app and with it pass it the program name for that to a variable called prog_name.

10:26 You’ll assign the value __app_name__.

10:32 This will serve as your app’s name. Then you’d run the main function. To do that, you check if __name__ == "__main__".

10:47 If true, you’d call the main function. After setting this up, you can go ahead to test your Typer CLI application for the first time. In your terminal, with the virtual environment activated, you can type python3 -m rptodo because you now have an rptodo package, you can call -v and you should see the application name and version just as you’ve defined in the version callback.

11:19 And the cool feature with Typer is that it automatically helps you with a documentation about your CLI application. To access this python3 -m rptodo --help.

11:32 You should now see some description about your CLI application.

11:36 Great, you’ve just completed initializing your CLI Typer application and are able to see the version of your application and also some help docs. In the next lesson, you’ll prepare your to-do database for use.

Avatar image for Vincenzo Fiorentini

Vincenzo Fiorentini on Feb. 11, 2025

Hi, I am using python 3.13.1, and installed typer 0.13.0 . After this lesson, I tried to get the version printed running the command in the rptodo_project dir. I get the error below.

Also, I find that if I run python in the project directory itself, nothing happens.

Any suggestion? Thanks, interesting lessons - Vincenzo

(toto) ~/rptodo_project/ python3 -m rptodo –version

Traceback (most recent call last):

File “<frozen runpy>”, line 198, in _run_module_as_main

File “<frozen runpy>”, line 88, in _run_code File “/Users/fiore/rptodo_project/rptodo/main.py”, line 1, in <module> from rptodo import cli,app_name File “/Users/fiore/rptodo_project/rptodo/cli.py”, line 14, in <module> @app.callback()

TypeError: Typer.callback() missing 1 required positional argument: ‘self’

Avatar image for Vincenzo Fiorentini

Vincenzo Fiorentini on Feb. 11, 2025

Sorry, I found a trivial error: missing brackets in the

app=typer.Typer()

call. Pushing forward!

Become a Member to join the conversation.