batchgrid 0.2.0b2__tar.gz → 0.2.0b3__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.0b2 → batchgrid-0.2.0b3}/PKG-INFO +6 -5
- {batchgrid-0.2.0b2 → batchgrid-0.2.0b3}/README.md +5 -4
- {batchgrid-0.2.0b2 → batchgrid-0.2.0b3}/pyproject.toml +1 -1
- {batchgrid-0.2.0b2 → batchgrid-0.2.0b3}/src/batchgrid/__init__.py +1 -1
- {batchgrid-0.2.0b2 → batchgrid-0.2.0b3}/src/batchgrid/_cli.py +2 -2
- {batchgrid-0.2.0b2 → batchgrid-0.2.0b3}/src/batchgrid/_ui.py +41 -3
- {batchgrid-0.2.0b2 → batchgrid-0.2.0b3}/tests/test_ui.py +38 -0
- {batchgrid-0.2.0b2 → batchgrid-0.2.0b3}/.gitignore +0 -0
- {batchgrid-0.2.0b2 → batchgrid-0.2.0b3}/src/batchgrid/_api.py +0 -0
- {batchgrid-0.2.0b2 → batchgrid-0.2.0b3}/src/batchgrid/errors.py +0 -0
- {batchgrid-0.2.0b2 → batchgrid-0.2.0b3}/src/batchgrid/py.typed +0 -0
- {batchgrid-0.2.0b2 → batchgrid-0.2.0b3}/tests/conftest.py +0 -0
- {batchgrid-0.2.0b2 → batchgrid-0.2.0b3}/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.0b3
|
|
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
|
|
@@ -112,9 +112,10 @@ sheet as the tab last showed it. For a DataFrame, that is a copy of yours with t
|
|
|
112
112
|
any cells you edited; untouched columns keep their dtypes. `ui()` with no data starts on an empty
|
|
113
113
|
sheet.
|
|
114
114
|
|
|
115
|
-
The workspace runs on
|
|
116
|
-
browser has to be able to reach
|
|
117
|
-
a remote server
|
|
115
|
+
The workspace runs on the machine running Python and uses the keys from `batchgrid config` or the
|
|
116
|
+
environment there. Your browser has to be able to reach that machine. On Google Colab it reaches it
|
|
117
|
+
through Colab's own proxy, so `ui()` works there too; a notebook on another remote server is out of
|
|
118
|
+
reach.
|
|
118
119
|
|
|
119
120
|
From a terminal, the `batchgrid` command this package installs does the same:
|
|
120
121
|
|
|
@@ -194,7 +195,7 @@ for the CLI in this order:
|
|
|
194
195
|
1. the `cli=[...]` argument
|
|
195
196
|
2. the `BATCHGRID_CLI` environment variable, e.g. `node /path/to/cli/dist/index.js`
|
|
196
197
|
3. the npm `batchgrid` on the PATH (never this package's own command)
|
|
197
|
-
4. `npx "batchgrid@>=0.3.
|
|
198
|
+
4. `npx "batchgrid@>=0.3.1 <1"`
|
|
198
199
|
|
|
199
200
|
## Development
|
|
200
201
|
|
|
@@ -86,9 +86,10 @@ sheet as the tab last showed it. For a DataFrame, that is a copy of yours with t
|
|
|
86
86
|
any cells you edited; untouched columns keep their dtypes. `ui()` with no data starts on an empty
|
|
87
87
|
sheet.
|
|
88
88
|
|
|
89
|
-
The workspace runs on
|
|
90
|
-
browser has to be able to reach
|
|
91
|
-
a remote server
|
|
89
|
+
The workspace runs on the machine running Python and uses the keys from `batchgrid config` or the
|
|
90
|
+
environment there. Your browser has to be able to reach that machine. On Google Colab it reaches it
|
|
91
|
+
through Colab's own proxy, so `ui()` works there too; a notebook on another remote server is out of
|
|
92
|
+
reach.
|
|
92
93
|
|
|
93
94
|
From a terminal, the `batchgrid` command this package installs does the same:
|
|
94
95
|
|
|
@@ -168,7 +169,7 @@ for the CLI in this order:
|
|
|
168
169
|
1. the `cli=[...]` argument
|
|
169
170
|
2. the `BATCHGRID_CLI` environment variable, e.g. `node /path/to/cli/dist/index.js`
|
|
170
171
|
3. the npm `batchgrid` on the PATH (never this package's own command)
|
|
171
|
-
4. `npx "batchgrid@>=0.3.
|
|
172
|
+
4. `npx "batchgrid@>=0.3.1 <1"`
|
|
172
173
|
|
|
173
174
|
## Development
|
|
174
175
|
|
|
@@ -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.0b3"
|
|
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"
|
|
@@ -19,8 +19,8 @@ import threading
|
|
|
19
19
|
from collections import deque
|
|
20
20
|
from typing import Callable, Deque, Dict, Iterator, List, Mapping, Optional, Sequence
|
|
21
21
|
|
|
22
|
-
#: The CLI releases this package speaks to: 0.3.0 was the first with ``ui``, and its events only gain fields
|
|
23
|
-
CLI_SPEC = "batchgrid@>=0.3.
|
|
22
|
+
#: The CLI releases this package speaks to: 0.3.0 was the first with ``ui``, 0.3.1 the first behind Colab's proxy, and its events only gain fields
|
|
23
|
+
CLI_SPEC = "batchgrid@>=0.3.1 <1"
|
|
24
24
|
|
|
25
25
|
Event = Dict[str, object]
|
|
26
26
|
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
+
import socket
|
|
6
7
|
import sys
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import TYPE_CHECKING, Any, Callable, List, Mapping, Optional, Sequence
|
|
10
|
+
from urllib.parse import urlsplit, urlunsplit
|
|
9
11
|
|
|
10
12
|
from ._api import Data, EventHandler, _input_file, _is_dataframe, _merge_output, _TempDir
|
|
11
13
|
from ._cli import Event, find_cli, stream_events
|
|
@@ -40,10 +42,12 @@ def ui(
|
|
|
40
42
|
a DataFrame you passed in, a copy with the new columns added and your
|
|
41
43
|
edits in it. None when nothing was opened, or pandas is not installed.
|
|
42
44
|
|
|
43
|
-
It runs
|
|
44
|
-
|
|
45
|
+
It runs where Python runs, so the browser has to reach that machine. On
|
|
46
|
+
Colab it does through Colab's own proxy; a notebook on another remote
|
|
47
|
+
server is out of reach.
|
|
45
48
|
"""
|
|
46
49
|
embed = _in_notebook() if inline is None else inline
|
|
50
|
+
colab = _in_colab()
|
|
47
51
|
frame_view = _Frame(height) if embed else None
|
|
48
52
|
printer = _Printer(link=not embed) if log else None
|
|
49
53
|
with _input_file(data) as (path, frame):
|
|
@@ -51,8 +55,15 @@ def ui(
|
|
|
51
55
|
try:
|
|
52
56
|
sheet = os.path.join(out.get(), "sheet.csv")
|
|
53
57
|
args: List[str] = ["ui", *([path] if path else []), "--output", sheet, "--json"]
|
|
54
|
-
|
|
58
|
+
# On Colab the browser that would open is on Google's machine, not yours
|
|
59
|
+
if embed or colab or not open_browser:
|
|
55
60
|
args.append("--no-open")
|
|
61
|
+
proxy: Optional[str] = None
|
|
62
|
+
if colab:
|
|
63
|
+
# The proxy's address names the port, so the port is settled before the server starts
|
|
64
|
+
port = port if port is not None else _free_port()
|
|
65
|
+
proxy = _colab_proxy(port)
|
|
66
|
+
args += ["--allow-host", str(urlsplit(proxy).hostname)]
|
|
56
67
|
if port is not None:
|
|
57
68
|
args += ["--port", str(port)]
|
|
58
69
|
|
|
@@ -65,6 +76,8 @@ def ui(
|
|
|
65
76
|
failure = event
|
|
66
77
|
elif event.get("type") == "ui":
|
|
67
78
|
started = True
|
|
79
|
+
if proxy is not None:
|
|
80
|
+
event = {**event, "url": _through(proxy, str(event.get("url")))}
|
|
68
81
|
if frame_view is not None:
|
|
69
82
|
frame_view.show(str(event.get("url")))
|
|
70
83
|
elif event.get("type") == "exit":
|
|
@@ -120,6 +133,31 @@ def _read_sheet(frame: Any, sheet: Path, sent: Optional[Path]) -> Optional["pd.D
|
|
|
120
133
|
return merged
|
|
121
134
|
|
|
122
135
|
|
|
136
|
+
def _in_colab() -> bool:
|
|
137
|
+
"""Whether this is a Colab kernel, on a machine the browser reaches only through Colab's proxy"""
|
|
138
|
+
return "google.colab" in sys.modules
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _colab_proxy(port: int) -> str:
|
|
142
|
+
"""The address Colab's proxy gives a port on the kernel's machine"""
|
|
143
|
+
from google.colab.output import eval_js # type: ignore[import-not-found]
|
|
144
|
+
|
|
145
|
+
return str(eval_js(f"google.colab.kernel.proxyPort({port}, {{cache: false}})"))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _through(proxy: str, url: str) -> str:
|
|
149
|
+
"""The server's link, with the proxy's address in place of 127.0.0.1"""
|
|
150
|
+
base, own = urlsplit(proxy), urlsplit(url)
|
|
151
|
+
query = "&".join(q for q in (base.query, own.query) if q)
|
|
152
|
+
return urlunsplit((base.scheme, base.netloc, own.path or "/", query, ""))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _free_port() -> int:
|
|
156
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
|
157
|
+
probe.bind(("127.0.0.1", 0))
|
|
158
|
+
return int(probe.getsockname()[1])
|
|
159
|
+
|
|
160
|
+
|
|
123
161
|
def _in_notebook() -> bool:
|
|
124
162
|
"""Whether this runs in a Jupyter kernel, whose front end can show the workspace in a cell"""
|
|
125
163
|
try:
|
|
@@ -169,6 +169,44 @@ def test_in_a_notebook_the_workspace_shows_in_the_cell(monkeypatch: pytest.Monke
|
|
|
169
169
|
assert "Finished." in printed
|
|
170
170
|
|
|
171
171
|
|
|
172
|
+
def test_on_colab_the_workspace_goes_through_its_proxy(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
173
|
+
asked: List[str] = []
|
|
174
|
+
|
|
175
|
+
def eval_js(script: str) -> str:
|
|
176
|
+
asked.append(script)
|
|
177
|
+
port = script.split("(")[1].split(",")[0]
|
|
178
|
+
return f"https://{port}-abc-colab.googleusercontent.com/?authuser=0"
|
|
179
|
+
|
|
180
|
+
# A stand-in for the module a Colab kernel has loaded
|
|
181
|
+
colab = types.ModuleType("google.colab")
|
|
182
|
+
colab_output = types.ModuleType("google.colab.output")
|
|
183
|
+
colab_output.eval_js = eval_js # type: ignore[attr-defined]
|
|
184
|
+
monkeypatch.setitem(sys.modules, "google.colab", colab)
|
|
185
|
+
monkeypatch.setitem(sys.modules, "google.colab.output", colab_output)
|
|
186
|
+
|
|
187
|
+
seen: Dict[str, Any] = {}
|
|
188
|
+
|
|
189
|
+
def on_event(event: Dict[str, Any]) -> None:
|
|
190
|
+
if event["type"] != "ui":
|
|
191
|
+
return
|
|
192
|
+
seen["url"] = event["url"]
|
|
193
|
+
port = asked[0].split("(")[1].split(",")[0]
|
|
194
|
+
token = event["url"].split("token=")[1]
|
|
195
|
+
# What Colab's proxy sends on: the request addressed to its own host name
|
|
196
|
+
page = urllib.request.Request(
|
|
197
|
+
f"http://127.0.0.1:{port}/?token={token}", headers={"Host": f"{port}-abc-colab.googleusercontent.com"}
|
|
198
|
+
)
|
|
199
|
+
with urllib.request.urlopen(page, timeout=10) as response:
|
|
200
|
+
seen["status"] = response.status
|
|
201
|
+
tab = FakeTab(f"http://127.0.0.1:{port}/?token={token}")
|
|
202
|
+
threading.Thread(target=tab.call, args=("finish", {}), daemon=True).start()
|
|
203
|
+
|
|
204
|
+
batchgrid.ui(inline=False, log=False, on_event=on_event)
|
|
205
|
+
|
|
206
|
+
assert seen["url"].startswith("https://") and "-abc-colab.googleusercontent.com/?authuser=0&token=" in seen["url"]
|
|
207
|
+
assert seen["status"] == 200
|
|
208
|
+
|
|
209
|
+
|
|
172
210
|
def test_nothing_synced_returns_none() -> None:
|
|
173
211
|
result = batchgrid.ui(open_browser=False, log=False, on_event=open_tab(lambda tab: tab.call("finish", {})))
|
|
174
212
|
assert result is None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|