screenbuffer 0.1.0__tar.gz → 1.0.1__tar.gz
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.
- {screenbuffer-0.1.0/screenbuffer.egg-info → screenbuffer-1.0.1}/PKG-INFO +285 -150
- screenbuffer-0.1.0/PKG-INFO → screenbuffer-1.0.1/README.md +626 -505
- {screenbuffer-0.1.0 → screenbuffer-1.0.1}/pyproject.toml +1 -1
- screenbuffer-0.1.0/README.md → screenbuffer-1.0.1/screenbuffer.egg-info/PKG-INFO +640 -491
- {screenbuffer-0.1.0 → screenbuffer-1.0.1}/screenbuffer/__init__.py +0 -0
- {screenbuffer-0.1.0 → screenbuffer-1.0.1}/screenbuffer/align.py +0 -0
- {screenbuffer-0.1.0 → screenbuffer-1.0.1}/screenbuffer/buffer.py +0 -0
- {screenbuffer-0.1.0 → screenbuffer-1.0.1}/screenbuffer/store.py +0 -0
- {screenbuffer-0.1.0 → screenbuffer-1.0.1}/screenbuffer/view.py +0 -0
- {screenbuffer-0.1.0 → screenbuffer-1.0.1}/screenbuffer.egg-info/SOURCES.txt +0 -0
- {screenbuffer-0.1.0 → screenbuffer-1.0.1}/screenbuffer.egg-info/dependency_links.txt +0 -0
- {screenbuffer-0.1.0 → screenbuffer-1.0.1}/screenbuffer.egg-info/requires.txt +0 -0
- {screenbuffer-0.1.0 → screenbuffer-1.0.1}/screenbuffer.egg-info/top_level.txt +0 -0
- {screenbuffer-0.1.0 → screenbuffer-1.0.1}/setup.cfg +0 -0
- {screenbuffer-0.1.0 → screenbuffer-1.0.1}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: screenbuffer
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: A Python utility for managing dynamic output, tracking progress, and organizing information throughout data-driven workflows.
|
|
5
5
|
Keywords: jupyter,notebook,progress,logging,display,data
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -14,45 +14,52 @@ Requires-Dist: ipython; extra == "jupyter"
|
|
|
14
14
|
|
|
15
15
|
# ScreenBuffer
|
|
16
16
|
|
|
17
|
-
**A
|
|
17
|
+
**A live output panel for Jupyter: a pinned header, a body that shows the newest N lines, and a footer for anything you want. Change any part, any time, and the rest stays exactly where it is.**
|
|
18
18
|
|
|
19
19
|
Long-running loops (scrapers, ETL jobs, model sweeps, simulations, data pulls) leave you two bad choices in Jupyter:
|
|
20
20
|
|
|
21
21
|
- `print()` every step: the cell fills with thousands of lines you cannot read.
|
|
22
22
|
- `clear_output()` every step: you lose everything, including the parts you wanted to keep (a progress header, a running total, the last error).
|
|
23
23
|
|
|
24
|
-
ScreenBuffer keeps the output as a **list of lines** and redraws the whole panel from that list. You
|
|
24
|
+
ScreenBuffer is the third choice. It keeps the output as a **list of numbered lines** and redraws the whole panel from that list. You decide what lives at each index, and you rewrite a slot whenever you like. The rest of the screen never moves.
|
|
25
25
|
|
|
26
26
|
```python
|
|
27
27
|
from screenbuffer import ScreenBuffer
|
|
28
28
|
|
|
29
|
-
sb = ScreenBuffer(max_display=8, header="Scraper
|
|
30
|
-
log_file="scrape_run", max_wait=1000, start=True)
|
|
29
|
+
sb = ScreenBuffer(max_display=8, header="Scraper, page 0/200")
|
|
31
30
|
|
|
32
31
|
for i, page in enumerate(pages, 1):
|
|
33
|
-
sb.update(header=f"Scraper
|
|
32
|
+
sb.update(header=f"Scraper, page {i}/200")
|
|
34
33
|
sb.put(f"page {i}: +{len(page)} rows")
|
|
35
34
|
sb.display() # draw once per step
|
|
36
35
|
```
|
|
37
36
|
|
|
38
37
|
The panel shows a fixed header, the newest 8 lines, and (optionally) a footer. Nothing else on screen moves.
|
|
39
38
|
|
|
39
|
+
That is the whole style: header, body, footer. [Header, body, footer](#header-body-footer) shows the different ways to play it.
|
|
40
|
+
|
|
40
41
|
---
|
|
41
42
|
|
|
42
43
|
## Who it is for
|
|
43
44
|
|
|
44
|
-
Analysts, data engineers, data scientists and researchers who run long jobs in Jupyter and want to watch progress without flooding the cell, and who want a plain-text record of what happened.
|
|
45
|
+
Analysts, data engineers, data scientists and researchers who run long jobs in Jupyter and want to watch progress without flooding the cell, and who want a plain-text record of what happened if they ask for one.
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
---
|
|
47
48
|
|
|
48
49
|
## Requirements
|
|
49
50
|
|
|
50
51
|
- Python 3.8+ and Jupyter (Notebook, JupyterLab, VS Code, or a Jupyter console).
|
|
51
52
|
- No third-party packages. Uses `IPython.display.clear_output`; if IPython is missing, drawing silently does nothing (the buffer and log still work).
|
|
52
53
|
|
|
54
|
+
---
|
|
55
|
+
|
|
53
56
|
## Install
|
|
54
57
|
|
|
55
|
-
|
|
58
|
+
```
|
|
59
|
+
pip install screenbuffer
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
In a notebook cell, use `%pip install screenbuffer` so it installs into the kernel's environment. Then:
|
|
56
63
|
|
|
57
64
|
```python
|
|
58
65
|
from screenbuffer import ScreenBuffer
|
|
@@ -60,56 +67,122 @@ from screenbuffer import ScreenBuffer
|
|
|
60
67
|
|
|
61
68
|
---
|
|
62
69
|
|
|
63
|
-
##
|
|
70
|
+
## Header, body, footer
|
|
64
71
|
|
|
65
|
-
|
|
72
|
+
Every panel has three parts:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
header pinned at the top. Set it with header=, change it with sb.update(header=...)
|
|
76
|
+
body the lines you put(). max_display is how many of them are shown
|
|
77
|
+
footer pinned at the bottom. Anything you want: counters, status, ETA, an error banner
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The header and footer never scroll. The body is the window of `max_display` lines. You decide what goes in each part, and you can change any of them in the middle of a loop.
|
|
81
|
+
|
|
82
|
+
### Ways to play
|
|
83
|
+
|
|
84
|
+
**1. Rolling body.** Append lines and let the window show the newest N. Use the footer for the running count.
|
|
66
85
|
|
|
67
86
|
```python
|
|
68
|
-
sb = ScreenBuffer(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
footer=None, footer_alignment=None,
|
|
77
|
-
show_header=None, show_footer=None,
|
|
78
|
-
silent_display=None, only_display=None,
|
|
79
|
-
auto_clean_edges=False,
|
|
80
|
-
auto_clean_silent_only_display=False,
|
|
81
|
-
shift_max_display=True,
|
|
82
|
-
width=None, # None = detect terminal width
|
|
83
|
-
)
|
|
87
|
+
sb = ScreenBuffer(max_display=8, width=70,
|
|
88
|
+
header="Scraper", header_alignment="center",
|
|
89
|
+
footer="starting", footer_alignment="center")
|
|
90
|
+
|
|
91
|
+
for i, page in enumerate(pages, 1):
|
|
92
|
+
sb.put(f"page {i}: +{len(page)} rows")
|
|
93
|
+
sb.update(footer=f"{i}/{len(pages)} pages")
|
|
94
|
+
sb.display() # one redraw per step
|
|
84
95
|
```
|
|
85
96
|
|
|
86
|
-
|
|
87
|
-
|---|---|---|
|
|
88
|
-
| `wait` | `True` (default): draw only when you call `display()`. `False`: redraw on every change | [Redraw modes](#redraw-modes) |
|
|
89
|
-
| `log_file` | Name of the log set (creates `screen_buffer_logs/<name>.txt` and companions). `None` = nothing written to disk | [Log files](#log-files) |
|
|
90
|
-
| `start` | `True` wipes existing files for that name; `False` restores the saved panel | [Persistence](#persistence-and-resume) |
|
|
91
|
-
| `max_display` | Draw only the newest N lines | [Display window](#display-window) |
|
|
92
|
-
| `max_main`, `max_wait` | Line caps for the history and saved-buffer files | [Limits](#limits-and-long-runs) |
|
|
93
|
-
| `header`, `footer` | Fixed text above / below the lines | [Header and footer](#header-and-footer) |
|
|
94
|
-
| `header_alignment`, `footer_alignment` | Alignment for those two | [Alignment](#alignment) |
|
|
95
|
-
| `show_header`, `show_footer` | Hide or show them (default: shown when text is set) | [Header and footer](#header-and-footer) |
|
|
96
|
-
| `silent_display`, `only_display` | Hide some lines / show only some lines | [Filtering](#filtering) |
|
|
97
|
-
| `auto_clean_silent_only_display`, `shift_max_display`, `auto_clean_edges` | Fine-tune filtering and spacing | [Filtering](#filtering) |
|
|
98
|
-
| `width` | Panel width in columns for `center` / `right`; `None` = detect | [Alignment](#alignment) |
|
|
97
|
+
**2. Slot body.** Treat the body as a few fixed slots and rewrite them by index. Writing to the highest index builds all the slots in one call, and `max_display` equals the number of slots, so every slot is always on screen.
|
|
99
98
|
|
|
100
|
-
|
|
99
|
+
```python
|
|
100
|
+
sb = ScreenBuffer(max_display=3, header="Current job", footer="running")
|
|
101
|
+
sb.put("", index=2) # builds body slots 0, 1 and 2
|
|
102
|
+
|
|
103
|
+
for item in items:
|
|
104
|
+
sb.put(f"working on {item}", index=0) # slot 0: what is running now
|
|
105
|
+
try:
|
|
106
|
+
sb.put(f"last ok: {work(item)}", index=1) # slot 1: last success
|
|
107
|
+
except Exception as e:
|
|
108
|
+
sb.put(f"last error: {e}", index=2) # slot 2: last error
|
|
109
|
+
sb.display()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**3. Ring of recent results.** Keep a small list and rewrite the slots each time. No deleting, and the newest result is always at the bottom.
|
|
101
113
|
|
|
102
114
|
```python
|
|
103
|
-
sb = ScreenBuffer(
|
|
104
|
-
sb
|
|
105
|
-
|
|
115
|
+
sb = ScreenBuffer(max_display=4, footer="0 found")
|
|
116
|
+
sb.put("", index=3)
|
|
117
|
+
|
|
118
|
+
recent = []
|
|
119
|
+
for n, item in enumerate(items, 1):
|
|
120
|
+
if hit(item):
|
|
121
|
+
recent = (recent + [str(item)])[-4:]
|
|
122
|
+
for k, text in enumerate(recent):
|
|
123
|
+
sb.put(text, index=k)
|
|
124
|
+
sb.update(footer=f"{n}/{len(items)} checked")
|
|
125
|
+
sb.display()
|
|
106
126
|
```
|
|
107
127
|
|
|
128
|
+
**4. Scoreboard.** One body slot per metric, rewritten every step.
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
sb = ScreenBuffer(max_display=3, header="Run stats")
|
|
132
|
+
sb.put("", index=2)
|
|
133
|
+
sb.put(f"rows: {rows}", index=0)
|
|
134
|
+
sb.put(f"errors: {errors}", index=1)
|
|
135
|
+
sb.put(f"speed: {speed}/s", index=2)
|
|
136
|
+
sb.display()
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**5. Footer as a banner.** The footer can carry a warning and then go back to normal.
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
sb.update(footer="!! network error, retrying !!")
|
|
143
|
+
sb.display()
|
|
144
|
+
# later
|
|
145
|
+
sb.update(footer="back online")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Mixing and matching
|
|
149
|
+
|
|
150
|
+
| Move | How |
|
|
151
|
+
|---|---|
|
|
152
|
+
| Something that must always be visible | Put it in the header or footer |
|
|
153
|
+
| A rolling log | Append with `sb.put("text")`; `max_display` shows the newest N |
|
|
154
|
+
| Fixed slots | Build them with `put("", index=last)`, rewrite with `put(text, index=n)`, set `max_display` to the slot count |
|
|
155
|
+
| A spacer line | `sb.put("", index=n)` |
|
|
156
|
+
| Different looks per line | `alignment="center"`, `"right"`, or an indent |
|
|
157
|
+
| A rolling log plus a status footer | Append to the body and call `sb.update(footer=...)` |
|
|
158
|
+
|
|
159
|
+
### House rules
|
|
160
|
+
|
|
161
|
+
1. **Use the header and footer for what must stay visible.** They never scroll, whatever the body does.
|
|
162
|
+
2. **The body is `max_display` lines.** Append for a rolling log, or rewrite by index for fixed slots.
|
|
163
|
+
3. **For slots, match the window to the slot count.** Then nothing scrolls out, and appending would break the layout, so rewrite instead.
|
|
164
|
+
4. **One redraw per step.** Keep the default `wait=True` and call `sb.display()` once at the end of each step, so you never see a half-updated panel.
|
|
165
|
+
5. **Spacing is yours.** `auto_clean_edges=True` trims blank edge lines in the body and adds one blank line on each side. For a tight panel, leave it `False` and add your own blank lines.
|
|
166
|
+
|
|
167
|
+
It is still one plain list of lines, so everything else works too: per-line alignment, a log of every change, filters, and resume after a crash.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Core concepts
|
|
172
|
+
|
|
173
|
+
- **Slots**: body lines used as fixed numbered positions that you rewrite by index. See [Header, body, footer](#header-body-footer).
|
|
174
|
+
- **Buffer**: the list of all lines you have `put()`. Each line has an index (0, 1, 2, ...).
|
|
175
|
+
- **View**: what is drawn. It is the header, then the visible lines (after the window and any filter), then the footer.
|
|
176
|
+
- **History log**: a timestamped file of every `put()`, only if you set `log_file`. Append-only record of the run.
|
|
177
|
+
- **Wait file**: a saved copy of the *current* buffer, so the panel can be restored (also only with `log_file`).
|
|
178
|
+
|
|
179
|
+
The screen is redrawn with Jupyter's own `clear_output(wait=True)`: the old frame stays until the new text is ready, then they are swapped. The new text is printed as one string, with your exact spacing.
|
|
180
|
+
|
|
108
181
|
---
|
|
109
182
|
|
|
110
183
|
## `put()`: the core call
|
|
111
184
|
|
|
112
|
-
`put()` is how everything gets into the panel. Every other feature (alignment, window, filters, logs, resume) works on the lines you `put()`.
|
|
185
|
+
`put()` is how everything gets into the panel. It is also how you rewrite slots. Every other feature (alignment, window, filters, logs, resume) works on the lines you `put()`.
|
|
113
186
|
|
|
114
187
|
```python
|
|
115
188
|
sb.put(text, state=0, index=None, fill_value=None, alignment=None)
|
|
@@ -135,7 +208,7 @@ sb.put("row 10", index=10) # lines 2..9 become blank, line 10 is "
|
|
|
135
208
|
sb.put("progress: 80%", index=-1) # negative: replaces the LAST line
|
|
136
209
|
```
|
|
137
210
|
|
|
138
|
-
**Replacing by index is the "update one part in a loop" trick
|
|
211
|
+
**Replacing by index is the "update one part in a loop" trick**, and it is how slots work (see [Header, body, footer](#header-body-footer)). Reserve a line, then keep rewriting it:
|
|
139
212
|
|
|
140
213
|
```python
|
|
141
214
|
sb.put("Progress: 0%", index=0) # reserve line 0 as a status line
|
|
@@ -144,7 +217,7 @@ for i in range(100):
|
|
|
144
217
|
sb.put(f"processed item {i}") # new lines keep appending below
|
|
145
218
|
```
|
|
146
219
|
|
|
147
|
-
(In a window with `max_display`, a line that has scrolled out of the window is still updated in the buffer, but you won't see it.
|
|
220
|
+
(In a window with `max_display`, a line that has scrolled out of the window is still updated in the buffer, but you won't see it. To keep something always visible, use the header or footer, or keep the buffer no longer than the window, as in the slot body (see [Header, body, footer](#header-body-footer)).)
|
|
148
221
|
|
|
149
222
|
### What happens on every `put()`
|
|
150
223
|
|
|
@@ -170,53 +243,78 @@ The history entry records the index, even for replacements:
|
|
|
170
243
|
|
|
171
244
|
---
|
|
172
245
|
|
|
173
|
-
##
|
|
246
|
+
## Create a buffer
|
|
174
247
|
|
|
175
|
-
|
|
248
|
+
Everything starts here. All arguments are optional.
|
|
176
249
|
|
|
177
|
-
|
|
250
|
+
```python
|
|
251
|
+
sb = ScreenBuffer(
|
|
252
|
+
wait=True, # False = redraw on every change
|
|
253
|
+
log_file=None, # name for the log set; None = no files
|
|
254
|
+
max_main=None, # history file line cap
|
|
255
|
+
max_wait=None, # saved-buffer file line cap
|
|
256
|
+
max_display=None, # lines drawn (newest N)
|
|
257
|
+
start=False, # True = wipe existing files for this log_file
|
|
258
|
+
header=None, header_alignment=None,
|
|
259
|
+
footer=None, footer_alignment=None,
|
|
260
|
+
show_header=None, show_footer=None,
|
|
261
|
+
silent_display=None, only_display=None,
|
|
262
|
+
auto_clean_edges=False,
|
|
263
|
+
auto_clean_silent_only_display=False,
|
|
264
|
+
shift_max_display=True,
|
|
265
|
+
width=None, # None = detect terminal width
|
|
266
|
+
)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
| Argument | What it does | Details |
|
|
178
270
|
|---|---|---|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
182
|
-
|
|
|
183
|
-
|
|
|
184
|
-
|
|
|
185
|
-
|
|
|
186
|
-
|
|
|
187
|
-
| Hide / show
|
|
188
|
-
|
|
|
189
|
-
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
| Start fresh and wipe old files | `start=True` | [Persistence](#persistence-and-resume) |
|
|
199
|
-
| Cap file sizes | `max_main`, `max_wait` | [Limits](#limits-and-long-runs) |
|
|
200
|
-
| Change any setting while running | `sb.update(...)` | [Changing settings](#changing-settings-at-runtime) |
|
|
201
|
-
| Fix wrong center/right alignment | `width=` | [Alignment](#alignment) |
|
|
202
|
-
| Get the panel as a string | `sb.get_text()` | [Reading state](#reading-state) |
|
|
203
|
-
| See raw buffer with indexes | `sb.display_index()` | [Reading state](#reading-state) |
|
|
204
|
-
| Use from several threads | built in | [Threads](#threads) |
|
|
271
|
+
| `wait` | `True` (default): draw only when you call `display()`. `False`: redraw on every change | [Redraw modes](#redraw-modes) |
|
|
272
|
+
| `log_file` | Name of the log set (creates `screen_buffer_logs/<name>.txt` and companions). `None` = nothing written to disk | [Log files](#log-files) |
|
|
273
|
+
| `start` | `True` wipes existing files for that name; `False` restores the saved panel | [Persistence](#persistence-and-resume) |
|
|
274
|
+
| `max_display` | Draw only the newest N lines | [Display window](#display-window) |
|
|
275
|
+
| `max_main`, `max_wait` | Line caps for the history and saved-buffer files | [Limits](#limits-and-long-runs) |
|
|
276
|
+
| `header`, `footer` | Fixed text above / below the lines | [Header and footer](#header-and-footer) |
|
|
277
|
+
| `header_alignment`, `footer_alignment` | Alignment for those two | [Alignment](#alignment) |
|
|
278
|
+
| `show_header`, `show_footer` | Hide or show them (default: shown when text is set) | [Header and footer](#header-and-footer) |
|
|
279
|
+
| `silent_display`, `only_display` | Hide some lines / show only some lines | [Filtering](#filtering) |
|
|
280
|
+
| `auto_clean_silent_only_display`, `shift_max_display`, `auto_clean_edges` | Fine-tune filtering and spacing | [Filtering](#filtering) |
|
|
281
|
+
| `width` | Panel width in columns for `center` / `right`; `None` = detect | [Alignment](#alignment) |
|
|
282
|
+
|
|
283
|
+
Typical setups:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
sb = ScreenBuffer() # simplest: collect lines, then sb.display()
|
|
287
|
+
sb = ScreenBuffer(wait=False, max_display=10) # live panel, newest 10 lines
|
|
288
|
+
sb = ScreenBuffer(max_display=10, log_file="run1", max_wait=1000, start=True) # long job with a log
|
|
289
|
+
```
|
|
205
290
|
|
|
206
291
|
---
|
|
207
292
|
|
|
208
|
-
##
|
|
293
|
+
## Usage
|
|
209
294
|
|
|
210
|
-
|
|
211
|
-
- **View**: what is drawn. It is the header, then the visible lines (after the window and any filter), then the footer.
|
|
212
|
-
- **History log**: a timestamped file of every `put()`. Append-only record of the run.
|
|
213
|
-
- **Wait file**: a saved copy of the *current* buffer, so the panel can be restored.
|
|
295
|
+
### Display window
|
|
214
296
|
|
|
215
|
-
|
|
297
|
+
```python
|
|
298
|
+
sb = ScreenBuffer(wait=False, max_display=10)
|
|
299
|
+
```
|
|
216
300
|
|
|
217
|
-
|
|
301
|
+
Only the newest 10 lines are drawn; all lines stay in the buffer and the log. Drawing cost depends on `max_display`, not on how many lines exist (8 lines out of 100,000 draws in under a millisecond).
|
|
218
302
|
|
|
219
|
-
|
|
303
|
+
When the buffer is exactly as long as `max_display`, the window shows the whole thing. That is how a slot body keeps every slot visible (see [Header, body, footer](#header-body-footer)).
|
|
304
|
+
|
|
305
|
+
### Header and footer
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
sb = ScreenBuffer(wait=False, header="Job: nightly_etl", footer="status: running",
|
|
309
|
+
header_alignment="center")
|
|
310
|
+
|
|
311
|
+
sb.update(header="Job: nightly_etl, 42%") # change in a loop
|
|
312
|
+
sb.update(footer="!! source unreachable !!")
|
|
313
|
+
sb.update(show_footer=False) # hide it (the text is kept)
|
|
314
|
+
sb.update(show_footer=True) # bring it back
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Setting a non-empty header/footer turns it on. Header and footer are saved to files when `log_file` is set. `set_header(text, save_to_file=False)` / `set_footer(...)` keep one in memory only.
|
|
220
318
|
|
|
221
319
|
### Alignment
|
|
222
320
|
|
|
@@ -243,27 +341,14 @@ Alignment follows its line: it is saved and restored with the buffer, and re-key
|
|
|
243
341
|
|
|
244
342
|
**Width:** by default the terminal width is detected. Jupyter front ends sometimes report the wrong width, which skews `center`/`right`. Set `width=70` (or whatever suits your output area) to fix it.
|
|
245
343
|
|
|
246
|
-
###
|
|
247
|
-
|
|
248
|
-
```python
|
|
249
|
-
sb = ScreenBuffer(wait=False, max_display=10)
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
Only the newest 10 lines are drawn; all lines stay in the buffer and the log. Drawing cost depends on `max_display`, not on how many lines exist (8 lines out of 100,000 draws in under a millisecond).
|
|
253
|
-
|
|
254
|
-
### Header and footer
|
|
255
|
-
|
|
256
|
-
```python
|
|
257
|
-
sb = ScreenBuffer(wait=False, header="Job: nightly_etl", footer="status: running",
|
|
258
|
-
header_alignment="center")
|
|
344
|
+
### Redraw modes
|
|
259
345
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
sb.
|
|
264
|
-
```
|
|
346
|
+
| Mode | Behaviour |
|
|
347
|
+
|---|---|
|
|
348
|
+
| `wait=False` | Live. Every change redraws (skipped if nothing on screen changed). The panel draws once on creation. |
|
|
349
|
+
| `wait=True` (default) | Quiet. Changes are stored but nothing is drawn until you call `sb.display()`. Good for loading lots of lines first. |
|
|
265
350
|
|
|
266
|
-
|
|
351
|
+
`sb.display()` always draws, even if nothing changed. `sb.flush()` clears the Jupyter output immediately.
|
|
267
352
|
|
|
268
353
|
### Smooth updates
|
|
269
354
|
|
|
@@ -286,15 +371,6 @@ Other things that keep it smooth, all automatic:
|
|
|
286
371
|
|
|
287
372
|
**Fast loops:** if a loop calls `put()` hundreds or thousands of times per second, Jupyter itself can be flooded (its output channel is rate-limited). Call `sb.display()` once per chunk of ~50 items instead of on every item, or add a short sleep.
|
|
288
373
|
|
|
289
|
-
### Redraw modes
|
|
290
|
-
|
|
291
|
-
| Mode | Behaviour |
|
|
292
|
-
|---|---|
|
|
293
|
-
| `wait=False` | Live. Every change redraws (skipped if nothing on screen changed). The panel draws once on creation. |
|
|
294
|
-
| `wait=True` (default) | Quiet. Changes are stored but nothing is drawn until you call `sb.display()`. Good for loading lots of lines first. |
|
|
295
|
-
|
|
296
|
-
`sb.display()` always draws, even if nothing changed. `sb.flush()` clears the Jupyter output immediately.
|
|
297
|
-
|
|
298
374
|
### Filtering
|
|
299
375
|
|
|
300
376
|
Hide or isolate lines **on screen only**. They stay in the buffer and the log. Indexes refer to the full buffer.
|
|
@@ -331,6 +407,33 @@ sb.clear_all() # empty the buffer AND remove hea
|
|
|
331
407
|
|
|
332
408
|
`contains` is case-insensitive. With `exact=True` (default) it matches a whole word; with `exact=False`, any substring. Alignments move with their lines when others are deleted. Deleting does **not** remove entries from the history log.
|
|
333
409
|
|
|
410
|
+
With slots you rarely need `delete`: rewrite the slot instead, and the layout never shifts.
|
|
411
|
+
|
|
412
|
+
### Reading state
|
|
413
|
+
|
|
414
|
+
```python
|
|
415
|
+
sb.get_text() # the current panel as one string
|
|
416
|
+
sb.display_index() # prints every buffer line with its index (debugging)
|
|
417
|
+
sb.buffer # the raw list of lines
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
`display_index()` is handy while designing slots: it shows which index holds which line.
|
|
421
|
+
|
|
422
|
+
### Changing settings at runtime
|
|
423
|
+
|
|
424
|
+
```python
|
|
425
|
+
sb.update(max_display=20, width=90, wait=False)
|
|
426
|
+
sb.update(log_file="second_phase") # switch to a different log set
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Settable: `wait`, `max_main`, `max_wait`, `max_display`, `shift_max_display`, `auto_clean_edges`, `auto_clean_silent_only_display`, `header`, `footer`, `header_alignment`, `footer_alignment`, `show_header`, `show_footer`, `silent_display`, `only_display`, `width`, `log_file`.
|
|
430
|
+
|
|
431
|
+
`update()` checks everything first: if any value is invalid it raises and changes **nothing**. Unknown names raise `ValueError`.
|
|
432
|
+
|
|
433
|
+
### Threads
|
|
434
|
+
|
|
435
|
+
`put()`, `update()`, `delete()` and drawing are thread-safe. Disk writes keep buffer order even when several threads write at once.
|
|
436
|
+
|
|
334
437
|
### Log files
|
|
335
438
|
|
|
336
439
|
```python
|
|
@@ -390,30 +493,44 @@ Log file: /home/you/project/screen_buffer_logs/scrape_run.txt
|
|
|
390
493
|
|
|
391
494
|
`max_wait` cannot be smaller than `max_display`. **Set `max_wait` (500–1000 is typical) on long runs**; it keeps the saved copy small and makes appends cheap.
|
|
392
495
|
|
|
393
|
-
These cap files, **not memory**. The in-memory buffer keeps every line (roughly 100 MB per million short lines).
|
|
394
|
-
|
|
395
|
-
### Changing settings at runtime
|
|
396
|
-
|
|
397
|
-
```python
|
|
398
|
-
sb.update(max_display=20, width=90, wait=False)
|
|
399
|
-
sb.update(log_file="second_phase") # switch to a different log set
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
Settable: `wait`, `max_main`, `max_wait`, `max_display`, `shift_max_display`, `auto_clean_edges`, `auto_clean_silent_only_display`, `header`, `footer`, `header_alignment`, `footer_alignment`, `show_header`, `show_footer`, `silent_display`, `only_display`, `width`, `log_file`.
|
|
403
|
-
|
|
404
|
-
`update()` checks everything first: if any value is invalid it raises and changes **nothing**. Unknown names raise `ValueError`.
|
|
496
|
+
These cap files, **not memory**. The in-memory buffer keeps every line (roughly 100 MB per million short lines). A slot body has a fixed number of lines, so its memory never grows.
|
|
405
497
|
|
|
406
|
-
|
|
498
|
+
---
|
|
407
499
|
|
|
408
|
-
|
|
409
|
-
sb.get_text() # the current panel as one string
|
|
410
|
-
sb.display_index() # prints every buffer line with its index (debugging)
|
|
411
|
-
sb.buffer # the raw list of lines
|
|
412
|
-
```
|
|
500
|
+
## Feature index
|
|
413
501
|
|
|
414
|
-
|
|
502
|
+
Find what you need:
|
|
415
503
|
|
|
416
|
-
|
|
504
|
+
| I want to... | Use | Section |
|
|
505
|
+
|---|---|---|
|
|
506
|
+
| Keep fixed slots in the body | `put(..., index=n)` + `max_display` = slot count | [Header, body, footer](#header-body-footer) |
|
|
507
|
+
| Keep something always visible | `header=` / `footer=`, changed with `sb.update(...)` | [Header, body, footer](#header-body-footer) |
|
|
508
|
+
| Create a buffer, see every option | `ScreenBuffer(...)` | [Create a buffer](#create-a-buffer) |
|
|
509
|
+
| Add a line | `sb.put("text")` | [put()](#put-the-core-call) |
|
|
510
|
+
| Replace / update an existing line | `sb.put("new", index=3)` | [put()](#put-the-core-call) |
|
|
511
|
+
| Mark a line OK / warning / error in the log | `sb.put("x", state=0/1/2)` | [put()](#put-the-core-call) |
|
|
512
|
+
| Center, right-align or indent a line | `put(..., alignment=...)` | [Alignment](#alignment) |
|
|
513
|
+
| Show only the newest N lines | `max_display=N` | [Display window](#display-window) |
|
|
514
|
+
| Have a fixed header / footer | `header=`, `footer=` | [Header and footer](#header-and-footer) |
|
|
515
|
+
| Change header/footer in a loop | `sb.update(header=...)` | [Header and footer](#header-and-footer) |
|
|
516
|
+
| Hide / show header or footer | `show_header`, `show_footer` | [Header and footer](#header-and-footer) |
|
|
517
|
+
| Draw once per loop step (no flicker) | `wait=True` + `sb.display()` | [Smooth updates](#smooth-updates) |
|
|
518
|
+
| Control when the screen redraws | `wait=True/False`, `sb.display()` | [Redraw modes](#redraw-modes) |
|
|
519
|
+
| Hide some lines on screen, keep them stored | `silent_display=` | [Filtering](#filtering) |
|
|
520
|
+
| Show only some lines on screen | `only_display=` | [Filtering](#filtering) |
|
|
521
|
+
| Remove blank gaps left by filtering | `auto_clean_silent_only_display=True` | [Filtering](#filtering) |
|
|
522
|
+
| Delete lines | `sb.delete(...)` | [Deleting and clearing](#deleting-and-clearing) |
|
|
523
|
+
| Wipe the screen buffer | `sb.clear()`, `sb.clear_all()` | [Deleting and clearing](#deleting-and-clearing) |
|
|
524
|
+
| Keep a full history file | `log_file="name"` | [Log files](#log-files) |
|
|
525
|
+
| Read the history back (with its location) | `sb.read_log()` | [Log files](#log-files) |
|
|
526
|
+
| Resume the panel after a kernel restart | `log_file=` without `start=True` | [Persistence](#persistence-and-resume) |
|
|
527
|
+
| Start fresh and wipe old files | `start=True` | [Persistence](#persistence-and-resume) |
|
|
528
|
+
| Cap file sizes | `max_main`, `max_wait` | [Limits](#limits-and-long-runs) |
|
|
529
|
+
| Change any setting while running | `sb.update(...)` | [Changing settings](#changing-settings-at-runtime) |
|
|
530
|
+
| Fix wrong center/right alignment | `width=` | [Alignment](#alignment) |
|
|
531
|
+
| Get the panel as a string | `sb.get_text()` | [Reading state](#reading-state) |
|
|
532
|
+
| See raw buffer with indexes | `sb.display_index()` | [Reading state](#reading-state) |
|
|
533
|
+
| Use from several threads | built in | [Threads](#threads) |
|
|
417
534
|
|
|
418
535
|
---
|
|
419
536
|
|
|
@@ -421,7 +538,7 @@ sb.buffer # the raw list of lines
|
|
|
421
538
|
|
|
422
539
|
| Method | Purpose |
|
|
423
540
|
|---|---|
|
|
424
|
-
| `put(text, state=0, index=None, fill_value=None, alignment=None)` | Add or replace a line |
|
|
541
|
+
| `put(text, state=0, index=None, fill_value=None, alignment=None)` | Add or replace a line (also the slot move) |
|
|
425
542
|
| `update(**settings)` | Change any setting (validated first) |
|
|
426
543
|
| `display()` | Draw now |
|
|
427
544
|
| `flush()` | Clear the Jupyter output now |
|
|
@@ -437,6 +554,34 @@ sb.buffer # the raw list of lines
|
|
|
437
554
|
|
|
438
555
|
---
|
|
439
556
|
|
|
557
|
+
## Example: slots
|
|
558
|
+
|
|
559
|
+
The same scraper with a slot body: three body slots, a header and a footer.
|
|
560
|
+
|
|
561
|
+
```python
|
|
562
|
+
import time, random
|
|
563
|
+
from screenbuffer import ScreenBuffer
|
|
564
|
+
|
|
565
|
+
sb = ScreenBuffer(max_display=3, width=70,
|
|
566
|
+
header="SCRAPER", header_alignment="center",
|
|
567
|
+
footer="0s elapsed", footer_alignment="center")
|
|
568
|
+
sb.put("", index=2) # builds body slots 0, 1 and 2
|
|
569
|
+
|
|
570
|
+
for t in range(1, 121):
|
|
571
|
+
sb.put(f"page {t}: +{random.randint(3, 9)} items", index=0) # current page
|
|
572
|
+
if t % 10 == 0:
|
|
573
|
+
sb.put(f"checkpoint at {t}s", index=1) # last checkpoint
|
|
574
|
+
if t % 40 == 0:
|
|
575
|
+
sb.put(f"retry at {t}s", index=2, alignment=4) # last retry
|
|
576
|
+
sb.update(footer=f"{t}s elapsed")
|
|
577
|
+
sb.display() # one redraw per step
|
|
578
|
+
time.sleep(0.5)
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
Nothing scrolls. The header and footer stay put, and each slot keeps its latest value.
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
440
585
|
## Example: network scraper with an error banner
|
|
441
586
|
|
|
442
587
|
```python
|
|
@@ -444,18 +589,18 @@ import time, random
|
|
|
444
589
|
from screenbuffer import ScreenBuffer
|
|
445
590
|
|
|
446
591
|
sb = ScreenBuffer(max_display=8, width=70,
|
|
447
|
-
header="SCRAPER
|
|
592
|
+
header="SCRAPER ONLINE", header_alignment="center",
|
|
448
593
|
footer_alignment="center",
|
|
449
594
|
log_file="scraper", max_wait=500, start=True)
|
|
450
595
|
|
|
451
596
|
for t in range(120):
|
|
452
597
|
online = not (40 <= t < 70)
|
|
453
598
|
if online:
|
|
454
|
-
sb.update(header=f"SCRAPER
|
|
599
|
+
sb.update(header=f"SCRAPER ONLINE, {t}s", show_footer=False)
|
|
455
600
|
sb.put(f"[{t}s] page {t//2}: +{random.randint(3, 9)} items")
|
|
456
601
|
else:
|
|
457
|
-
sb.update(header=f"SCRAPER
|
|
458
|
-
footer="!! NETWORK ERROR
|
|
602
|
+
sb.update(header=f"SCRAPER OFFLINE, {t}s",
|
|
603
|
+
footer="!! NETWORK ERROR, retrying !!")
|
|
459
604
|
if t % 5 == 0:
|
|
460
605
|
sb.put(f"[{t}s] retrying...", state=1, alignment=4)
|
|
461
606
|
sb.display() # one redraw per step
|
|
@@ -468,22 +613,10 @@ The error banner appears at the bottom during the outage and disappears when the
|
|
|
468
613
|
|
|
469
614
|
---
|
|
470
615
|
|
|
471
|
-
## Performance (measured in a sandbox)
|
|
472
|
-
|
|
473
|
-
| Case | Before tuning | Now |
|
|
474
|
-
|---|---|---|
|
|
475
|
-
| `put()` with 20k lines in the buffer (no log / log / log + `max_main`) | 2.1 / 10.7 / 16.4 ms | 0.005 / 0.035 / 0.11 ms |
|
|
476
|
-
| Draw 8 lines out of 100k | 30.5 ms | 0.72 ms |
|
|
477
|
-
| Frame wait with a 20 ms disk delay | 202 ms | 0.1 ms |
|
|
478
|
-
| Old frame cleared → new text shown | 100–200 ms | ~0.5 ms |
|
|
479
|
-
| Redraws in a 300-step demo (one draw per step) | 241 | 81 |
|
|
480
|
-
|
|
481
|
-
These come from a Linux sandbox with a simulated Jupyter kernel. Real browser rendering was not measurable there; if you see flicker, see Troubleshooting.
|
|
482
|
-
|
|
483
616
|
## Known limits
|
|
484
617
|
|
|
485
618
|
- **Jupyter only.** Terminals and plain scripts are not supported (drawing is a no-op without IPython).
|
|
486
|
-
- **Replacing or deleting an old line rewrites the saved-buffer file.** Appending is cheap; edits cost O(lines in that file), which `max_wait` keeps small.
|
|
619
|
+
- **Replacing or deleting an old line rewrites the saved-buffer file.** Appending is cheap; edits cost O(lines in that file), which `max_wait` keeps small. A slot body is small by design, so this stays cheap.
|
|
487
620
|
- **`max_main` trimming** rewrites the history file once it goes over the cap.
|
|
488
621
|
- **Memory is not capped** (see Limits).
|
|
489
622
|
- **Queued writes** can be lost if the kernel is killed abruptly. Call `sb.read_log()` (it waits for writes) or let the cell finish before shutting down.
|
|
@@ -496,6 +629,8 @@ These come from a Linux sandbox with a simulated Jupyter kernel. Real browser re
|
|
|
496
629
|
| Symptom | Likely cause / fix |
|
|
497
630
|
|---|---|
|
|
498
631
|
| Nothing appears | `wait=True` (default): call `sb.display()`, or create with `wait=False` |
|
|
632
|
+
| A slot scrolls out of view | The buffer is longer than `max_display`. Make the window equal the slot count, or stop appending and rewrite slots by index |
|
|
633
|
+
| Blank line above the first body line | `auto_clean_edges=True` adds one. Set it to `False` and add your own blank lines |
|
|
499
634
|
| Flicker in a fast loop | Use `wait=True` and call `sb.display()` once per step (or per chunk of items); add a tiny sleep |
|
|
500
635
|
| Header and footer change on separate frames | Use `wait=True`, make both updates, then call `sb.display()` once |
|
|
501
636
|
| `center` / `right` look off | Set `width=` to your output area's real width |
|