diff --git a/CHANGELOG.md b/CHANGELOG.md index 77dd5a0a..2c96b72d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added json.dumps parameters to print_json https://github.com/willmcgugan/rich/issues/1638 +### Added + +- Added dynamic_progress.py to examples + ### Fixed - Fixed an edge case bug when console module try to detect if they are in a tty at the end of a pytest run diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 0e299be6..41654aaa 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -8,6 +8,7 @@ The following people have contributed to the development of Rich: - [Pete Davison](https://github.com/pd93) - [James Estevez](https://github.com/jstvz) - [Oleksis Fraga](https://github.com/oleksis) +- [Kenneth Hoste](https://github.com/boegel) - [Finn Hughes](https://github.com/finnhughes) - [Josh Karpel](https://github.com/JoshKarpel) - [Andrew Kettmann](https://github.com/akettmann) diff --git a/docs/source/progress.rst b/docs/source/progress.rst index 5c70e329..364ad94c 100644 --- a/docs/source/progress.rst +++ b/docs/source/progress.rst @@ -192,7 +192,7 @@ If the :class:`~rich.progress.Progress` class doesn't offer exactly what you nee Multiple Progress ----------------- -You can't have different columns per task with a single Progress instance. However, you can have as many Progress instance as you like in a :ref:`live`. See `live_progress.py `_ for an example of using multiple Progress instances. +You can't have different columns per task with a single Progress instance. However, you can have as many Progress instance as you like in a :ref:`live`. See `live_progress.py `_ and `dynamic_progress.py `_ for examples of using multiple Progress instances. Example ------- diff --git a/examples/dynamic_progress.py b/examples/dynamic_progress.py new file mode 100644 index 00000000..c38da2eb --- /dev/null +++ b/examples/dynamic_progress.py @@ -0,0 +1,118 @@ +""" + +Demonstrates how to create a dynamic group of progress bars, +showing multi-level progress for multiple tasks (installing apps in the example), +each of which consisting of multiple steps. + +""" + +import time + +from rich.console import Group +from rich.panel import Panel +from rich.live import Live +from rich.progress import ( + BarColumn, + Progress, + SpinnerColumn, + TextColumn, + TimeElapsedColumn, +) + + +def run_steps(name, step_times, app_steps_task_id): + """Run steps for a single app, and update corresponding progress bars.""" + + for idx, step_time in enumerate(step_times): + # add progress bar for this step (time elapsed + spinner) + action = step_actions[idx] + step_task_id = step_progress.add_task("", action=action, name=name) + + # run steps, update progress + for _ in range(step_time): + time.sleep(0.5) + step_progress.update(step_task_id, advance=1) + + # stop and hide progress bar for this step when done + step_progress.stop_task(step_task_id) + step_progress.update(step_task_id, visible=False) + + # also update progress bar for current app when step is done + app_steps_progress.update(app_steps_task_id, advance=1) + + +# progress bar for current app showing only elapsed time, +# which will stay visible when app is installed +current_app_progress = Progress( + TimeElapsedColumn(), + TextColumn("{task.description}"), +) + +# progress bars for single app steps (will be hidden when step is done) +step_progress = Progress( + TextColumn(" "), + TimeElapsedColumn(), + TextColumn("[bold purple]{task.fields[action]}"), + SpinnerColumn("simpleDots"), +) +# progress bar for current app (progress in steps) +app_steps_progress = Progress( + TextColumn( + "[bold blue]Progress for app {task.fields[name]}: {task.percentage:.0f}%" + ), + BarColumn(), + TextColumn("({task.completed} of {task.total} steps done)"), +) +# overall progress bar +overall_progress = Progress( + TimeElapsedColumn(), BarColumn(), TextColumn("{task.description}") +) +# group of progress bars; +# some are always visible, others will disappear when progress is complete +progress_group = Group( + Panel(Group(current_app_progress, step_progress, app_steps_progress)), + overall_progress, +) + +# tuple specifies how long each step takes for that app +step_actions = ("downloading", "configuring", "building", "installing") +apps = [ + ("one", (2, 1, 4, 2)), + ("two", (1, 3, 8, 4)), + ("three", (2, 1, 3, 2)), +] + +# create overall progress bar +overall_task_id = overall_progress.add_task("", total=len(apps)) + +# use own live instance as context manager with group of progress bars, +# which allows for running multiple different progress bars in parallel, +# and dynamically showing/hiding them +with Live(progress_group): + + for idx, (name, step_times) in enumerate(apps): + # update message on overall progress bar + top_descr = "[bold #AAAAAA](%d out of %d apps installed)" % (idx, len(apps)) + overall_progress.update(overall_task_id, description=top_descr) + + # add progress bar for steps of this app, and run the steps + current_task_id = current_app_progress.add_task("Installing app %s" % name) + app_steps_task_id = app_steps_progress.add_task( + "", total=len(step_times), name=name + ) + run_steps(name, step_times, app_steps_task_id) + + # stop and hide steps progress bar for this specific app + app_steps_progress.update(app_steps_task_id, visible=False) + current_app_progress.stop_task(current_task_id) + current_app_progress.update( + current_task_id, description="[bold green]App %s installed!" % name + ) + + # increase overall progress now this task is done + overall_progress.update(overall_task_id, advance=1) + + # final update for message on overall progress bar + overall_progress.update( + overall_task_id, description="[bold green]%s apps installed, done!" % len(apps) + )