Skip to content

Commit

Permalink
added transient switch to progress and live render
Browse files Browse the repository at this point in the history
  • Loading branch information
willmcgugan committed Jun 11, 2020
1 parent f9ac1c2 commit c6c197d
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 15 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
21 changes: 16 additions & 5 deletions docs/source/progress.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,34 @@ 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.

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.

Expand Down Expand Up @@ -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
~~~~~~~~~

Expand All @@ -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
-------

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>"]
license = "MIT"
Expand Down
3 changes: 2 additions & 1 deletion rich/constrain.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
25 changes: 24 additions & 1 deletion rich/live_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,31 @@


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
Expand All @@ -30,6 +42,17 @@ def position_cursor(self) -> Control:
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:
Expand Down
1 change: 0 additions & 1 deletion rich/markup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
21 changes: 17 additions & 4 deletions rich/progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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:
Expand Down Expand Up @@ -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__(
Expand All @@ -378,6 +387,7 @@ def __init__(
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"
Expand All @@ -392,6 +402,7 @@ def __init__(
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())
Expand Down Expand Up @@ -449,6 +460,8 @@ def stop(self) -> None:
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()
Expand All @@ -467,13 +480,13 @@ def track(
"""[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):
Expand Down Expand Up @@ -786,7 +799,7 @@ def log(

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)
Expand Down

0 comments on commit c6c197d

Please sign in to comment.