rich-transient 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""Reusable Rich-based braille spinner and transient live panel for CLI output.
|
|
2
|
+
|
|
3
|
+
This package can be used by any project that needs:
|
|
4
|
+
- A braille-style status spinner (accessible, compact)
|
|
5
|
+
- A transient live panel that streams output and clears on exit
|
|
6
|
+
|
|
7
|
+
Dependencies: rich
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import threading
|
|
13
|
+
import time
|
|
14
|
+
from contextlib import contextmanager
|
|
15
|
+
from dataclasses import dataclass, fields, replace
|
|
16
|
+
from types import SimpleNamespace
|
|
17
|
+
from typing import Callable, Literal, TypeVar
|
|
18
|
+
|
|
19
|
+
from rich.console import Console
|
|
20
|
+
from rich.live import Live
|
|
21
|
+
from rich.panel import Panel
|
|
22
|
+
from rich.text import Text
|
|
23
|
+
|
|
24
|
+
T = TypeVar("T")
|
|
25
|
+
|
|
26
|
+
# Braille spinner frames (one per refresh) so the status line visibly animates.
|
|
27
|
+
SPINNER_BRAILLE: tuple[str, ...] = ("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏")
|
|
28
|
+
|
|
29
|
+
# Shared refresh rate for Live displays (transient panel and other live UIs).
|
|
30
|
+
LIVE_REFRESH_PER_SECOND: float = 8.0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_braille_frame() -> int:
|
|
34
|
+
"""Return the current animation frame index for the braille spinner (0 to len(SPINNER_BRAILLE)-1).
|
|
35
|
+
|
|
36
|
+
Use with SPINNER_BRAILLE[get_braille_frame()] when building custom live UIs (e.g. tables, status lines).
|
|
37
|
+
"""
|
|
38
|
+
return int(time.monotonic() * LIVE_REFRESH_PER_SECOND) % len(SPINNER_BRAILLE)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Name under which we register the braille spinner in Rich's SPINNERS dict.
|
|
42
|
+
_BRAILLE_SPINNER_NAME = "braille"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def register_braille_spinner() -> None:
|
|
46
|
+
"""Register the braille spinner with Rich so console.status(..., spinner='braille') works.
|
|
47
|
+
|
|
48
|
+
Idempotent; safe to call multiple times. Call once at startup or rely on braille_spinner_for_status().
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
from rich import _spinners
|
|
52
|
+
|
|
53
|
+
_spinners.SPINNERS[_BRAILLE_SPINNER_NAME] = {
|
|
54
|
+
"frames": list(SPINNER_BRAILLE),
|
|
55
|
+
"interval": 1000.0 / LIVE_REFRESH_PER_SECOND,
|
|
56
|
+
}
|
|
57
|
+
except (ImportError, AttributeError):
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def braille_spinner_for_status() -> str:
|
|
62
|
+
"""Return the Rich spinner name 'braille' for use with console.status(spinner=...).
|
|
63
|
+
|
|
64
|
+
Registers the braille frames with Rich on first use, then returns the name so Status
|
|
65
|
+
can look it up. Rich's Status expects a spinner name (str), not a Spinner instance.
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
with console.status("Loading...", spinner=braille_spinner_for_status()):
|
|
69
|
+
do_work()
|
|
70
|
+
"""
|
|
71
|
+
register_braille_spinner()
|
|
72
|
+
return _BRAILLE_SPINNER_NAME
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class TransientPanelConfig:
|
|
77
|
+
"""Configuration for transient live panels. Use presets via transient_live_panel(..., preset='streaming')."""
|
|
78
|
+
|
|
79
|
+
max_lines: int = 100
|
|
80
|
+
display_lines: int = 24
|
|
81
|
+
refresh_per_second: float = LIVE_REFRESH_PER_SECOND
|
|
82
|
+
default_status: str = "Running"
|
|
83
|
+
reserve_lines: int = 12 # Space for title, borders, padding, subtitle
|
|
84
|
+
border_style: str = "dim"
|
|
85
|
+
padding: tuple[int, int] = (0, 1)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# Presets for consistent behavior across commands.
|
|
89
|
+
TRANSIENT_PANEL_PRESETS: dict[str, TransientPanelConfig] = {
|
|
90
|
+
"default": TransientPanelConfig(),
|
|
91
|
+
"streaming": TransientPanelConfig(
|
|
92
|
+
max_lines=200,
|
|
93
|
+
display_lines=28,
|
|
94
|
+
default_status="Running",
|
|
95
|
+
),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _resolve_panel_config(
|
|
100
|
+
preset: Literal["default", "streaming"] | None = None,
|
|
101
|
+
config: TransientPanelConfig | None = None,
|
|
102
|
+
**overrides: object,
|
|
103
|
+
) -> TransientPanelConfig:
|
|
104
|
+
"""Resolve config from preset, optional explicit config, and overrides."""
|
|
105
|
+
base = config or TRANSIENT_PANEL_PRESETS.get(preset or "default") or TRANSIENT_PANEL_PRESETS["default"]
|
|
106
|
+
valid_names = {f.name for f in fields(TransientPanelConfig)}
|
|
107
|
+
clean = {k: v for k, v in overrides.items() if k in valid_names and v is not None}
|
|
108
|
+
if not clean:
|
|
109
|
+
return base
|
|
110
|
+
return replace(base, **clean)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@contextmanager
|
|
114
|
+
def transient_live_panel(
|
|
115
|
+
title: str,
|
|
116
|
+
preset: Literal["default", "streaming"] = "default",
|
|
117
|
+
config: TransientPanelConfig | None = None,
|
|
118
|
+
*,
|
|
119
|
+
max_lines: int | None = None,
|
|
120
|
+
display_lines: int | None = None,
|
|
121
|
+
border_style: str | None = None,
|
|
122
|
+
console: Console | None = None,
|
|
123
|
+
):
|
|
124
|
+
"""Context manager that shows streaming output in a live panel; panel is cleared on exit.
|
|
125
|
+
|
|
126
|
+
Use for verbose subprocess output: output streams into the panel while the task runs,
|
|
127
|
+
then the panel is removed so only the high-level success/failure line remains.
|
|
128
|
+
|
|
129
|
+
Presets:
|
|
130
|
+
- "default": Short steps. max_lines=100, display_lines=24.
|
|
131
|
+
- "streaming": Long tool output. max_lines=200, display_lines=28.
|
|
132
|
+
|
|
133
|
+
Optional kwargs override the preset (e.g. display_lines=30).
|
|
134
|
+
Pass console= to use a specific Rich Console (e.g. with custom theme).
|
|
135
|
+
|
|
136
|
+
Yields an object with:
|
|
137
|
+
- append(line: str) -> None
|
|
138
|
+
- set_status(text: str) -> None -- update the status line (e.g. "Downloading X...")
|
|
139
|
+
- run_task(task_callable: Callable[[], T]) -> T
|
|
140
|
+
"""
|
|
141
|
+
target_console = console if console is not None else Console()
|
|
142
|
+
cfg = _resolve_panel_config(
|
|
143
|
+
preset=preset,
|
|
144
|
+
config=config,
|
|
145
|
+
max_lines=max_lines,
|
|
146
|
+
display_lines=display_lines,
|
|
147
|
+
border_style=border_style,
|
|
148
|
+
)
|
|
149
|
+
lines_list: list[str] = []
|
|
150
|
+
current_status: list[str] = [cfg.default_status]
|
|
151
|
+
lock = threading.Lock()
|
|
152
|
+
result_holder: list = []
|
|
153
|
+
try:
|
|
154
|
+
console_height = target_console.size.height
|
|
155
|
+
except Exception:
|
|
156
|
+
console_height = 30
|
|
157
|
+
tail = min(cfg.display_lines, max(1, console_height - cfg.reserve_lines))
|
|
158
|
+
|
|
159
|
+
def append(line: str) -> None:
|
|
160
|
+
with lock:
|
|
161
|
+
lines_list.append(line)
|
|
162
|
+
|
|
163
|
+
def set_status(text: str) -> None:
|
|
164
|
+
with lock:
|
|
165
|
+
current_status[0] = text
|
|
166
|
+
|
|
167
|
+
def render() -> Panel:
|
|
168
|
+
with lock:
|
|
169
|
+
recent = lines_list[-cfg.max_lines:] if len(lines_list) > cfg.max_lines else lines_list
|
|
170
|
+
visible = recent[-tail:] if len(recent) > tail else recent
|
|
171
|
+
content = "\n".join(visible)
|
|
172
|
+
status_text = current_status[0]
|
|
173
|
+
if content:
|
|
174
|
+
try:
|
|
175
|
+
streamed = Text.from_markup(content)
|
|
176
|
+
except Exception:
|
|
177
|
+
streamed = Text(content)
|
|
178
|
+
else:
|
|
179
|
+
streamed = Text("")
|
|
180
|
+
frame_idx = get_braille_frame()
|
|
181
|
+
status_line = Text(f" {SPINNER_BRAILLE[frame_idx]} {status_text}", style="dim")
|
|
182
|
+
return Panel(
|
|
183
|
+
streamed,
|
|
184
|
+
title=title,
|
|
185
|
+
subtitle=status_line,
|
|
186
|
+
border_style=cfg.border_style,
|
|
187
|
+
padding=cfg.padding,
|
|
188
|
+
expand=True,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def run_task(task_callable: Callable[[], T]) -> T:
|
|
192
|
+
result_holder.clear()
|
|
193
|
+
exc_holder: list[BaseException | None] = [None]
|
|
194
|
+
|
|
195
|
+
def run() -> None:
|
|
196
|
+
try:
|
|
197
|
+
result_holder.append(task_callable())
|
|
198
|
+
except BaseException as e:
|
|
199
|
+
exc_holder[0] = e
|
|
200
|
+
|
|
201
|
+
th = threading.Thread(target=run)
|
|
202
|
+
th.start()
|
|
203
|
+
with Live(
|
|
204
|
+
render(),
|
|
205
|
+
refresh_per_second=cfg.refresh_per_second,
|
|
206
|
+
transient=True,
|
|
207
|
+
console=target_console,
|
|
208
|
+
) as live:
|
|
209
|
+
while th.is_alive():
|
|
210
|
+
live.update(render())
|
|
211
|
+
time.sleep(0.05)
|
|
212
|
+
live.update(render())
|
|
213
|
+
th.join()
|
|
214
|
+
if exc_holder[0] is not None:
|
|
215
|
+
raise exc_holder[0]
|
|
216
|
+
return result_holder[0]
|
|
217
|
+
|
|
218
|
+
yield SimpleNamespace(append=append, set_status=set_status, run_task=run_task)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
__all__ = [
|
|
222
|
+
"SPINNER_BRAILLE",
|
|
223
|
+
"LIVE_REFRESH_PER_SECOND",
|
|
224
|
+
"TransientPanelConfig",
|
|
225
|
+
"TRANSIENT_PANEL_PRESETS",
|
|
226
|
+
"braille_spinner_for_status",
|
|
227
|
+
"get_braille_frame",
|
|
228
|
+
"register_braille_spinner",
|
|
229
|
+
"transient_live_panel",
|
|
230
|
+
]
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rich-transient
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Reusable Rich-based braille spinner and transient live panel for CLI output
|
|
5
|
+
Project-URL: Homepage, https://github.com/your-org/rich-transient
|
|
6
|
+
Project-URL: Repository, https://github.com/your-org/rich-transient
|
|
7
|
+
Project-URL: Documentation, https://github.com/your-org/rich-transient#readme
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: cli,live,panel,progress,rich,spinner,terminal
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: rich>=13.7.1
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# rich_transient
|
|
25
|
+
|
|
26
|
+
Reusable [Rich](https://github.com/Textualize/rich)-based **braille spinner** and **transient live panel** for CLI output. Use it to show streaming subprocess or task output in a live-updating panel that disappears when the task finishes, with an animated status line.
|
|
27
|
+
|
|
28
|
+
**Requires:** Python 3.11+, `rich>=13.7.1`
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
**From PyPI:**
|
|
35
|
+
```bash
|
|
36
|
+
pip install rich-transient
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Standalone (from this repo):**
|
|
40
|
+
```bash
|
|
41
|
+
pip install -e /path/to/AutoIaC/rich_transient
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**As part of AutoIaC:**
|
|
45
|
+
```bash
|
|
46
|
+
pip install -e /path/to/AutoIaC
|
|
47
|
+
# installs both auto_iac and rich_transient
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Quick start: transient live panel
|
|
53
|
+
|
|
54
|
+
Wrap a long-running task so its output streams into a live panel; when the task ends, the panel is cleared (transient) and only your final message remains.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from rich_transient import transient_live_panel
|
|
58
|
+
|
|
59
|
+
with transient_live_panel("Installing dependencies") as panel:
|
|
60
|
+
def do_work():
|
|
61
|
+
# Simulate streaming output
|
|
62
|
+
for i in range(20):
|
|
63
|
+
print(f"Step {i + 1}/20...")
|
|
64
|
+
return "done"
|
|
65
|
+
|
|
66
|
+
result = panel.run_task(do_work)
|
|
67
|
+
|
|
68
|
+
print(f"Result: {result}")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The panel shows a scrolling tail of output and an animated braille spinner in the status line; when `run_task` returns, the panel is removed.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Using the panel API
|
|
76
|
+
|
|
77
|
+
The context manager yields an object with three methods:
|
|
78
|
+
|
|
79
|
+
| Method | Description |
|
|
80
|
+
|--------|-------------|
|
|
81
|
+
| `panel.append(line: str)` | Add a line to the panel content (e.g. from a subprocess stdout). |
|
|
82
|
+
| `panel.set_status(text: str)` | Update the status line shown next to the spinner (e.g. "Downloading X..."). |
|
|
83
|
+
| `panel.run_task(callable)` | Run a callable in a background thread; the panel refreshes until it completes. Returns the callable's return value; re-raises any exception. |
|
|
84
|
+
|
|
85
|
+
**Streaming subprocess output into the panel:**
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import subprocess
|
|
89
|
+
from rich_transient import transient_live_panel
|
|
90
|
+
|
|
91
|
+
with transient_live_panel("Running tests") as panel:
|
|
92
|
+
def run():
|
|
93
|
+
proc = subprocess.Popen(
|
|
94
|
+
["pytest", "-v"],
|
|
95
|
+
stdout=subprocess.PIPE,
|
|
96
|
+
stderr=subprocess.STDOUT,
|
|
97
|
+
text=True,
|
|
98
|
+
)
|
|
99
|
+
for line in proc.stdout:
|
|
100
|
+
panel.append(line.rstrip())
|
|
101
|
+
proc.wait()
|
|
102
|
+
return proc.returncode
|
|
103
|
+
|
|
104
|
+
exit_code = panel.run_task(run)
|
|
105
|
+
print(f"Tests exited with code {exit_code}")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Updating the status line:**
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
with transient_live_panel("Building") as panel:
|
|
112
|
+
def build():
|
|
113
|
+
panel.set_status("Compiling...")
|
|
114
|
+
# do compile
|
|
115
|
+
panel.set_status("Linking...")
|
|
116
|
+
# do link
|
|
117
|
+
return 0
|
|
118
|
+
|
|
119
|
+
panel.run_task(build)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Braille spinner
|
|
125
|
+
|
|
126
|
+
Use the spinner frames for your own live UI (e.g. a custom table or status line).
|
|
127
|
+
|
|
128
|
+
**With Rich `console.status()`** — use the built-in braille spinner so the whole app shares one style:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from rich.console import Console
|
|
132
|
+
from rich_transient import braille_spinner_for_status
|
|
133
|
+
|
|
134
|
+
console = Console()
|
|
135
|
+
|
|
136
|
+
with console.status("Loading state...", spinner=braille_spinner_for_status()) as status:
|
|
137
|
+
do_work()
|
|
138
|
+
status.update("Almost done...")
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Custom live UI** — use `get_braille_frame()` so you don't duplicate the frame math:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
import time
|
|
145
|
+
from rich.console import Console
|
|
146
|
+
from rich.live import Live
|
|
147
|
+
from rich_transient import SPINNER_BRAILLE, LIVE_REFRESH_PER_SECOND, get_braille_frame
|
|
148
|
+
|
|
149
|
+
console = Console()
|
|
150
|
+
|
|
151
|
+
def make_status():
|
|
152
|
+
frame = get_braille_frame()
|
|
153
|
+
return f"{SPINNER_BRAILLE[frame]} Working..."
|
|
154
|
+
|
|
155
|
+
with Live(make_status(), refresh_per_second=LIVE_REFRESH_PER_SECOND, console=console) as live:
|
|
156
|
+
for _ in range(20):
|
|
157
|
+
time.sleep(0.1)
|
|
158
|
+
live.update(make_status())
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Exports:**
|
|
162
|
+
|
|
163
|
+
- `SPINNER_BRAILLE` — tuple of 10 braille characters for animation frames.
|
|
164
|
+
- `LIVE_REFRESH_PER_SECOND` — default refresh rate (8.0) for live displays.
|
|
165
|
+
- `get_braille_frame()` — current animation frame index (use with `SPINNER_BRAILLE[i]`).
|
|
166
|
+
- `braille_spinner_for_status()` — Registers the braille spinner with Rich and returns the name `"braille"` for `console.status(spinner=...)`.
|
|
167
|
+
- `register_braille_spinner()` — Idempotent registration of the braille spinner in Rich's SPINNERS dict (called automatically by `braille_spinner_for_status()`).
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Presets and options
|
|
172
|
+
|
|
173
|
+
**Presets** control buffer size and visible lines:
|
|
174
|
+
|
|
175
|
+
- `preset="default"` — max_lines=100, display_lines=24 (short steps).
|
|
176
|
+
- `preset="streaming"` — max_lines=200, display_lines=28 (long streaming output).
|
|
177
|
+
|
|
178
|
+
Override per call:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
with transient_live_panel(
|
|
182
|
+
"Long log",
|
|
183
|
+
preset="streaming",
|
|
184
|
+
max_lines=500,
|
|
185
|
+
display_lines=40,
|
|
186
|
+
) as panel:
|
|
187
|
+
panel.run_task(my_task)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Custom Rich theme:** pass a `Console` so the panel uses your theme:
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from rich.console import Console
|
|
194
|
+
from rich.theme import Theme
|
|
195
|
+
from rich_transient import transient_live_panel
|
|
196
|
+
|
|
197
|
+
my_theme = Theme({"info": "cyan", "success": "green"})
|
|
198
|
+
console = Console(theme=my_theme)
|
|
199
|
+
|
|
200
|
+
with transient_live_panel("Task", console=console) as panel:
|
|
201
|
+
panel.run_task(my_task)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Configuration
|
|
207
|
+
|
|
208
|
+
For full control, use `TransientPanelConfig` and `TRANSIENT_PANEL_PRESETS`:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from rich_transient import (
|
|
212
|
+
TransientPanelConfig,
|
|
213
|
+
TRANSIENT_PANEL_PRESETS,
|
|
214
|
+
transient_live_panel,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Use a built-in preset
|
|
218
|
+
cfg = TRANSIENT_PANEL_PRESETS["streaming"]
|
|
219
|
+
|
|
220
|
+
# Or build your own
|
|
221
|
+
custom = TransientPanelConfig(
|
|
222
|
+
max_lines=300,
|
|
223
|
+
display_lines=30,
|
|
224
|
+
default_status="Processing...",
|
|
225
|
+
border_style="blue",
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
with transient_live_panel("Custom", config=custom) as panel:
|
|
229
|
+
panel.run_task(my_task)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## API summary
|
|
235
|
+
|
|
236
|
+
| Export | Type | Description |
|
|
237
|
+
|--------|------|-------------|
|
|
238
|
+
| `SPINNER_BRAILLE` | `tuple[str, ...]` | Braille spinner frames. |
|
|
239
|
+
| `LIVE_REFRESH_PER_SECOND` | `float` | Default refresh rate for live displays. |
|
|
240
|
+
| `get_braille_frame()` | `() -> int` | Current animation frame index for use with `SPINNER_BRAILLE`. |
|
|
241
|
+
| `braille_spinner_for_status()` | `() -> str` | Registers braille spinner with Rich and returns `"braille"` for `console.status(spinner=...)`. |
|
|
242
|
+
| `register_braille_spinner()` | `() -> None` | Idempotent registration of braille spinner in Rich's SPINNERS. |
|
|
243
|
+
| `TransientPanelConfig` | dataclass | Panel configuration (max_lines, display_lines, border_style, etc.). |
|
|
244
|
+
| `TRANSIENT_PANEL_PRESETS` | `dict[str, TransientPanelConfig]` | `"default"` and `"streaming"` presets. |
|
|
245
|
+
| `transient_live_panel(...)` | context manager | Yields an object with `append`, `set_status`, `run_task`. |
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
rich_transient/__init__.py,sha256=LZo3iGwMpg1FAB7jushlI9QVcMa3tNC5CM_ovtD8fyY,7619
|
|
2
|
+
rich_transient-0.1.0.dist-info/METADATA,sha256=vg78FBOkmw_lu3v1mmXXjVDkwfYP_IElKMQSqgVU4WQ,7538
|
|
3
|
+
rich_transient-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
4
|
+
rich_transient-0.1.0.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
|
|
5
|
+
rich_transient-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|