Progress Bar
If you are executing an operation that can take some time, you can inform it to the user with a progress bar.
For this, you can use typer.progressbar()
:
import time
import typer_cloup as 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
typer.echo(f"Processed {total} things.")
if __name__ == "__main__":
typer.run(main)
You 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.
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
¶
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_cloup as typer
def iterate_user_ids():
# Let's imagine this is a web API, not a range()
yield from range(100)
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
typer.echo(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
¶
You can also set a label
:
import time
import typer_cloup as 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
typer.echo(f"Processed {total} things.")
if __name__ == "__main__":
typer.run(main)
Check it:
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_cloup as 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)
typer.echo(f"Processed {total} things in batches.")
if __name__ == "__main__":
typer.run(main)
Check it: