added transient switch to progress and live render

This commit is contained in:
Will McGugan 2020-06-11 15:10:32 +01:00
parent f9ac1c2f54
commit c6c197da56
7 changed files with 66 additions and 15 deletions

View File

@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.2] - Unreleased
## [2.1.0] - Unreleased
### Added
- Added 'transient' option to Progress
### Changed
@ -470,4 +474,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- First official release, API still bto be stabilized
- First official release, API still to be stabilized

View File

@ -44,13 +44,24 @@ Here's a simple example::
progress.update(task3, advance=0.9)
time.sleep(0.02)
Starting Tasks
Transient progress
~~~~~~~~~~~~~~~~~~
Normally when you exit the progress context manager (or call :meth:`~rich.progress.Progress.stop`) the last refreshed display remains in the terminal with the cursor on the following line. You can also make the progress display disappear on exit by setting ``transient=False`` on the Progress constructor. Here's an example
with Progress(transient=False) as progress:
task = progress.add_task("Working", total=100)
do_work(task)
Transient progress displays are useful if you want more minimal output in the terminal when tasks are complete.
Starting tasks
~~~~~~~~~~~~~~
When you add a task it is automatically *started* which means it will show a progress bar at 0% and the time remaining will be calculated from the current time. Occasionally this may not work well if there is a long delay before you can start updating progress, you may need to wait for a response from a server, or count files in a directory (for example) before you can begin tracking progress. In these cases you can call :meth:`~rich.progress.Progress.add_task` with ``start=False`` which will display a pulsing animation that lets the user know something is working. When you have the number of steps you can call :meth:`~rich.progress.Progress.start_task` which will display the progress bar at 0%, then :meth:`~rich.progress.Progress.update` as normal.
Updating Tasks
Updating tasks
~~~~~~~~~~~~~~
When you add a task you get back a `Task ID`. Use this ID to call :meth:`~rich.progress.Progress.update` whenever you have completed some work, or any information has changed.
@ -58,7 +69,9 @@ When you add a task you get back a `Task ID`. Use this ID to call :meth:`~rich.p
Auto refresh
~~~~~~~~~~~~
By default, the progress information will auto refresh at 10 times a second. Refreshing in a predictable rate can make numbers more readable if they are updating very quickly. Auto refresh can also prevent excessive rendering to the terminal.
By default, the progress information will refresh 10 times a second. Refreshing at a predictable rate can make numbers more readable if they are updating quickly. Auto refresh can also prevent excessive rendering to the terminal.
You can set the refresh rate with the ``refresh_per_second`` argument on the :class:`~rich.progress.Progress` constructor. You could set this to something lower than 10 if you know your updates will not be that frequent.
You can disable auto-refresh by setting ``auto_refresh=False`` on the constructor and call :meth:`~rich.progress.Progress.refresh` manually when there are updates to display.
@ -93,7 +106,6 @@ Print / log
When a progress display is running, printing or logging anything directly to the console will break the visuals. To work around this, the Progress class provides :meth:`~rich.progress.Progress.print` and :meth:`~rich.progress.Progress.log` which work the same as their counterparts on :class:`~rich.console.Console` but will move the cursor and refresh automatically -- ensure that everything renders properly.
Extending
~~~~~~~~~
@ -106,7 +118,6 @@ If the progress API doesn't offer exactly what you need in terms of a progress d
def get_renderables(self):
yield Panel(self.make_tasks_table(self.tasks))
Example
-------

View File

@ -2,7 +2,7 @@
name = "rich"
homepage = "https://github.com/willmcgugan/rich"
documentation = "https://rich.readthedocs.io/en/latest/"
version = "2.0.1"
version = "2.1.0"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
authors = ["Will McGugan <willmcgugan@gmail.com>"]
license = "MIT"

View File

@ -1,10 +1,11 @@
from typing import Optional
from .console import Console, ConsoleOptions, RenderableType, RenderResult
from .jupyter import JupyterMixin
from .measure import Measurement
class Constrain:
class Constrain(JupyterMixin):
"""Constrain the width of a renderable to a given number of characters.
Args:

View File

@ -8,19 +8,31 @@ from ._loop import loop_last
class LiveRender:
"""Creates a renderable that may be updated.
Args:
renderable (RenderableType): Any renderable object.
style (StyleType, optional): An optional style to apply to the renderable. Defaults to "".
"""
def __init__(self, renderable: RenderableType, style: StyleType = "") -> None:
self.renderable = renderable
self.style = style
self._shape: Optional[Tuple[int, int]] = None
def set_renderable(self, renderable: RenderableType) -> None:
"""Set a new renderable.
Args:
renderable (RenderableType): Any renderable object, including str.
"""
self.renderable = renderable
def position_cursor(self) -> Control:
"""Get control codes to move cursor to beggining of live render.
Returns:
str: String containing control codes.
Control: A control instance that may be printed.
"""
if self._shape is not None:
_, height = self._shape
@ -30,6 +42,17 @@ class LiveRender:
return Control("\r\x1b[2K")
return Control("")
def restore_cursor(self) -> Control:
"""Get control codes to clear the render and restore the cursor to its previous position.
Returns:
Control: A Control instance that may be printed.
"""
if self._shape is not None:
_, height = self._shape
return Control("\r" + "\x1b[1A\x1b[2K" * height)
return Control("")
def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:

View File

@ -50,7 +50,6 @@ def _parse(markup: str) -> Iterable[Tuple[int, Optional[str], Optional[Tag]]]:
"""
position = 0
normalize = Style.normalize
for match in re_tags.finditer(markup):
escape_open, escape_close, tag_text = match.groups()
start, end = match.span()

View File

@ -50,6 +50,7 @@ def track(
total: int = None,
auto_refresh=True,
console: Optional[Console] = None,
transient: bool = False,
get_time: Callable[[], float] = None,
) -> Iterable[ProgressType]:
"""Track progress of processing a sequence.
@ -59,13 +60,19 @@ def track(
description (str, optional): Description of task show next to progress bar. Defaults to "Working".
total: (int, optional): Total number of steps. Default is len(sequence).
auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True.
transient: (bool, optional): Clear the progress on exit. Defaults to False.
console (Console, optional): Console to write to. Default creates internal Console instance.
Returns:
Iterable[ProgressType]: An iterable of the values in the sequence.
"""
progress = Progress(auto_refresh=auto_refresh, console=console, get_time=get_time)
progress = Progress(
auto_refresh=auto_refresh,
console=console,
transient=transient,
get_time=get_time,
)
task_total = total
if task_total is None:
@ -369,6 +376,8 @@ class Progress:
auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()`.
refresh_per_second (int, optional): Number of times per second to refresh the progress information. Defaults to 10.
speed_estimate_period: (float, optional): Period (in seconds) used to calculate the speed estimate. Defaults to 30.
transient: (bool, optional): Clear the progress on exit. Defaults to False.
get_time: (Callable, optional): A callable that gets the current time, or None to use time.monotonic. Defaults to None.
"""
def __init__(
@ -378,6 +387,7 @@ class Progress:
auto_refresh: bool = True,
refresh_per_second: int = 10,
speed_estimate_period: float = 30.0,
transient: bool = False,
get_time: GetTimeCallable = None,
) -> None:
assert refresh_per_second > 0, "refresh_per_second must be > 0"
@ -392,6 +402,7 @@ class Progress:
self.auto_refresh = auto_refresh
self.refresh_per_second = refresh_per_second
self.speed_estimate_period = speed_estimate_period
self.transient = transient
self.get_time = get_time or monotonic
self._tasks: Dict[TaskID, Task] = {}
self._live_render = LiveRender(self.get_renderable())
@ -449,6 +460,8 @@ class Progress:
if self._refresh_thread is not None:
self._refresh_thread.join()
self._refresh_thread = None
if self.transient:
self.console.control(self._live_render.restore_cursor())
def __enter__(self) -> "Progress":
self.start()
@ -467,13 +480,13 @@ class Progress:
"""[summary]
Args:
sequence (Sequence[ProgressType]): [description]
sequence (Sequence[ProgressType]): A sequence of values you want to iterate over and track progress.
total: (int, optional): Total number of steps. Default is len(sequence).
task_id: (TaskID): Task to track. Default is new task.
description: (str, optional): Description of task, if new task is created.
Returns:
Iterable[ProgressType]: [description]
Iterable[ProgressType]: An iterable of values taken from the provided sequence.
"""
if total is None:
if isinstance(sequence, Sized):
@ -786,7 +799,7 @@ yield True, previous_value''',
examples = cycle(progress_renderables)
with Progress() as progress:
with Progress(transient=True) as progress:
task1 = progress.add_task(" [red]Downloading", total=1000)
task2 = progress.add_task(" [green]Processing", total=1000)