mirror of
https://github.com/Textualize/rich.git
synced 2025-12-23 07:08:35 +00:00
Merge 381ade046d into f82a399d58
This commit is contained in:
commit
4e8340ba69
9 changed files with 83 additions and 25 deletions
|
|
@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Added
|
||||
|
||||
- Added `TTY_INTERACTIVE` environment variable to force interactive mode off or on https://github.com/Textualize/rich/pull/3777
|
||||
- Allowed custom spinner animations throughout the library https://github.com/Textualize/rich/pull/3791
|
||||
|
||||
## [14.0.0] - 2025-03-30
|
||||
|
||||
|
|
|
|||
|
|
@ -94,3 +94,4 @@ The following people have contributed to the development of Rich:
|
|||
- [Jonathan Helmus](https://github.com/jjhelmus)
|
||||
- [Brandon Capener](https://github.com/bcapener)
|
||||
- [Alex Zheng](https://github.com/alexzheng111)
|
||||
- [Maddy Guthridge](https://maddyguthridge.com/)
|
||||
|
|
|
|||
|
|
@ -136,6 +136,10 @@ Run the following command to see the available choices for ``spinner``::
|
|||
|
||||
python -m rich.spinner
|
||||
|
||||
You can use a custom spinner by providing a dictionary with the following properties
|
||||
|
||||
* ``"interval"`` Intended time per frame of spinner
|
||||
* ``"frames"`` Frames of the spinner. If this is a single ``str``, each character is a single frame. If a ``list[str]`` is given, each list element is a single frame.
|
||||
|
||||
Justify / Alignment
|
||||
-------------------
|
||||
|
|
|
|||
|
|
@ -19,7 +19,20 @@ Spinners are from:
|
|||
IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
SPINNERS = {
|
||||
from typing import TypedDict, List, Dict, Union
|
||||
|
||||
|
||||
class SpinnerAnimation(TypedDict):
|
||||
interval: float
|
||||
"""Intended time per frame, in milliseconds"""
|
||||
frames: Union[List[str], str]
|
||||
"""
|
||||
Frames of this spinner. If a single `str`, each character is a single
|
||||
frame. If a `list[str]`, each list element is a single frame.
|
||||
"""
|
||||
|
||||
|
||||
SPINNERS: Dict[str, SpinnerAnimation] = {
|
||||
"dots": {
|
||||
"interval": 80,
|
||||
"frames": "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏",
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ from typing import (
|
|||
)
|
||||
|
||||
from rich._null_file import NULL_FILE
|
||||
from rich._spinners import SpinnerAnimation
|
||||
|
||||
from . import errors, themes
|
||||
from ._emoji_replace import _emoji_replace
|
||||
|
|
@ -1164,7 +1165,7 @@ class Console:
|
|||
self,
|
||||
status: RenderableType,
|
||||
*,
|
||||
spinner: str = "dots",
|
||||
spinner: Union[str, SpinnerAnimation] = "dots",
|
||||
spinner_style: StyleType = "status.spinner",
|
||||
speed: float = 1.0,
|
||||
refresh_per_second: float = 12.5,
|
||||
|
|
@ -2182,9 +2183,9 @@ class Console:
|
|||
str: String containing console contents.
|
||||
|
||||
"""
|
||||
assert (
|
||||
self.record
|
||||
), "To export console contents set record=True in the constructor or instance"
|
||||
assert self.record, (
|
||||
"To export console contents set record=True in the constructor or instance"
|
||||
)
|
||||
|
||||
with self._record_buffer_lock:
|
||||
if styles:
|
||||
|
|
@ -2238,9 +2239,9 @@ class Console:
|
|||
Returns:
|
||||
str: String containing console contents as HTML.
|
||||
"""
|
||||
assert (
|
||||
self.record
|
||||
), "To export console contents set record=True in the constructor or instance"
|
||||
assert self.record, (
|
||||
"To export console contents set record=True in the constructor or instance"
|
||||
)
|
||||
fragments: List[str] = []
|
||||
append = fragments.append
|
||||
_theme = theme or DEFAULT_TERMINAL_THEME
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ from .highlighter import Highlighter
|
|||
from .jupyter import JupyterMixin
|
||||
from .live import Live
|
||||
from .progress_bar import ProgressBar
|
||||
from .spinner import Spinner
|
||||
from .spinner import Spinner, SpinnerAnimation
|
||||
from .style import StyleType
|
||||
from .table import Column, Table
|
||||
from .text import Text, TextType
|
||||
|
|
@ -575,7 +575,7 @@ class SpinnerColumn(ProgressColumn):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
spinner_name: str = "dots",
|
||||
spinner_name: Union[str, SpinnerAnimation] = "dots",
|
||||
style: Optional[StyleType] = "progress.spinner",
|
||||
speed: float = 1.0,
|
||||
finished_text: TextType = " ",
|
||||
|
|
@ -591,7 +591,7 @@ class SpinnerColumn(ProgressColumn):
|
|||
|
||||
def set_spinner(
|
||||
self,
|
||||
spinner_name: str,
|
||||
spinner_name: Union[str, SpinnerAnimation],
|
||||
spinner_style: Optional[StyleType] = "progress.spinner",
|
||||
speed: float = 1.0,
|
||||
) -> None:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from typing import TYPE_CHECKING, List, Optional, Union, cast
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
|
||||
from ._spinners import SPINNERS
|
||||
from ._spinners import SPINNERS, SpinnerAnimation
|
||||
from .measure import Measurement
|
||||
from .table import Table
|
||||
from .text import Text
|
||||
|
|
@ -10,11 +10,19 @@ if TYPE_CHECKING:
|
|||
from .style import StyleType
|
||||
|
||||
|
||||
# Explicitly export `SpinnerInfo` to avoid linter annoyances if other people
|
||||
# want to use our type definition.
|
||||
__all__ = [
|
||||
"Spinner",
|
||||
"SpinnerAnimation",
|
||||
]
|
||||
|
||||
|
||||
class Spinner:
|
||||
"""A spinner animation.
|
||||
|
||||
Args:
|
||||
name (str): Name of spinner (run python -m rich.spinner).
|
||||
name (str | SpinnerInfo): Name of spinner (run python -m rich.spinner), or a dict of shape { "interval": float, "frames": str | list[str] }
|
||||
text (RenderableType, optional): A renderable to display at the right of the spinner (str or Text typically). Defaults to "".
|
||||
style (StyleType, optional): Style for spinner animation. Defaults to None.
|
||||
speed (float, optional): Speed factor for animation. Defaults to 1.0.
|
||||
|
|
@ -25,22 +33,26 @@ class Spinner:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
name: str | SpinnerAnimation,
|
||||
text: "RenderableType" = "",
|
||||
*,
|
||||
style: Optional["StyleType"] = None,
|
||||
speed: float = 1.0,
|
||||
) -> None:
|
||||
try:
|
||||
spinner = SPINNERS[name]
|
||||
except KeyError:
|
||||
raise KeyError(f"no spinner called {name!r}")
|
||||
if isinstance(name, str):
|
||||
try:
|
||||
spinner = SPINNERS[name]
|
||||
except KeyError:
|
||||
raise KeyError(f"no spinner called {name!r}")
|
||||
else:
|
||||
spinner = name
|
||||
|
||||
self.text: "Union[RenderableType, Text]" = (
|
||||
Text.from_markup(text) if isinstance(text, str) else text
|
||||
)
|
||||
self.name = name
|
||||
self.frames = cast(List[str], spinner["frames"])[:]
|
||||
self.interval = cast(float, spinner["interval"])
|
||||
self.frames = spinner["frames"][:]
|
||||
self.interval = spinner["interval"]
|
||||
self.start_time: Optional[float] = None
|
||||
self.style = style
|
||||
self.speed = speed
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from types import TracebackType
|
||||
from typing import Optional, Type
|
||||
from typing import Optional, Type, Union
|
||||
|
||||
from ._spinners import SpinnerAnimation
|
||||
from .console import Console, RenderableType
|
||||
from .jupyter import JupyterMixin
|
||||
from .live import Live
|
||||
|
|
@ -25,7 +26,7 @@ class Status(JupyterMixin):
|
|||
status: RenderableType,
|
||||
*,
|
||||
console: Optional[Console] = None,
|
||||
spinner: str = "dots",
|
||||
spinner: Union[str, SpinnerAnimation] = "dots",
|
||||
spinner_style: StyleType = "status.spinner",
|
||||
speed: float = 1.0,
|
||||
refresh_per_second: float = 12.5,
|
||||
|
|
@ -54,7 +55,7 @@ class Status(JupyterMixin):
|
|||
self,
|
||||
status: Optional[RenderableType] = None,
|
||||
*,
|
||||
spinner: Optional[str] = None,
|
||||
spinner: Union[str, SpinnerAnimation, None] = None,
|
||||
spinner_style: Optional[StyleType] = None,
|
||||
speed: Optional[float] = None,
|
||||
) -> None:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import pytest
|
|||
from rich.console import Console
|
||||
from rich.measure import Measurement
|
||||
from rich.rule import Rule
|
||||
from rich.spinner import Spinner
|
||||
from rich.spinner import Spinner, SpinnerAnimation
|
||||
from rich.text import Text
|
||||
|
||||
|
||||
|
|
@ -70,3 +70,28 @@ def test_spinner_markup():
|
|||
spinner = Spinner("dots", "[bold]spinning[/bold]")
|
||||
assert isinstance(spinner.text, Text)
|
||||
assert str(spinner.text) == "spinning"
|
||||
|
||||
|
||||
def test_custom_spinner_render():
|
||||
custom_spinner: SpinnerAnimation = {
|
||||
"interval": 80,
|
||||
"frames": "abcdef",
|
||||
}
|
||||
time = 0.0
|
||||
|
||||
def get_time():
|
||||
nonlocal time
|
||||
return time
|
||||
|
||||
console = Console(
|
||||
width=80, color_system=None, force_terminal=True, get_time=get_time
|
||||
)
|
||||
console.begin_capture()
|
||||
spinner = Spinner(custom_spinner, "Foo")
|
||||
console.print(spinner)
|
||||
time += 80 / 1000
|
||||
console.print(spinner)
|
||||
result = console.end_capture()
|
||||
print(repr(result))
|
||||
expected = "a Foo\nb Foo\n"
|
||||
assert result == expected
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue