rich icon indicating copy to clipboard operation
rich copied to clipboard

Add pause() and resume() methods to progress bars

Open nottings opened this issue 3 years ago • 7 comments

A couple problems I'm noticing:

  1. If prompted while a progress bar is in use, the user typing is done at the end of the progress bar. If auto_refresh is True, you cannot even see what you type as you type it. Example output below is with auto_refresh=False
  2. When using choices=[] and default="" There are escape characters displayed, so it is pretty un-readable.
What value would you like to enter?:
Playbook running... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━   0% -:--:--foobarbaz
demo action requiring user prompt: The user responded with foobarbaz
Choose a value. [1;35m[Scott/Alex/Sam][0m [1;36m(Scott)[0m:
Playbook running... ━━━━━━━━━━━━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━  50% -:--:--Sam
demo prompt for user choice: The user chose Sam

I suspect both of these things are related to the fact that the prompt is occurring within a with Progress() as progress: context

nottings avatar Oct 01 '21 17:10 nottings

I'm afraid input() (as used by Prompt) will not work with progress bars.

willmcgugan avatar Oct 01 '21 17:10 willmcgugan

bummer. Is there anyway to temporarily hide the Progress if I know a prompt will occur within that context; and then re-display the bar after input is done?

nottings avatar Oct 01 '21 17:10 nottings

Not currently. But it may grow pause() and resume() methods in the future. You might want to post a feature request for that.

willmcgugan avatar Oct 01 '21 17:10 willmcgugan

Many thanks!

nottings avatar Oct 01 '21 17:10 nottings

I'd like to second this. I'm building a log parser and displaying a progress bar so the user knows how far along they are. Asking for input is obscured by the progress bar. Just started using rich today. Thanks for such a great library!

goldstar611 avatar Oct 01 '21 18:10 goldstar611

I found out that start() and stop() seem to achieve a similar effect (assuming you don't care about showing the progress bar during the input/Prompt). I put together a simple context manager that does just that (for simple cases):

from rich.progress import Progress
class PauseProgress:
    def __init__(self, progress: Progress) -> None:
        self._progress = progress

    def _clear_line(self) -> None:
        UP = "\x1b[1A"
        CLEAR = "\x1b[2K"
        for _ in self._progress.tasks:
            print(UP + CLEAR + UP)

    def __enter__(self):
        self._progress.stop()
        self._clear_line()
        return self._progress

    def __exit__(self, exc_type, exc_value, exc_traceback):
        self._progress.start()

Here is a simple usage example, where work_with_input() simulates a task with prompt/input from the user:

import time
from rich.prompt import Confirm
from rich.progress import Progress

progress = Progress()

def work() -> bool:
    time.sleep(1)
    return True

def work_with_input() -> bool:
    with PauseProgress(progress) as p:
        if not Confirm.ask(prompt=f"Stopped at job {i}. Continue?"):
            return False
    return work()

with progress as pbar:
    jobs = [i for i in range(10)]
    task = pbar.add_task("task", total=len(jobs))

    for i in jobs:
        print(f"Running job {i}!")

        if i == 5:
            ret = work_with_input()
        else:
            ret = work()

        if not ret:
            break

        pbar.advance(task)

leocencetti avatar Sep 08 '23 14:09 leocencetti

Building on @leocencetti's answer, instead of manually clearing the prompt by printing the control characters you can set progress.live.transient = True before stopping it. Also, if you want it to not erase any lines when resuming, you can print the right number of newlines before restarting it.

@contextmanager
def hide(progress: Progress):
    transient = progress.live.transient # save the old value
    progress.live.transient = True
    progress.stop()
    progress.live.transient = transient # restore the old value
    try:
        yield
    finally:
        # make space for the progress to use so it doesn't overwrite any previous lines
        print("\n" * (len(progress.tasks) - 2))
        progress.start()

abrahammurciano avatar Oct 03 '23 16:10 abrahammurciano