rich
rich copied to clipboard
Add pause() and resume() methods to progress bars
A couple problems I'm noticing:
- 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
- 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
I'm afraid input() (as used by Prompt) will not work with progress bars.
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?
Not currently. But it may grow pause()
and resume()
methods in the future. You might want to post a feature request for that.
Many thanks!
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!
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)
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()