batchgrid 0.2.0b1__tar.gz → 0.2.0b2__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.
- {batchgrid-0.2.0b1 → batchgrid-0.2.0b2}/PKG-INFO +8 -5
- {batchgrid-0.2.0b1 → batchgrid-0.2.0b2}/README.md +7 -4
- {batchgrid-0.2.0b1 → batchgrid-0.2.0b2}/pyproject.toml +1 -1
- {batchgrid-0.2.0b1 → batchgrid-0.2.0b2}/src/batchgrid/__init__.py +1 -1
- {batchgrid-0.2.0b1 → batchgrid-0.2.0b2}/src/batchgrid/_ui.py +70 -19
- {batchgrid-0.2.0b1 → batchgrid-0.2.0b2}/tests/test_ui.py +40 -0
- {batchgrid-0.2.0b1 → batchgrid-0.2.0b2}/.gitignore +0 -0
- {batchgrid-0.2.0b1 → batchgrid-0.2.0b2}/src/batchgrid/_api.py +0 -0
- {batchgrid-0.2.0b1 → batchgrid-0.2.0b2}/src/batchgrid/_cli.py +0 -0
- {batchgrid-0.2.0b1 → batchgrid-0.2.0b2}/src/batchgrid/errors.py +0 -0
- {batchgrid-0.2.0b1 → batchgrid-0.2.0b2}/src/batchgrid/py.typed +0 -0
- {batchgrid-0.2.0b1 → batchgrid-0.2.0b2}/tests/conftest.py +0 -0
- {batchgrid-0.2.0b1 → batchgrid-0.2.0b2}/tests/test_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: batchgrid
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.0b2
|
|
4
4
|
Summary: Run a prompt, or a whole pipeline, over every row of a DataFrame or spreadsheet - with cost estimates, retries and resumable runs.
|
|
5
5
|
Project-URL: Homepage, https://github.com/mertguvencli/batchgrid
|
|
6
6
|
Project-URL: Source, https://github.com/mertguvencli/batchgrid/tree/main/python
|
|
@@ -83,7 +83,8 @@ The file is the same one `batchgrid --save-plan` writes and `batchgrid --plan` r
|
|
|
83
83
|
|
|
84
84
|
## In the browser
|
|
85
85
|
|
|
86
|
-
`batchgrid.ui()` opens the batchgrid workspace
|
|
86
|
+
`batchgrid.ui()` opens the batchgrid workspace with your data in the sheet. In a Jupyter notebook it
|
|
87
|
+
shows in the cell's output; elsewhere, or with `inline=False`, it opens in your browser. You can
|
|
87
88
|
chat, check the plan and run it there, while the log prints in the notebook cell or terminal as it
|
|
88
89
|
happens:
|
|
89
90
|
|
|
@@ -105,7 +106,8 @@ batchgrid: 100%|██████████| 1000/1000 [00:42<00:00, failed=0
|
|
|
105
106
|
Finished in the browser.
|
|
106
107
|
```
|
|
107
108
|
|
|
108
|
-
To come back, press **Finish** in the
|
|
109
|
+
To come back, press **Finish** in the workspace, close its tab, or interrupt the cell. Once it stops,
|
|
110
|
+
a note takes the workspace's place in the cell. `ui()` returns the
|
|
109
111
|
sheet as the tab last showed it. For a DataFrame, that is a copy of yours with the new columns and
|
|
110
112
|
any cells you edited; untouched columns keep their dtypes. `ui()` with no data starts on an empty
|
|
111
113
|
sheet.
|
|
@@ -163,9 +165,10 @@ except KeyboardInterrupt:
|
|
|
163
165
|
result = batchgrid.resume(df)
|
|
164
166
|
```
|
|
165
167
|
|
|
166
|
-
### `batchgrid.ui(data=None, *, open_browser=True, port=None, log=True) -> DataFrame | None`
|
|
168
|
+
### `batchgrid.ui(data=None, *, inline=None, height=720, open_browser=True, port=None, log=True) -> DataFrame | None`
|
|
167
169
|
|
|
168
|
-
See [In the browser](#in-the-browser). `
|
|
170
|
+
See [In the browser](#in-the-browser). `inline` shows the workspace in the cell, `height` pixels tall;
|
|
171
|
+
by default it does in a Jupyter notebook and opens a browser tab anywhere else. `on_event` receives the same events as in `run()`, plus `ui`,
|
|
169
172
|
`sheet`, `browser_error` and `closed`.
|
|
170
173
|
|
|
171
174
|
### Errors
|
|
@@ -57,7 +57,8 @@ The file is the same one `batchgrid --save-plan` writes and `batchgrid --plan` r
|
|
|
57
57
|
|
|
58
58
|
## In the browser
|
|
59
59
|
|
|
60
|
-
`batchgrid.ui()` opens the batchgrid workspace
|
|
60
|
+
`batchgrid.ui()` opens the batchgrid workspace with your data in the sheet. In a Jupyter notebook it
|
|
61
|
+
shows in the cell's output; elsewhere, or with `inline=False`, it opens in your browser. You can
|
|
61
62
|
chat, check the plan and run it there, while the log prints in the notebook cell or terminal as it
|
|
62
63
|
happens:
|
|
63
64
|
|
|
@@ -79,7 +80,8 @@ batchgrid: 100%|██████████| 1000/1000 [00:42<00:00, failed=0
|
|
|
79
80
|
Finished in the browser.
|
|
80
81
|
```
|
|
81
82
|
|
|
82
|
-
To come back, press **Finish** in the
|
|
83
|
+
To come back, press **Finish** in the workspace, close its tab, or interrupt the cell. Once it stops,
|
|
84
|
+
a note takes the workspace's place in the cell. `ui()` returns the
|
|
83
85
|
sheet as the tab last showed it. For a DataFrame, that is a copy of yours with the new columns and
|
|
84
86
|
any cells you edited; untouched columns keep their dtypes. `ui()` with no data starts on an empty
|
|
85
87
|
sheet.
|
|
@@ -137,9 +139,10 @@ except KeyboardInterrupt:
|
|
|
137
139
|
result = batchgrid.resume(df)
|
|
138
140
|
```
|
|
139
141
|
|
|
140
|
-
### `batchgrid.ui(data=None, *, open_browser=True, port=None, log=True) -> DataFrame | None`
|
|
142
|
+
### `batchgrid.ui(data=None, *, inline=None, height=720, open_browser=True, port=None, log=True) -> DataFrame | None`
|
|
141
143
|
|
|
142
|
-
See [In the browser](#in-the-browser). `
|
|
144
|
+
See [In the browser](#in-the-browser). `inline` shows the workspace in the cell, `height` pixels tall;
|
|
145
|
+
by default it does in a Jupyter notebook and opens a browser tab anywhere else. `on_event` receives the same events as in `run()`, plus `ui`,
|
|
143
146
|
`sheet`, `browser_error` and `closed`.
|
|
144
147
|
|
|
145
148
|
### Errors
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "batchgrid"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.0b2"
|
|
8
8
|
description = "Run a prompt, or a whole pipeline, over every row of a DataFrame or spreadsheet - with cost estimates, retries and resumable runs."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""ui() - the web workspace in the browser, with its log here and its sheet coming back."""
|
|
1
|
+
"""ui() - the web workspace in the notebook or the browser, with its log here and its sheet coming back."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -18,6 +18,8 @@ if TYPE_CHECKING:
|
|
|
18
18
|
def ui(
|
|
19
19
|
data: Optional[Data] = None,
|
|
20
20
|
*,
|
|
21
|
+
inline: Optional[bool] = None,
|
|
22
|
+
height: int = 720,
|
|
21
23
|
open_browser: bool = True,
|
|
22
24
|
port: Optional[int] = None,
|
|
23
25
|
log: bool = True,
|
|
@@ -25,27 +27,31 @@ def ui(
|
|
|
25
27
|
cli: Optional[Sequence[str]] = None,
|
|
26
28
|
env: Optional[Mapping[str, str]] = None,
|
|
27
29
|
) -> Optional["pd.DataFrame"]:
|
|
28
|
-
"""Open the batchgrid workspace
|
|
30
|
+
"""Open the batchgrid workspace and wait until you are done there.
|
|
29
31
|
|
|
30
|
-
``data`` - a file path or a pandas DataFrame - opens in the sheet.
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
``data`` - a file path or a pandas DataFrame - opens in the sheet. In a
|
|
33
|
+
Jupyter notebook the workspace shows in the cell's output, ``height``
|
|
34
|
+
pixels tall; ``inline=False`` opens it in a browser tab instead, which is
|
|
35
|
+
what happens outside a notebook. Chat, plan and run there; what happens is
|
|
36
|
+
printed here as it happens, with a progress bar while rows run.
|
|
33
37
|
|
|
34
|
-
Press Finish in the
|
|
35
|
-
back. The sheet as the tab last showed it is returned as a DataFrame: for
|
|
38
|
+
Press Finish in the workspace, close its tab, or interrupt the cell
|
|
39
|
+
(Ctrl+C) to come back. The sheet as the tab last showed it is returned as a DataFrame: for
|
|
36
40
|
a DataFrame you passed in, a copy with the new columns added and your
|
|
37
41
|
edits in it. None when nothing was opened, or pandas is not installed.
|
|
38
42
|
|
|
39
43
|
It runs on this machine, so it needs a browser that can reach it - not a
|
|
40
44
|
notebook running on a remote server.
|
|
41
45
|
"""
|
|
42
|
-
|
|
46
|
+
embed = _in_notebook() if inline is None else inline
|
|
47
|
+
frame_view = _Frame(height) if embed else None
|
|
48
|
+
printer = _Printer(link=not embed) if log else None
|
|
43
49
|
with _input_file(data) as (path, frame):
|
|
44
50
|
out = _TempDir()
|
|
45
51
|
try:
|
|
46
52
|
sheet = os.path.join(out.get(), "sheet.csv")
|
|
47
53
|
args: List[str] = ["ui", *([path] if path else []), "--output", sheet, "--json"]
|
|
48
|
-
if not open_browser:
|
|
54
|
+
if embed or not open_browser:
|
|
49
55
|
args.append("--no-open")
|
|
50
56
|
if port is not None:
|
|
51
57
|
args += ["--port", str(port)]
|
|
@@ -59,6 +65,8 @@ def ui(
|
|
|
59
65
|
failure = event
|
|
60
66
|
elif event.get("type") == "ui":
|
|
61
67
|
started = True
|
|
68
|
+
if frame_view is not None:
|
|
69
|
+
frame_view.show(str(event.get("url")))
|
|
62
70
|
elif event.get("type") == "exit":
|
|
63
71
|
ended = event
|
|
64
72
|
if printer is not None:
|
|
@@ -71,6 +79,8 @@ def ui(
|
|
|
71
79
|
finally:
|
|
72
80
|
if printer is not None:
|
|
73
81
|
printer.close()
|
|
82
|
+
if frame_view is not None:
|
|
83
|
+
frame_view.close()
|
|
74
84
|
if failure is not None:
|
|
75
85
|
raise BatchgridError(str(failure.get("code")), str(failure.get("message")))
|
|
76
86
|
if not started and ended is not None and ended.get("code"):
|
|
@@ -110,11 +120,56 @@ def _read_sheet(frame: Any, sheet: Path, sent: Optional[Path]) -> Optional["pd.D
|
|
|
110
120
|
return merged
|
|
111
121
|
|
|
112
122
|
|
|
123
|
+
def _in_notebook() -> bool:
|
|
124
|
+
"""Whether this runs in a Jupyter kernel, whose front end can show the workspace in a cell"""
|
|
125
|
+
try:
|
|
126
|
+
from IPython import get_ipython
|
|
127
|
+
|
|
128
|
+
shell = get_ipython()
|
|
129
|
+
return shell is not None and "IPKernelApp" in shell.config
|
|
130
|
+
except Exception: # noqa: BLE001 - no IPython means a plain console
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class _Frame:
|
|
135
|
+
"""The workspace in the cell's output; once it stops, a note takes its place"""
|
|
136
|
+
|
|
137
|
+
def __init__(self, height: int) -> None:
|
|
138
|
+
self.height = height
|
|
139
|
+
self.handle: Any = None
|
|
140
|
+
|
|
141
|
+
def show(self, url: str) -> None:
|
|
142
|
+
from IPython.display import IFrame, display
|
|
143
|
+
|
|
144
|
+
self.handle = display(
|
|
145
|
+
IFrame(
|
|
146
|
+
url,
|
|
147
|
+
width="100%",
|
|
148
|
+
height=self.height,
|
|
149
|
+
extras=[
|
|
150
|
+
'style="border: 1px solid rgba(128, 128, 128, 0.35); border-radius: 6px"',
|
|
151
|
+
'allow="clipboard-read; clipboard-write"',
|
|
152
|
+
],
|
|
153
|
+
),
|
|
154
|
+
display_id=True,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def close(self) -> None:
|
|
158
|
+
if self.handle is None:
|
|
159
|
+
return
|
|
160
|
+
from IPython.display import HTML
|
|
161
|
+
|
|
162
|
+
# The server behind the frame is gone, so a saved notebook keeps no dead workspace
|
|
163
|
+
self.handle.update(HTML("<em>batchgrid closed. Run the cell again to reopen it.</em>"))
|
|
164
|
+
self.handle = None
|
|
165
|
+
|
|
166
|
+
|
|
113
167
|
class _Printer:
|
|
114
168
|
"""The terminal's log, in a notebook cell or a Python console"""
|
|
115
169
|
|
|
116
|
-
def __init__(self) -> None:
|
|
170
|
+
def __init__(self, link: bool = True) -> None:
|
|
117
171
|
self.bar: Any = None
|
|
172
|
+
self.link = link
|
|
118
173
|
try:
|
|
119
174
|
from tqdm.auto import tqdm
|
|
120
175
|
|
|
@@ -130,7 +185,7 @@ class _Printer:
|
|
|
130
185
|
# Anything else ends the run's bar, so its lines are not drawn over it
|
|
131
186
|
if kind in ("result", "closed", "user", "plan"):
|
|
132
187
|
self.close()
|
|
133
|
-
if kind == "ui":
|
|
188
|
+
if kind == "ui" and self.link:
|
|
134
189
|
self._link(str(event.get("url")))
|
|
135
190
|
elif kind == "user":
|
|
136
191
|
self._line(f"\n❯ {event.get('text')}")
|
|
@@ -155,7 +210,7 @@ class _Printer:
|
|
|
155
210
|
elif kind == "error":
|
|
156
211
|
self._line(f"✖ {event.get('message')}")
|
|
157
212
|
elif kind == "closed":
|
|
158
|
-
why = {"tab_closed": "The tab was closed", "finished": "Finished in the browser"}.get(
|
|
213
|
+
why = {"tab_closed": "The tab was closed", "finished": "Finished in the browser" if self.link else "Finished"}.get(
|
|
159
214
|
str(event.get("reason")), "Stopped"
|
|
160
215
|
)
|
|
161
216
|
self._line(f"\n{why}.")
|
|
@@ -173,15 +228,11 @@ class _Printer:
|
|
|
173
228
|
self.bar.refresh()
|
|
174
229
|
|
|
175
230
|
def _link(self, url: str) -> None:
|
|
176
|
-
|
|
177
|
-
from IPython import get_ipython
|
|
231
|
+
if _in_notebook():
|
|
178
232
|
from IPython.display import HTML, display
|
|
179
233
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return
|
|
183
|
-
except Exception: # noqa: BLE001 - no IPython means a plain console
|
|
184
|
-
pass
|
|
234
|
+
display(HTML(f'batchgrid is open at <a href="{url}" target="_blank">{url.split("?")[0]}</a>'))
|
|
235
|
+
return
|
|
185
236
|
self._line(f"batchgrid is open at {url}")
|
|
186
237
|
|
|
187
238
|
def _line(self, text: str) -> None:
|
|
@@ -14,6 +14,7 @@ import stat
|
|
|
14
14
|
import sys
|
|
15
15
|
import threading
|
|
16
16
|
import time
|
|
17
|
+
import types
|
|
17
18
|
import urllib.request
|
|
18
19
|
from pathlib import Path
|
|
19
20
|
from typing import Any, Dict, List
|
|
@@ -129,6 +130,45 @@ def test_interrupting_the_cell_returns_instead_of_raising() -> None:
|
|
|
129
130
|
assert result.loc[0, "sentiment"] == "positive"
|
|
130
131
|
|
|
131
132
|
|
|
133
|
+
def test_in_a_notebook_the_workspace_shows_in_the_cell(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
|
|
134
|
+
shown: List[Any] = []
|
|
135
|
+
|
|
136
|
+
class Handle:
|
|
137
|
+
def update(self, obj: Any) -> None:
|
|
138
|
+
shown.append(obj)
|
|
139
|
+
|
|
140
|
+
def display(obj: Any, display_id: bool = False) -> Handle:
|
|
141
|
+
shown.append(obj)
|
|
142
|
+
return Handle()
|
|
143
|
+
|
|
144
|
+
# A stand-in for the IPython a Jupyter kernel runs
|
|
145
|
+
ipython = types.ModuleType("IPython")
|
|
146
|
+
ipython.get_ipython = lambda: types.SimpleNamespace(config={"IPKernelApp": {}}) # type: ignore[attr-defined]
|
|
147
|
+
ipython_display = types.ModuleType("IPython.display")
|
|
148
|
+
ipython_display.HTML = lambda html: html # type: ignore[attr-defined]
|
|
149
|
+
ipython_display.IFrame = lambda src, **options: {"src": src, **options} # type: ignore[attr-defined]
|
|
150
|
+
ipython_display.display = display # type: ignore[attr-defined]
|
|
151
|
+
monkeypatch.setitem(sys.modules, "IPython", ipython)
|
|
152
|
+
monkeypatch.setitem(sys.modules, "IPython.display", ipython_display)
|
|
153
|
+
|
|
154
|
+
urls: List[str] = []
|
|
155
|
+
|
|
156
|
+
def act(tab: FakeTab) -> None:
|
|
157
|
+
urls.append(tab.url)
|
|
158
|
+
tab.call("finish", {})
|
|
159
|
+
|
|
160
|
+
# No open_browser=False: showing it in the cell never opens a tab
|
|
161
|
+
batchgrid.ui(on_event=open_tab(act))
|
|
162
|
+
|
|
163
|
+
assert len(shown) == 2
|
|
164
|
+
assert shown[0]["src"] == urls[0] and shown[0]["height"] == 720
|
|
165
|
+
# The server behind it stopped, so a note takes the workspace's place
|
|
166
|
+
assert "batchgrid closed" in shown[1]
|
|
167
|
+
printed = capsys.readouterr().out
|
|
168
|
+
assert "is open at" not in printed
|
|
169
|
+
assert "Finished." in printed
|
|
170
|
+
|
|
171
|
+
|
|
132
172
|
def test_nothing_synced_returns_none() -> None:
|
|
133
173
|
result = batchgrid.ui(open_browser=False, log=False, on_event=open_tab(lambda tab: tab.call("finish", {})))
|
|
134
174
|
assert result is None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|