Skip to content

Progress Bar

If you are executing an operation that can take some time, you can inform it to the user. 🤓

Progress Bar

You can use Rich's Progress Display to show a progress bar, for example:

import time

import typer
from rich.progress import track


def main():
    total = 0
    for value in track(range(100), description="Processing..."):
        # Fake processing time
        time.sleep(0.01)
        total += 1
    print(f"Processed {total} things.")


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

You put the thing that you want to iterate over inside of Rich's track(), and then iterate over that.

Check it:

$ python main.py

---> 100%

Processed 100 things.

...actually, it will look a lot prettier. ✨ But I can't show you the animation here in the docs. 😅

The colors and information will look something like this:

$ python main.py

Processing... <font color="#F92672">━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╸</font><font color="#3A3A3A">━━━━━━━━━━</font> <font color="#AE81FF"> 74%</font> <font color="#A1EFE4">0:00:01</font>

Spinner

When you don't know how long the operation will take, you can use a spinner instead.

Rich allows you to display many things in complex and advanced ways.

For example, this will show two spinners:

import time

import typer
from rich.progress import Progress, SpinnerColumn, TextColumn


def main():
    with Progress(
        SpinnerColumn(),
        TextColumn("[progress.description]{task.description}"),
        transient=True,
    ) as progress:
        progress.add_task(description="Processing...", total=None)
        progress.add_task(description="Preparing...", total=None)
        time.sleep(5)
    print("Done!")


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

I can't show you the beautiful animation here in the docs. 😅

But at some point in time it will look like this (imagine it's spinning). 🤓

$ python main.py

<font color="#A6E22E">⠹</font> Processing...
<font color="#A6E22E">⠹</font> Preparing...

You can learn more about it in the Rich docs for Progress Display.

Typer progressbar

If you can, you should use Rich as explained above, it has more features, it's more advanced, and can display information more beautifully. ✨

Tip

If you can use Rich, use the information above, the Rich docs, and skip the rest of this page. 😎

But if you can't use Rich, Typer (actually Click) comes with a simple utility to show progress bars.

Info

typer.progressbar() comes directly from Click, you can read more about it in Click's docs.

Use typer.progressbar

Tip

Remember, you are much better off using Rich for this. 😎

You can use typer.progressbar() with a with statement, as in:

with typer.progressbar(something) as progress:
    pass

And you pass as function argument to typer.progressbar() the thing that you would normally iterate over.

import time

import typer


def main():
    total = 0
    with typer.progressbar(range(100)) as progress:
        for value in progress:
            # Fake processing time
            time.sleep(0.01)
            total += 1
    print(f"Processed {total} things.")


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

So, if you have a list of users, this could be:

users = ["Camila", "Rick", "Morty"]

with typer.progressbar(users) as progress:
    pass

And the with statement using typer.progressbar() gives you an object that you can iterate over, just like if it was the same thing that you would iterate over normally.

But by iterating over this object Typer (actually Click) will know to update the progress bar:

users = ["Camila", "Rick", "Morty"]

with typer.progressbar(users) as progress:
    for user in progress:
        typer.echo(user)

Tip

Notice that there are 2 levels of code blocks. One for the with statement and one for the for statement.

Info

This is mostly useful for operations that take some time.

In the example above we are faking it with time.sleep().

Check it:

$ python main.py

---> 100%

Processed 100 things.

Setting a Progress Bar length

Tip

Remember, you are much better off using Rich for this. 😎

The progress bar is generated from the length of the iterable (e.g. the list of users).

But if the length is not available (for example, with something that fetches a new user from a web API each time) you can pass an explicit length to typer.progressbar().

import time

import typer


def iterate_user_ids():
    # Let's imagine this is a web API, not a range()
    for i in range(100):
        yield i


def main():
    total = 0
    with typer.progressbar(iterate_user_ids(), length=100) as progress:
        for value in progress:
            # Fake processing time
            time.sleep(0.01)
            total += 1
    print(f"Processed {total} user IDs.")


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

Check it:

$ python main.py

---> 100%

Processed 100 user IDs.

About the function with yield

If you hadn't seen something like that yield above, that's a "generator".

You can iterate over that function with a for and at each iteration it will give you the value at yield.

yield is like a return that gives values multiple times and let's you use the function in a for loop.

For example:

def iterate_user_ids():
    # Let's imagine this is a web API, not a range()
    for i in range(100):
        yield i

for i in iterate_user_ids():
    print(i)

would print each of the "user IDs" (here it's just the numbers from 0 to 99).

Add a label

Tip

Remember, you are much better off using Rich for this. 😎

You can also set a label:

import time

import typer


def main():
    total = 0
    with typer.progressbar(range(100), label="Processing") as progress:
        for value in progress:
            # Fake processing time
            time.sleep(0.01)
            total += 1
    print(f"Processed {total} things.")


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

Check it:

python main.py Processed 100 things.

Iterate manually

If you need to manually iterate over something and update the progress bar irregularly, you can do it by not passing an iterable but just a length to typer.progressbar().

And then calling the .update() method in the object from the with statement:

import time

import typer


def main():
    total = 1000
    with typer.progressbar(length=total) as progress:
        for batch in range(4):
            # Fake processing time
            time.sleep(1)
            progress.update(250)
    print(f"Processed {total} things in batches.")


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

Check it:

python main.py Processed 100 things in batches.