screenbuffer 1.0.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-1.0.0/screenbuffer.egg-info → screenbuffer-1.0.1}/PKG-INFO +280 -149
- screenbuffer-1.0.0/PKG-INFO → screenbuffer-1.0.1/README.md +626 -509
- {screenbuffer-1.0.0 → screenbuffer-1.0.1}/pyproject.toml +1 -1
- screenbuffer-1.0.0/README.md → screenbuffer-1.0.1/screenbuffer.egg-info/PKG-INFO +640 -495
- {screenbuffer-1.0.0 → screenbuffer-1.0.1}/screenbuffer/__init__.py +0 -0
- {screenbuffer-1.0.0 → screenbuffer-1.0.1}/screenbuffer/align.py +0 -0
- {screenbuffer-1.0.0 → screenbuffer-1.0.1}/screenbuffer/buffer.py +0 -0
- {screenbuffer-1.0.0 → screenbuffer-1.0.1}/screenbuffer/store.py +0 -0
- {screenbuffer-1.0.0 → screenbuffer-1.0.1}/screenbuffer/view.py +0 -0
- {screenbuffer-1.0.0 → screenbuffer-1.0.1}/screenbuffer.egg-info/SOURCES.txt +0 -0
- {screenbuffer-1.0.0 → screenbuffer-1.0.1}/screenbuffer.egg-info/dependency_links.txt +0 -0
- {screenbuffer-1.0.0 → screenbuffer-1.0.1}/screenbuffer.egg-info/requires.txt +0 -0
- {screenbuffer-1.0.0 → screenbuffer-1.0.1}/screenbuffer.egg-info/top_level.txt +0 -0
- {screenbuffer-1.0.0 → screenbuffer-1.0.1}/setup.cfg +0 -0
- {screenbuffer-1.0.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: 1.0.
|
|
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,42 +14,45 @@ Requires-Dist: ipython; extra == "jupyter"
|
|
|
14
14
|
|
|
15
15
|
# ScreenBuffer
|
|
16
16
|
|
|
17
|
-
**A live output panel for Jupyter:
|
|
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
|
```
|
|
@@ -64,56 +67,122 @@ from screenbuffer import ScreenBuffer
|
|
|
64
67
|
|
|
65
68
|
---
|
|
66
69
|
|
|
67
|
-
##
|
|
70
|
+
## Header, body, footer
|
|
68
71
|
|
|
69
|
-
|
|
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.
|
|
70
85
|
|
|
71
86
|
```python
|
|
72
|
-
sb = ScreenBuffer(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
footer=None, footer_alignment=None,
|
|
81
|
-
show_header=None, show_footer=None,
|
|
82
|
-
silent_display=None, only_display=None,
|
|
83
|
-
auto_clean_edges=False,
|
|
84
|
-
auto_clean_silent_only_display=False,
|
|
85
|
-
shift_max_display=True,
|
|
86
|
-
width=None, # None = detect terminal width
|
|
87
|
-
)
|
|
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
|
|
88
95
|
```
|
|
89
96
|
|
|
90
|
-
|
|
91
|
-
|---|---|---|
|
|
92
|
-
| `wait` | `True` (default): draw only when you call `display()`. `False`: redraw on every change | [Redraw modes](#redraw-modes) |
|
|
93
|
-
| `log_file` | Name of the log set (creates `screen_buffer_logs/<name>.txt` and companions). `None` = nothing written to disk | [Log files](#log-files) |
|
|
94
|
-
| `start` | `True` wipes existing files for that name; `False` restores the saved panel | [Persistence](#persistence-and-resume) |
|
|
95
|
-
| `max_display` | Draw only the newest N lines | [Display window](#display-window) |
|
|
96
|
-
| `max_main`, `max_wait` | Line caps for the history and saved-buffer files | [Limits](#limits-and-long-runs) |
|
|
97
|
-
| `header`, `footer` | Fixed text above / below the lines | [Header and footer](#header-and-footer) |
|
|
98
|
-
| `header_alignment`, `footer_alignment` | Alignment for those two | [Alignment](#alignment) |
|
|
99
|
-
| `show_header`, `show_footer` | Hide or show them (default: shown when text is set) | [Header and footer](#header-and-footer) |
|
|
100
|
-
| `silent_display`, `only_display` | Hide some lines / show only some lines | [Filtering](#filtering) |
|
|
101
|
-
| `auto_clean_silent_only_display`, `shift_max_display`, `auto_clean_edges` | Fine-tune filtering and spacing | [Filtering](#filtering) |
|
|
102
|
-
| `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.
|
|
103
98
|
|
|
104
|
-
|
|
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.
|
|
105
113
|
|
|
106
114
|
```python
|
|
107
|
-
sb = ScreenBuffer(
|
|
108
|
-
sb
|
|
109
|
-
|
|
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()
|
|
126
|
+
```
|
|
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()
|
|
110
137
|
```
|
|
111
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
|
+
|
|
112
181
|
---
|
|
113
182
|
|
|
114
183
|
## `put()`: the core call
|
|
115
184
|
|
|
116
|
-
`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()`.
|
|
117
186
|
|
|
118
187
|
```python
|
|
119
188
|
sb.put(text, state=0, index=None, fill_value=None, alignment=None)
|
|
@@ -139,7 +208,7 @@ sb.put("row 10", index=10) # lines 2..9 become blank, line 10 is "
|
|
|
139
208
|
sb.put("progress: 80%", index=-1) # negative: replaces the LAST line
|
|
140
209
|
```
|
|
141
210
|
|
|
142
|
-
**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:
|
|
143
212
|
|
|
144
213
|
```python
|
|
145
214
|
sb.put("Progress: 0%", index=0) # reserve line 0 as a status line
|
|
@@ -148,7 +217,7 @@ for i in range(100):
|
|
|
148
217
|
sb.put(f"processed item {i}") # new lines keep appending below
|
|
149
218
|
```
|
|
150
219
|
|
|
151
|
-
(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)).)
|
|
152
221
|
|
|
153
222
|
### What happens on every `put()`
|
|
154
223
|
|
|
@@ -174,53 +243,78 @@ The history entry records the index, even for replacements:
|
|
|
174
243
|
|
|
175
244
|
---
|
|
176
245
|
|
|
177
|
-
##
|
|
246
|
+
## Create a buffer
|
|
178
247
|
|
|
179
|
-
|
|
248
|
+
Everything starts here. All arguments are optional.
|
|
180
249
|
|
|
181
|
-
|
|
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 |
|
|
182
270
|
|---|---|---|
|
|
183
|
-
|
|
|
184
|
-
|
|
|
185
|
-
|
|
|
186
|
-
|
|
|
187
|
-
|
|
|
188
|
-
|
|
|
189
|
-
|
|
|
190
|
-
|
|
|
191
|
-
| Hide / show
|
|
192
|
-
|
|
|
193
|
-
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
| Start fresh and wipe old files | `start=True` | [Persistence](#persistence-and-resume) |
|
|
203
|
-
| Cap file sizes | `max_main`, `max_wait` | [Limits](#limits-and-long-runs) |
|
|
204
|
-
| Change any setting while running | `sb.update(...)` | [Changing settings](#changing-settings-at-runtime) |
|
|
205
|
-
| Fix wrong center/right alignment | `width=` | [Alignment](#alignment) |
|
|
206
|
-
| Get the panel as a string | `sb.get_text()` | [Reading state](#reading-state) |
|
|
207
|
-
| See raw buffer with indexes | `sb.display_index()` | [Reading state](#reading-state) |
|
|
208
|
-
| 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
|
+
```
|
|
209
290
|
|
|
210
291
|
---
|
|
211
292
|
|
|
212
|
-
##
|
|
293
|
+
## Usage
|
|
213
294
|
|
|
214
|
-
|
|
215
|
-
- **View**: what is drawn. It is the header, then the visible lines (after the window and any filter), then the footer.
|
|
216
|
-
- **History log**: a timestamped file of every `put()`. Append-only record of the run.
|
|
217
|
-
- **Wait file**: a saved copy of the *current* buffer, so the panel can be restored.
|
|
295
|
+
### Display window
|
|
218
296
|
|
|
219
|
-
|
|
297
|
+
```python
|
|
298
|
+
sb = ScreenBuffer(wait=False, max_display=10)
|
|
299
|
+
```
|
|
220
300
|
|
|
221
|
-
|
|
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).
|
|
222
302
|
|
|
223
|
-
|
|
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.
|
|
224
318
|
|
|
225
319
|
### Alignment
|
|
226
320
|
|
|
@@ -247,27 +341,14 @@ Alignment follows its line: it is saved and restored with the buffer, and re-key
|
|
|
247
341
|
|
|
248
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.
|
|
249
343
|
|
|
250
|
-
###
|
|
251
|
-
|
|
252
|
-
```python
|
|
253
|
-
sb = ScreenBuffer(wait=False, max_display=10)
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
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).
|
|
257
|
-
|
|
258
|
-
### Header and footer
|
|
259
|
-
|
|
260
|
-
```python
|
|
261
|
-
sb = ScreenBuffer(wait=False, header="Job: nightly_etl", footer="status: running",
|
|
262
|
-
header_alignment="center")
|
|
344
|
+
### Redraw modes
|
|
263
345
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
sb.
|
|
268
|
-
```
|
|
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. |
|
|
269
350
|
|
|
270
|
-
|
|
351
|
+
`sb.display()` always draws, even if nothing changed. `sb.flush()` clears the Jupyter output immediately.
|
|
271
352
|
|
|
272
353
|
### Smooth updates
|
|
273
354
|
|
|
@@ -290,15 +371,6 @@ Other things that keep it smooth, all automatic:
|
|
|
290
371
|
|
|
291
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.
|
|
292
373
|
|
|
293
|
-
### Redraw modes
|
|
294
|
-
|
|
295
|
-
| Mode | Behaviour |
|
|
296
|
-
|---|---|
|
|
297
|
-
| `wait=False` | Live. Every change redraws (skipped if nothing on screen changed). The panel draws once on creation. |
|
|
298
|
-
| `wait=True` (default) | Quiet. Changes are stored but nothing is drawn until you call `sb.display()`. Good for loading lots of lines first. |
|
|
299
|
-
|
|
300
|
-
`sb.display()` always draws, even if nothing changed. `sb.flush()` clears the Jupyter output immediately.
|
|
301
|
-
|
|
302
374
|
### Filtering
|
|
303
375
|
|
|
304
376
|
Hide or isolate lines **on screen only**. They stay in the buffer and the log. Indexes refer to the full buffer.
|
|
@@ -335,6 +407,33 @@ sb.clear_all() # empty the buffer AND remove hea
|
|
|
335
407
|
|
|
336
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.
|
|
337
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
|
+
|
|
338
437
|
### Log files
|
|
339
438
|
|
|
340
439
|
```python
|
|
@@ -394,30 +493,44 @@ Log file: /home/you/project/screen_buffer_logs/scrape_run.txt
|
|
|
394
493
|
|
|
395
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.
|
|
396
495
|
|
|
397
|
-
These cap files, **not memory**. The in-memory buffer keeps every line (roughly 100 MB per million short lines).
|
|
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.
|
|
398
497
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
```python
|
|
402
|
-
sb.update(max_display=20, width=90, wait=False)
|
|
403
|
-
sb.update(log_file="second_phase") # switch to a different log set
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
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`.
|
|
407
|
-
|
|
408
|
-
`update()` checks everything first: if any value is invalid it raises and changes **nothing**. Unknown names raise `ValueError`.
|
|
409
|
-
|
|
410
|
-
### Reading state
|
|
498
|
+
---
|
|
411
499
|
|
|
412
|
-
|
|
413
|
-
sb.get_text() # the current panel as one string
|
|
414
|
-
sb.display_index() # prints every buffer line with its index (debugging)
|
|
415
|
-
sb.buffer # the raw list of lines
|
|
416
|
-
```
|
|
500
|
+
## Feature index
|
|
417
501
|
|
|
418
|
-
|
|
502
|
+
Find what you need:
|
|
419
503
|
|
|
420
|
-
|
|
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) |
|
|
421
534
|
|
|
422
535
|
---
|
|
423
536
|
|
|
@@ -425,7 +538,7 @@ sb.buffer # the raw list of lines
|
|
|
425
538
|
|
|
426
539
|
| Method | Purpose |
|
|
427
540
|
|---|---|
|
|
428
|
-
| `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) |
|
|
429
542
|
| `update(**settings)` | Change any setting (validated first) |
|
|
430
543
|
| `display()` | Draw now |
|
|
431
544
|
| `flush()` | Clear the Jupyter output now |
|
|
@@ -441,6 +554,34 @@ sb.buffer # the raw list of lines
|
|
|
441
554
|
|
|
442
555
|
---
|
|
443
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
|
+
|
|
444
585
|
## Example: network scraper with an error banner
|
|
445
586
|
|
|
446
587
|
```python
|
|
@@ -448,18 +589,18 @@ import time, random
|
|
|
448
589
|
from screenbuffer import ScreenBuffer
|
|
449
590
|
|
|
450
591
|
sb = ScreenBuffer(max_display=8, width=70,
|
|
451
|
-
header="SCRAPER
|
|
592
|
+
header="SCRAPER ONLINE", header_alignment="center",
|
|
452
593
|
footer_alignment="center",
|
|
453
594
|
log_file="scraper", max_wait=500, start=True)
|
|
454
595
|
|
|
455
596
|
for t in range(120):
|
|
456
597
|
online = not (40 <= t < 70)
|
|
457
598
|
if online:
|
|
458
|
-
sb.update(header=f"SCRAPER
|
|
599
|
+
sb.update(header=f"SCRAPER ONLINE, {t}s", show_footer=False)
|
|
459
600
|
sb.put(f"[{t}s] page {t//2}: +{random.randint(3, 9)} items")
|
|
460
601
|
else:
|
|
461
|
-
sb.update(header=f"SCRAPER
|
|
462
|
-
footer="!! NETWORK ERROR
|
|
602
|
+
sb.update(header=f"SCRAPER OFFLINE, {t}s",
|
|
603
|
+
footer="!! NETWORK ERROR, retrying !!")
|
|
463
604
|
if t % 5 == 0:
|
|
464
605
|
sb.put(f"[{t}s] retrying...", state=1, alignment=4)
|
|
465
606
|
sb.display() # one redraw per step
|
|
@@ -472,22 +613,10 @@ The error banner appears at the bottom during the outage and disappears when the
|
|
|
472
613
|
|
|
473
614
|
---
|
|
474
615
|
|
|
475
|
-
## Performance (measured in a sandbox)
|
|
476
|
-
|
|
477
|
-
| Case | Before tuning | Now |
|
|
478
|
-
|---|---|---|
|
|
479
|
-
| `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 |
|
|
480
|
-
| Draw 8 lines out of 100k | 30.5 ms | 0.72 ms |
|
|
481
|
-
| Frame wait with a 20 ms disk delay | 202 ms | 0.1 ms |
|
|
482
|
-
| Old frame cleared → new text shown | 100–200 ms | ~0.5 ms |
|
|
483
|
-
| Redraws in a 300-step demo (one draw per step) | 241 | 81 |
|
|
484
|
-
|
|
485
|
-
These come from a Linux sandbox with a simulated Jupyter kernel. Real browser rendering was not measurable there; if you see flicker, see Troubleshooting.
|
|
486
|
-
|
|
487
616
|
## Known limits
|
|
488
617
|
|
|
489
618
|
- **Jupyter only.** Terminals and plain scripts are not supported (drawing is a no-op without IPython).
|
|
490
|
-
- **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.
|
|
491
620
|
- **`max_main` trimming** rewrites the history file once it goes over the cap.
|
|
492
621
|
- **Memory is not capped** (see Limits).
|
|
493
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.
|
|
@@ -500,6 +629,8 @@ These come from a Linux sandbox with a simulated Jupyter kernel. Real browser re
|
|
|
500
629
|
| Symptom | Likely cause / fix |
|
|
501
630
|
|---|---|
|
|
502
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 |
|
|
503
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 |
|
|
504
635
|
| Header and footer change on separate frames | Use `wait=True`, make both updates, then call `sb.display()` once |
|
|
505
636
|
| `center` / `right` look off | Set `width=` to your output area's real width |
|