mirror of
https://github.com/Textualize/rich.git
synced 2025-08-19 09:50:42 +00:00
track optimization
This commit is contained in:
parent
847a38ecd4
commit
59eee8930b
5 changed files with 87 additions and 16 deletions
|
@ -5,6 +5,13 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [4.1.0] - 2020-07-26
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Optimized progress.track for very quick iterations
|
||||||
|
- Force default size of 80x25 if get_terminal_size reports size of 0,0
|
||||||
|
|
||||||
## [4.0.0] - 2020-07-23
|
## [4.0.0] - 2020-07-23
|
||||||
|
|
||||||
Major version bump for a breaking change to `Text.stylize signature`, which corrects a minor but irritating API wart. The style now comes first and the `start` and `end` offsets default to the entire text. This allows for `text.stylize_all(style)` to be replaced with `text.stylize(style)`. The `start` and `end` offsets now support negative indexing, so `text.stylize("bold", -1)` makes the last character bold.
|
Major version bump for a breaking change to `Text.stylize signature`, which corrects a minor but irritating API wart. The style now comes first and the `start` and `end` offsets default to the entire text. This allows for `text.stylize_all(style)` to be replaced with `text.stylize(style)`. The `start` and `end` offsets now support negative indexing, so `text.stylize("bold", -1)` makes the last character bold.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "rich"
|
name = "rich"
|
||||||
homepage = "https://github.com/willmcgugan/rich"
|
homepage = "https://github.com/willmcgugan/rich"
|
||||||
documentation = "https://rich.readthedocs.io/en/latest/"
|
documentation = "https://rich.readthedocs.io/en/latest/"
|
||||||
version = "4.0.0"
|
version = "4.1.0"
|
||||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||||
authors = ["Will McGugan <willmcgugan@gmail.com>"]
|
authors = ["Will McGugan <willmcgugan@gmail.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
|
@ -386,12 +386,12 @@ LEGACY_WINDOWS_SUBSTITUTIONS = {
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_safe_box(box: None, legacy_windows: bool) -> None: # pragma: no cover
|
def get_safe_box(box: None, legacy_windows: bool) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def get_safe_box(box: Box, legacy_windows: bool) -> Box: # pragma: no cover
|
def get_safe_box(box: Box, legacy_windows: bool) -> Box:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -517,6 +517,9 @@ class Console:
|
||||||
return ConsoleDimensions(80, 25)
|
return ConsoleDimensions(80, 25)
|
||||||
|
|
||||||
width, height = shutil.get_terminal_size()
|
width, height = shutil.get_terminal_size()
|
||||||
|
# get_terminal_size can report 0, 0 if run from psuedo-terminal
|
||||||
|
width = width or 80
|
||||||
|
height = height or 25
|
||||||
return ConsoleDimensions(
|
return ConsoleDimensions(
|
||||||
(width - self.legacy_windows) if self._width is None else self._width,
|
(width - self.legacy_windows) if self._width is None else self._width,
|
||||||
height if self._height is None else self._height,
|
height if self._height is None else self._height,
|
||||||
|
|
|
@ -6,7 +6,7 @@ from datetime import timedelta
|
||||||
import io
|
import io
|
||||||
from math import ceil
|
from math import ceil
|
||||||
import sys
|
import sys
|
||||||
from time import monotonic
|
from time import monotonic, perf_counter
|
||||||
from threading import Event, RLock, Thread
|
from threading import Event, RLock, Thread
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
|
@ -53,6 +53,36 @@ ProgressType = TypeVar("ProgressType")
|
||||||
GetTimeCallable = Callable[[], float]
|
GetTimeCallable = Callable[[], float]
|
||||||
|
|
||||||
|
|
||||||
|
def iter_track(
|
||||||
|
values: ProgressType, update_period: float = 0.05
|
||||||
|
) -> Iterable[List[ProgressType]]:
|
||||||
|
"""Break a sequence in to chunks based on time.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
values (ProgressType): An iterable of values.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Iterable[List[ProgressType]]: An iterable containing lists of values from sequence.
|
||||||
|
|
||||||
|
"""
|
||||||
|
output: List[ProgressType] = []
|
||||||
|
append = output.append
|
||||||
|
get_time = perf_counter
|
||||||
|
period_size = 1
|
||||||
|
for value in values:
|
||||||
|
append(value)
|
||||||
|
if len(output) >= period_size:
|
||||||
|
start_time = get_time()
|
||||||
|
yield output
|
||||||
|
time_taken = get_time() - start_time
|
||||||
|
del output[:]
|
||||||
|
if abs(time_taken - update_period) > 0.2 * update_period:
|
||||||
|
period_size = period_size * (1.5 if time_taken < update_period else 0.8)
|
||||||
|
|
||||||
|
if output:
|
||||||
|
yield output
|
||||||
|
|
||||||
|
|
||||||
def track(
|
def track(
|
||||||
sequence: Union[Sequence[ProgressType], Iterable[ProgressType]],
|
sequence: Union[Sequence[ProgressType], Iterable[ProgressType]],
|
||||||
description="Working...",
|
description="Working...",
|
||||||
|
@ -66,6 +96,7 @@ def track(
|
||||||
complete_style: StyleType = "bar.complete",
|
complete_style: StyleType = "bar.complete",
|
||||||
finished_style: StyleType = "bar.finished",
|
finished_style: StyleType = "bar.finished",
|
||||||
pulse_style: StyleType = "bar.pulse",
|
pulse_style: StyleType = "bar.pulse",
|
||||||
|
update_period: float = 0.02,
|
||||||
) -> Iterable[ProgressType]:
|
) -> Iterable[ProgressType]:
|
||||||
"""Track progress by iterating over a sequence.
|
"""Track progress by iterating over a sequence.
|
||||||
|
|
||||||
|
@ -80,7 +111,8 @@ def track(
|
||||||
style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
|
style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
|
||||||
complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
|
complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
|
||||||
finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.done".
|
finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.done".
|
||||||
pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
|
pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
|
||||||
|
update_period (float, optional): Minimum time (in seconds) between calls to update(). Defaults to 0.05.
|
||||||
Returns:
|
Returns:
|
||||||
Iterable[ProgressType]: An iterable of the values in the sequence.
|
Iterable[ProgressType]: An iterable of the values in the sequence.
|
||||||
|
|
||||||
|
@ -120,10 +152,13 @@ def track(
|
||||||
)
|
)
|
||||||
|
|
||||||
task_id = progress.add_task(description, total=task_total)
|
task_id = progress.add_task(description, total=task_total)
|
||||||
|
advance = progress.advance
|
||||||
with progress:
|
with progress:
|
||||||
for completed, value in enumerate(sequence, 1):
|
for values in iter_track(
|
||||||
yield value
|
sequence, update_period=update_period if auto_refresh else 0
|
||||||
progress.update(task_id, completed=completed)
|
):
|
||||||
|
yield from values
|
||||||
|
advance(task_id, len(values))
|
||||||
if not progress.auto_refresh:
|
if not progress.auto_refresh:
|
||||||
progress.refresh()
|
progress.refresh()
|
||||||
|
|
||||||
|
@ -390,13 +425,15 @@ class Task:
|
||||||
"""Optional[float]: Get the estimated speed in steps per second."""
|
"""Optional[float]: Get the estimated speed in steps per second."""
|
||||||
if self.start_time is None:
|
if self.start_time is None:
|
||||||
return None
|
return None
|
||||||
progress = list(self._progress)
|
progress = self._progress
|
||||||
if not progress:
|
if not progress:
|
||||||
return None
|
return None
|
||||||
total_time = progress[-1].timestamp - progress[0].timestamp
|
total_time = progress[-1].timestamp - progress[0].timestamp
|
||||||
if total_time == 0:
|
if total_time == 0:
|
||||||
return None
|
return None
|
||||||
total_completed = sum(sample.completed for sample in progress[1:])
|
iter_progress = iter(progress)
|
||||||
|
next(iter_progress)
|
||||||
|
total_completed = sum(sample.completed for sample in iter_progress)
|
||||||
speed = total_completed / total_time
|
speed = total_completed / total_time
|
||||||
return speed
|
return speed
|
||||||
|
|
||||||
|
@ -611,6 +648,7 @@ class Progress(JupyterMixin, RenderHook):
|
||||||
total: int = None,
|
total: int = None,
|
||||||
task_id: Optional[TaskID] = None,
|
task_id: Optional[TaskID] = None,
|
||||||
description="Working...",
|
description="Working...",
|
||||||
|
update_period: float = 0.05,
|
||||||
) -> Iterable[ProgressType]:
|
) -> Iterable[ProgressType]:
|
||||||
"""Track progress by iterating over a sequence.
|
"""Track progress by iterating over a sequence.
|
||||||
|
|
||||||
|
@ -619,6 +657,7 @@ class Progress(JupyterMixin, RenderHook):
|
||||||
total: (int, optional): Total number of steps. Default is len(sequence).
|
total: (int, optional): Total number of steps. Default is len(sequence).
|
||||||
task_id: (TaskID): Task to track. Default is new task.
|
task_id: (TaskID): Task to track. Default is new task.
|
||||||
description: (str, optional): Description of task, if new task is created.
|
description: (str, optional): Description of task, if new task is created.
|
||||||
|
update_period (float, optional): Minimum time (in seconds) between calls to update(). Defaults to 0.05.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Iterable[ProgressType]: An iterable of values taken from the provided sequence.
|
Iterable[ProgressType]: An iterable of values taken from the provided sequence.
|
||||||
|
@ -638,9 +677,14 @@ class Progress(JupyterMixin, RenderHook):
|
||||||
else:
|
else:
|
||||||
self.update(task_id, total=task_total)
|
self.update(task_id, total=task_total)
|
||||||
with self:
|
with self:
|
||||||
for completed, value in enumerate(sequence, 1):
|
advance = self.advance
|
||||||
yield value
|
for values in iter_track(
|
||||||
self.update(task_id, completed=completed)
|
sequence, update_period=update_period if self.auto_refresh else 0
|
||||||
|
):
|
||||||
|
yield from values
|
||||||
|
advance(task_id, len(values))
|
||||||
|
if not self.auto_refresh:
|
||||||
|
progress.refresh()
|
||||||
|
|
||||||
def start_task(self, task_id: TaskID) -> None:
|
def start_task(self, task_id: TaskID) -> None:
|
||||||
"""Start a task.
|
"""Start a task.
|
||||||
|
@ -716,9 +760,12 @@ class Progress(JupyterMixin, RenderHook):
|
||||||
old_sample_time = current_time - self.speed_estimate_period
|
old_sample_time = current_time - self.speed_estimate_period
|
||||||
_progress = task._progress
|
_progress = task._progress
|
||||||
|
|
||||||
|
popleft = _progress.popleft
|
||||||
while _progress and _progress[0].timestamp < old_sample_time:
|
while _progress and _progress[0].timestamp < old_sample_time:
|
||||||
_progress.popleft()
|
popleft()
|
||||||
task._progress.append(ProgressSample(current_time, update_completed))
|
while len(_progress) > 10:
|
||||||
|
popleft()
|
||||||
|
_progress.append(ProgressSample(current_time, update_completed))
|
||||||
if refresh:
|
if refresh:
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
|
@ -729,7 +776,21 @@ class Progress(JupyterMixin, RenderHook):
|
||||||
task_id (TaskID): ID of task.
|
task_id (TaskID): ID of task.
|
||||||
advance (float): Number of steps to advance. Default is 1.
|
advance (float): Number of steps to advance. Default is 1.
|
||||||
"""
|
"""
|
||||||
self.update(task_id, advance=advance)
|
current_time = self.get_time()
|
||||||
|
with self._lock:
|
||||||
|
task = self._tasks[task_id]
|
||||||
|
completed_start = task.completed
|
||||||
|
task.completed += advance
|
||||||
|
update_completed = task.completed - completed_start
|
||||||
|
old_sample_time = current_time - self.speed_estimate_period
|
||||||
|
_progress = task._progress
|
||||||
|
|
||||||
|
popleft = _progress.popleft
|
||||||
|
while _progress and _progress[0].timestamp < old_sample_time:
|
||||||
|
popleft()
|
||||||
|
while len(_progress) > 10:
|
||||||
|
popleft()
|
||||||
|
_progress.append(ProgressSample(current_time, update_completed))
|
||||||
|
|
||||||
def refresh(self) -> None:
|
def refresh(self) -> None:
|
||||||
"""Refresh (render) the progress information."""
|
"""Refresh (render) the progress information."""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue