mirror of
https://github.com/Textualize/rich.git
synced 2025-08-19 09:50:42 +00:00
add new render hooks for transparent progress
This commit is contained in:
parent
8b5a8ddf07
commit
f8b45a87be
4 changed files with 99 additions and 101 deletions
101
rich/console.py
101
rich/console.py
|
@ -235,6 +235,15 @@ class ConsoleThreadLocals(threading.local):
|
||||||
buffer_index: int = 0
|
buffer_index: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
class RenderHook:
|
||||||
|
"""Provides hooks in to render process."""
|
||||||
|
|
||||||
|
def process_renderables(
|
||||||
|
self, renderables: List[ConsoleRenderable]
|
||||||
|
) -> List[ConsoleRenderable]:
|
||||||
|
return renderables
|
||||||
|
|
||||||
|
|
||||||
def detect_legacy_windows() -> bool:
|
def detect_legacy_windows() -> bool:
|
||||||
"""Detect legacy Windows."""
|
"""Detect legacy Windows."""
|
||||||
return "WINDIR" in os.environ and "WT_SESSION" not in os.environ
|
return "WINDIR" in os.environ and "WT_SESSION" not in os.environ
|
||||||
|
@ -325,6 +334,7 @@ class Console:
|
||||||
self._record_buffer_lock = threading.RLock()
|
self._record_buffer_lock = threading.RLock()
|
||||||
self._thread_locals = ConsoleThreadLocals()
|
self._thread_locals = ConsoleThreadLocals()
|
||||||
self._record_buffer: List[Segment] = []
|
self._record_buffer: List[Segment] = []
|
||||||
|
self._render_hooks: List[RenderHook] = []
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<console width={self.width} {str(self._color_system)}>"
|
return f"<console width={self.width} {str(self._color_system)}>"
|
||||||
|
@ -368,6 +378,19 @@ class Console:
|
||||||
self._buffer_index -= 1
|
self._buffer_index -= 1
|
||||||
self._check_buffer()
|
self._check_buffer()
|
||||||
|
|
||||||
|
def push_render_hook(self, hook: RenderHook) -> None:
|
||||||
|
"""Add a new render hook to the stack.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hook (RenderHook): Render hook instance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._render_hooks.append(hook)
|
||||||
|
|
||||||
|
def pop_render_hook(self) -> None:
|
||||||
|
"""Pop the last renderhook from the stack."""
|
||||||
|
self._render_hooks.pop()
|
||||||
|
|
||||||
def __enter__(self) -> "Console":
|
def __enter__(self) -> "Console":
|
||||||
"""Own context manager to enter buffer context."""
|
"""Own context manager to enter buffer context."""
|
||||||
self._enter_buffer()
|
self._enter_buffer()
|
||||||
|
@ -684,14 +707,15 @@ class Console:
|
||||||
if rich_cast:
|
if rich_cast:
|
||||||
renderable = rich_cast()
|
renderable = rich_cast()
|
||||||
if isinstance(renderable, str):
|
if isinstance(renderable, str):
|
||||||
append_text(
|
if renderable:
|
||||||
self.render_str(
|
append_text(
|
||||||
renderable,
|
self.render_str(
|
||||||
emoji=emoji,
|
renderable,
|
||||||
markup=markup,
|
emoji=emoji,
|
||||||
highlighter=_highlighter,
|
markup=markup,
|
||||||
|
highlighter=_highlighter,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
elif isinstance(renderable, ConsoleRenderable):
|
elif isinstance(renderable, ConsoleRenderable):
|
||||||
check_text()
|
check_text()
|
||||||
append(renderable)
|
append(renderable)
|
||||||
|
@ -754,7 +778,7 @@ class Console:
|
||||||
end (str, optional): String to write at end of print data. Defaults to "\n".
|
end (str, optional): String to write at end of print data. Defaults to "\n".
|
||||||
style (Union[str, Style], optional): A style to apply to output. Defaults to None.
|
style (Union[str, Style], optional): A style to apply to output. Defaults to None.
|
||||||
justify (str, optional): Justify method: "default", "left", "right", "center", or "full". Defaults to ``None``.
|
justify (str, optional): Justify method: "default", "left", "right", "center", or "full". Defaults to ``None``.
|
||||||
overflow (str, optional): Overflow method: "crop", "fold", or "ellipisis". Defaults to None.
|
overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None.
|
||||||
no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to None.
|
no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to None.
|
||||||
emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to ``None``.
|
emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to ``None``.
|
||||||
markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to ``None``.
|
markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to ``None``.
|
||||||
|
@ -775,6 +799,8 @@ class Console:
|
||||||
markup=markup,
|
markup=markup,
|
||||||
highlight=highlight,
|
highlight=highlight,
|
||||||
)
|
)
|
||||||
|
for hook in self._render_hooks:
|
||||||
|
renderables = hook.process_renderables(renderables)
|
||||||
render_options = self.options.update(
|
render_options = self.options.update(
|
||||||
justify=justify, overflow=overflow, width=width, no_wrap=no_wrap
|
justify=justify, overflow=overflow, width=width, no_wrap=no_wrap
|
||||||
)
|
)
|
||||||
|
@ -843,35 +869,39 @@ class Console:
|
||||||
if not objects:
|
if not objects:
|
||||||
self.line()
|
self.line()
|
||||||
return
|
return
|
||||||
renderables = self._collect_renderables(
|
|
||||||
objects,
|
|
||||||
sep,
|
|
||||||
end,
|
|
||||||
justify=justify,
|
|
||||||
emoji=emoji,
|
|
||||||
markup=markup,
|
|
||||||
highlight=highlight,
|
|
||||||
)
|
|
||||||
|
|
||||||
caller = inspect.stack()[_stack_offset]
|
|
||||||
path = caller.filename.rpartition(os.sep)[-1]
|
|
||||||
line_no = caller.lineno
|
|
||||||
if log_locals:
|
|
||||||
locals_map = {
|
|
||||||
key: value
|
|
||||||
for key, value in caller.frame.f_locals.items()
|
|
||||||
if not key.startswith("__")
|
|
||||||
}
|
|
||||||
renderables.append(tabulate_mapping(locals_map, title="Locals"))
|
|
||||||
|
|
||||||
with self:
|
with self:
|
||||||
self._buffer.extend(
|
renderables = self._collect_renderables(
|
||||||
self.render(
|
objects,
|
||||||
self._log_render(self, renderables, path=path, line_no=line_no),
|
sep,
|
||||||
self.options,
|
end,
|
||||||
)
|
justify=justify,
|
||||||
|
emoji=emoji,
|
||||||
|
markup=markup,
|
||||||
|
highlight=highlight,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
caller = inspect.stack()[_stack_offset]
|
||||||
|
path = caller.filename.rpartition(os.sep)[-1]
|
||||||
|
line_no = caller.lineno
|
||||||
|
if log_locals:
|
||||||
|
locals_map = {
|
||||||
|
key: value
|
||||||
|
for key, value in caller.frame.f_locals.items()
|
||||||
|
if not key.startswith("__")
|
||||||
|
}
|
||||||
|
renderables.append(tabulate_mapping(locals_map, title="Locals"))
|
||||||
|
|
||||||
|
renderables = [
|
||||||
|
self._log_render(self, renderables, path=path, line_no=line_no)
|
||||||
|
]
|
||||||
|
for hook in self._render_hooks:
|
||||||
|
renderables = hook.process_renderables(renderables)
|
||||||
|
extend = self._buffer.extend
|
||||||
|
render = self.render
|
||||||
|
render_options = self.options
|
||||||
|
for renderable in renderables:
|
||||||
|
extend(render(renderable, render_options))
|
||||||
|
|
||||||
def _check_buffer(self) -> None:
|
def _check_buffer(self) -> None:
|
||||||
"""Check if the buffer may be rendered."""
|
"""Check if the buffer may be rendered."""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
|
@ -883,7 +913,8 @@ class Console:
|
||||||
del self._buffer[:]
|
del self._buffer[:]
|
||||||
else:
|
else:
|
||||||
text = self._render_buffer()
|
text = self._render_buffer()
|
||||||
self.file.write(text)
|
if text:
|
||||||
|
self.file.write(text)
|
||||||
self.file.flush()
|
self.file.flush()
|
||||||
|
|
||||||
def _render_buffer(self) -> str:
|
def _render_buffer(self) -> str:
|
||||||
|
|
|
@ -37,7 +37,7 @@ class LiveRender:
|
||||||
if self._shape is not None:
|
if self._shape is not None:
|
||||||
_, height = self._shape
|
_, height = self._shape
|
||||||
if height > 1:
|
if height > 1:
|
||||||
return Control(f"\r\x1b[{height - 1}A\x1b[2K")
|
return Control("\r\x1b[2K" + "\x1b[1A\x1b[2K" * (height - 1))
|
||||||
else:
|
else:
|
||||||
return Control("\r\x1b[2K")
|
return Control("\r\x1b[2K")
|
||||||
return Control("")
|
return Control("")
|
||||||
|
|
|
@ -3,6 +3,7 @@ from datetime import datetime
|
||||||
from logging import Handler, LogRecord
|
from logging import Handler, LogRecord
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from . import get_console
|
||||||
from rich._log_render import LogRender
|
from rich._log_render import LogRender
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.highlighter import ReprHighlighter
|
from rich.highlighter import ReprHighlighter
|
||||||
|
@ -25,7 +26,7 @@ class RichHandler(Handler):
|
||||||
|
|
||||||
def __init__(self, level: int = logging.NOTSET, console: Console = None) -> None:
|
def __init__(self, level: int = logging.NOTSET, console: Console = None) -> None:
|
||||||
super().__init__(level=level)
|
super().__init__(level=level)
|
||||||
self.console = Console() if console is None else console
|
self.console = get_console() if console is None else console
|
||||||
self.highlighter = ReprHighlighter()
|
self.highlighter = ReprHighlighter()
|
||||||
self._log_render = LogRender(show_level=True)
|
self._log_render = LogRender(show_level=True)
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,15 @@ from typing import (
|
||||||
|
|
||||||
from . import get_console
|
from . import get_console
|
||||||
from .bar import Bar
|
from .bar import Bar
|
||||||
from .console import Console, JustifyMethod, RenderGroup, RenderableType
|
from .console import (
|
||||||
|
Console,
|
||||||
|
ConsoleRenderable,
|
||||||
|
JustifyMethod,
|
||||||
|
RenderGroup,
|
||||||
|
RenderHook,
|
||||||
|
RenderableType,
|
||||||
|
)
|
||||||
|
from .control import Control
|
||||||
from .highlighter import Highlighter
|
from .highlighter import Highlighter
|
||||||
from . import filesize
|
from . import filesize
|
||||||
from .live_render import LiveRender
|
from .live_render import LiveRender
|
||||||
|
@ -368,7 +376,7 @@ class _RefreshThread(Thread):
|
||||||
self.progress.refresh()
|
self.progress.refresh()
|
||||||
|
|
||||||
|
|
||||||
class Progress:
|
class Progress(RenderHook):
|
||||||
"""Renders an auto-updating progress bar(s).
|
"""Renders an auto-updating progress bar(s).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -410,6 +418,8 @@ class Progress:
|
||||||
self._refresh_thread: Optional[_RefreshThread] = None
|
self._refresh_thread: Optional[_RefreshThread] = None
|
||||||
self._refresh_count = 0
|
self._refresh_count = 0
|
||||||
self._started = False
|
self._started = False
|
||||||
|
self.print = self.console.print
|
||||||
|
self.log = self.console.log
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tasks(self) -> List[Task]:
|
def tasks(self) -> List[Task]:
|
||||||
|
@ -438,6 +448,7 @@ class Progress:
|
||||||
return
|
return
|
||||||
self._started = True
|
self._started = True
|
||||||
self.console.show_cursor(False)
|
self.console.show_cursor(False)
|
||||||
|
self.console.push_render_hook(self)
|
||||||
self.refresh()
|
self.refresh()
|
||||||
if self.auto_refresh:
|
if self.auto_refresh:
|
||||||
self._refresh_thread = _RefreshThread(self, self.refresh_per_second)
|
self._refresh_thread = _RefreshThread(self, self.refresh_per_second)
|
||||||
|
@ -457,6 +468,7 @@ class Progress:
|
||||||
self.console.line()
|
self.console.line()
|
||||||
finally:
|
finally:
|
||||||
self.console.show_cursor(True)
|
self.console.show_cursor(True)
|
||||||
|
self.console.pop_render_hook()
|
||||||
if self._refresh_thread is not None:
|
if self._refresh_thread is not None:
|
||||||
self._refresh_thread.join()
|
self._refresh_thread.join()
|
||||||
self._refresh_thread = None
|
self._refresh_thread = None
|
||||||
|
@ -598,8 +610,9 @@ class Progress:
|
||||||
self._live_render.set_renderable(self.get_renderable())
|
self._live_render.set_renderable(self.get_renderable())
|
||||||
if self.console.is_terminal:
|
if self.console.is_terminal:
|
||||||
with self.console:
|
with self.console:
|
||||||
self.console.print(self._live_render.position_cursor())
|
# self.console.print(self._live_render.position_cursor())
|
||||||
self.console.print(self._live_render)
|
self.console.print(Control(""))
|
||||||
|
# self.console.print(self._live_render)
|
||||||
self._refresh_count += 1
|
self._refresh_count += 1
|
||||||
|
|
||||||
def get_renderable(self) -> RenderableType:
|
def get_renderable(self) -> RenderableType:
|
||||||
|
@ -693,65 +706,17 @@ class Progress:
|
||||||
with self._lock:
|
with self._lock:
|
||||||
del self._tasks[task_id]
|
del self._tasks[task_id]
|
||||||
|
|
||||||
def print(
|
def process_renderables(
|
||||||
self,
|
self, renderables: List[ConsoleRenderable]
|
||||||
*objects: Any,
|
) -> List[ConsoleRenderable]:
|
||||||
sep=" ",
|
"""Process renderables to restore cursor and display progress."""
|
||||||
end="\n",
|
if self.console.is_terminal:
|
||||||
style: Union[str, Style] = None,
|
renderables = [
|
||||||
justify: JustifyMethod = None,
|
self._live_render.position_cursor(),
|
||||||
emoji: bool = None,
|
*renderables,
|
||||||
markup: bool = None,
|
self._live_render,
|
||||||
highlight: bool = None,
|
]
|
||||||
) -> None:
|
return renderables
|
||||||
"""Print to the terminal and preserve progress display. Parameters identical to :class:`~rich.console.Console.print`."""
|
|
||||||
console = self.console
|
|
||||||
with console:
|
|
||||||
if console.is_terminal:
|
|
||||||
console.print(self._live_render.position_cursor())
|
|
||||||
console.print(
|
|
||||||
*objects,
|
|
||||||
sep=sep,
|
|
||||||
end=end,
|
|
||||||
style=style,
|
|
||||||
justify=justify,
|
|
||||||
emoji=emoji,
|
|
||||||
markup=markup,
|
|
||||||
highlight=highlight,
|
|
||||||
)
|
|
||||||
if console.is_terminal:
|
|
||||||
console.print(self._live_render)
|
|
||||||
|
|
||||||
def log(
|
|
||||||
self,
|
|
||||||
*objects: Any,
|
|
||||||
sep=" ",
|
|
||||||
end="\n",
|
|
||||||
justify: JustifyMethod = None,
|
|
||||||
emoji: bool = None,
|
|
||||||
markup: bool = None,
|
|
||||||
highlight: bool = None,
|
|
||||||
log_locals: bool = False,
|
|
||||||
_stack_offset=1,
|
|
||||||
) -> None:
|
|
||||||
"""Log to the terminal and preserve progress display. Parameters identical to :class:`~rich.console.Console.log`."""
|
|
||||||
console = self.console
|
|
||||||
with console:
|
|
||||||
if console.is_terminal:
|
|
||||||
console.print(self._live_render.position_cursor())
|
|
||||||
console.log(
|
|
||||||
*objects,
|
|
||||||
sep=sep,
|
|
||||||
end=end,
|
|
||||||
justify=justify,
|
|
||||||
emoji=emoji,
|
|
||||||
markup=markup,
|
|
||||||
highlight=highlight,
|
|
||||||
log_locals=log_locals,
|
|
||||||
_stack_offset=_stack_offset + 1,
|
|
||||||
)
|
|
||||||
if console.is_terminal:
|
|
||||||
console.print(self._live_render)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__": # pragma: no coverage
|
if __name__ == "__main__": # pragma: no coverage
|
||||||
|
@ -799,7 +764,8 @@ yield True, previous_value''',
|
||||||
|
|
||||||
examples = cycle(progress_renderables)
|
examples = cycle(progress_renderables)
|
||||||
|
|
||||||
with Progress(transient=True) as progress:
|
console = Console()
|
||||||
|
with Progress(console=console, transient=True) as progress:
|
||||||
|
|
||||||
task1 = progress.add_task(" [red]Downloading", total=1000)
|
task1 = progress.add_task(" [red]Downloading", total=1000)
|
||||||
task2 = progress.add_task(" [green]Processing", total=1000)
|
task2 = progress.add_task(" [green]Processing", total=1000)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue