diff --git a/docs/src/progressbar/tutorial001.py b/docs/src/progressbar/tutorial001.py new file mode 100644 index 0000000..b609a6e --- /dev/null +++ b/docs/src/progressbar/tutorial001.py @@ -0,0 +1,17 @@ +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 + typer.echo(f"Processed {total} things.") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs/src/progressbar/tutorial002.py b/docs/src/progressbar/tutorial002.py new file mode 100644 index 0000000..45f40d9 --- /dev/null +++ b/docs/src/progressbar/tutorial002.py @@ -0,0 +1,23 @@ +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 + typer.echo(f"Processed {total} user IDs.") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs/src/progressbar/tutorial003.py b/docs/src/progressbar/tutorial003.py new file mode 100644 index 0000000..579a28d --- /dev/null +++ b/docs/src/progressbar/tutorial003.py @@ -0,0 +1,17 @@ +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 + typer.echo(f"Processed {total} things.") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs/src/progressbar/tutorial004.py b/docs/src/progressbar/tutorial004.py new file mode 100644 index 0000000..957630b --- /dev/null +++ b/docs/src/progressbar/tutorial004.py @@ -0,0 +1,17 @@ +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(2500) + typer.echo(f"Processed {total} things in batches.") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs/tutorial/progressbar.md b/docs/tutorial/progressbar.md new file mode 100644 index 0000000..af72989 --- /dev/null +++ b/docs/tutorial/progressbar.md @@ -0,0 +1,139 @@ +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()`: + +```Python hl_lines="8" +{!./src/progressbar/tutorial001.py!} +``` + +You use `typer.progressbar()` with a `with` statement, as in: + +```Python +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: + +```Python +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: + +```Python +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: + +
+ +```console +$ 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()`. + +```Python hl_lines="14" +{!./src/progressbar/tutorial002.py!} +``` + +Check it: + +
+ +```console +$ 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: + +```Python +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`: + +```Python hl_lines="8" +{!./src/progressbar/tutorial003.py!} +``` + +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: + +```Python hl_lines="8 12" +{!./src/progressbar/tutorial004.py!} +``` + +Check it: + +
+python main.py + +Processed 100 things in batches. +
diff --git a/mkdocs.yml b/mkdocs.yml index 293fee4..b790c69 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -48,6 +48,7 @@ nav: - Path: 'tutorial/parameter-types/path.md' - File: 'tutorial/parameter-types/file.md' - Ask with Prompt: 'tutorial/prompt.md' + - Progress Bar: 'tutorial/progressbar.md' - Alternatives, Inspiration and Comparisons: 'alternatives.md' - Help Typer - Get Help: 'help-typer.md' - Development - Contributing: 'contributing.md' diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial002.py b/tests/test_tutorial/test_commands/test_help/test_tutorial002.py index 1413247..95d4bd9 100644 --- a/tests/test_tutorial/test_commands/test_help/test_tutorial002.py +++ b/tests/test_tutorial/test_commands/test_help/test_tutorial002.py @@ -26,7 +26,7 @@ def test_help_create(): assert "Some internal utility function to create." not in result.output -def test_help_create(): +def test_help_delete(): result = runner.invoke(app, ["delete", "--help"]) assert result.exit_code == 0 assert "Delete a user with USERNAME." in result.output