Metadata-Version: 2.5
Name: batchgrid
Version: 0.2.0b3
Summary: Run a prompt, or a whole pipeline, over every row of a DataFrame or spreadsheet - with cost estimates, retries and resumable runs.
Project-URL: Homepage, https://github.com/mertguvencli/batchgrid
Project-URL: Source, https://github.com/mertguvencli/batchgrid/tree/main/python
Project-URL: Issues, https://github.com/mertguvencli/batchgrid/issues
Author: Mert Guvencli
License-Expression: MIT
Keywords: anthropic,batch,csv,dataframe,gemini,llm,openai,pandas
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: pandas>=1.5; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Provides-Extra: pandas
Requires-Dist: pandas>=1.5; extra == 'pandas'
Provides-Extra: progress
Requires-Dist: tqdm>=4.60; extra == 'progress'
Description-Content-Type: text/markdown

# batchgrid for Python

Run a prompt, or a whole pipeline, over every row of a DataFrame or spreadsheet. You get a cost
estimate before anything runs, parallel calls with retries and rate-limit backoff, and runs that
resume after an interruption.

```python
import pandas as pd
import batchgrid

df = pd.read_csv("reviews.csv")
result = batchgrid.run(df, "classify the sentiment and extract keywords", max_cost=5)
result.data  # df with the new columns
```

> **Beta.** This package drives the [batchgrid CLI](https://www.npmjs.com/package/batchgrid), so it
> needs **Node.js 22 or newer**. It uses a `batchgrid` on your PATH, or fetches the CLI with `npx`
> on first use.

## Install

```bash
pip install "batchgrid[pandas,progress]"
```

`pandas` is needed for DataFrame input and `progress` adds a tqdm progress bar. File paths work
without either.

Set the key of the provider you use, either as an environment variable (`OPENAI_API_KEY`,
`ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`, `OPENROUTER_API_KEY`, `XAI_API_KEY`) or once with
`npx batchgrid config --set-key openai:sk-…`.

## Plan, check, run

Planning asks a model, so the same request can produce a slightly different plan each time. Look at
the plan and its cost first, then run it:

```python
plan = batchgrid.plan(df, "classify the sentiment of each review")
print(plan)            # the steps, the columns they write, the estimated cost
plan.cost_usd          # 0.08

result = batchgrid.run(df, plan=plan)
```

A pipeline that must behave the same on every run should save the plan once and run that file.
Running a saved plan does not ask the planner again:

```python
plan.save("sentiment.json")

# later, in the pipeline
result = batchgrid.run("next_week.csv", plan="sentiment.json", max_cost=10)
```

The file is the same one `batchgrid --save-plan` writes and `batchgrid --plan` reads.

## In the browser

`batchgrid.ui()` opens the batchgrid workspace with your data in the sheet. In a Jupyter notebook it
shows in the cell's output; elsewhere, or with `inline=False`, it opens in your browser. You can
chat, check the plan and run it there, while the log prints in the notebook cell or terminal as it
happens:

```python
df2 = batchgrid.ui(df)
```

```
batchgrid is open at http://127.0.0.1:4817

❯ classify the sentiment of each review
  ✔ Reading data.csv - 1,000 rows · 1 column: review
  ✔ Asking gpt-5.6 for a plan - 1 step · 2.1k tokens
  ✔ Estimating the cost - About ~$0.08 for 1,000 rows
batchgrid: 100%|██████████| 1000/1000 [00:42<00:00, failed=0]
  ✔ 1000/1000 rows, 0 failed
  ↳ Sheet synced: 1,000 rows × 2 columns

Finished in the browser.
```

To come back, press **Finish** in the workspace, close its tab, or interrupt the cell. Once it stops,
a note takes the workspace's place in the cell. `ui()` returns the
sheet as the tab last showed it. For a DataFrame, that is a copy of yours with the new columns and
any cells you edited; untouched columns keep their dtypes. `ui()` with no data starts on an empty
sheet.

The workspace runs on the machine running Python and uses the keys from `batchgrid config` or the
environment there. Your browser has to be able to reach that machine. On Google Colab it reaches it
through Colab's own proxy, so `ui()` works there too; a notebook on another remote server is out of
reach.

From a terminal, the `batchgrid` command this package installs does the same:

```bash
batchgrid ui data.csv     # or: batchgrid --ui data.csv
```

## Reference

### `batchgrid.run(data, prompt=None, *, plan=None, ...) -> Result`

| Argument | Meaning |
|---|---|
| `data` | A CSV, TSV or Excel path, a pandas DataFrame, or `None` when the request brings its own rows |
| `prompt` | What to do, in plain language. Pass either this or `plan` |
| `plan` | A `Plan`, its dict, or a saved JSON file |
| `model` | `"gpt-5.6"` or `"provider:model"`, e.g. `"anthropic:claude-sonnet-5"`. The default is the saved choice |
| `rows` | Run only the first N rows, a cheap way to try a plan |
| `concurrency` | The most requests running at once |
| `output` | Where to write the result. The extension picks the format |
| `max_cost` | In US dollars. The plan is priced first and nothing runs above it, or when the model has no price list |
| `progress` | Show a progress bar when tqdm is installed. Defaults to on |
| `on_event` | Called with each event the CLI reports: plan, progress, result… |

`Result` has `status` (`done` or `stopped`), `total`, `success`, `failed`, `input_tokens`,
`output_tokens`, `duration_ms`, `output_path`, `error_log_path`, and `data`. For DataFrame input,
`data` is a copy of your DataFrame with the new columns added, with its index and dtypes kept. If a
plan filters rows or drops columns, `data` holds the output as it was written.

Rows that still fail after their retries do not raise an error. Check `result.failed`, and look in
`result.error_log_path` for the reasons.

### `batchgrid.plan(data, prompt, *, model=None, rows=None) -> Plan`

Plans the request and prices it without running anything. `Plan` has `title`, `summary`,
`cost_usd`, `cost_formatted`, `rows`, `missing_secrets`, the raw `steps`, and `save(path)` /
`Plan.load(path)`.

### `batchgrid.resume(data) -> Result`

Ctrl+C (or a Jupyter interrupt) stops a run and keeps the rows that finished. Call `resume` with the
same file or DataFrame to finish it. Only the unfinished rows are sent to the model.

```python
try:
    result = batchgrid.run(df, plan="sentiment.json")
except KeyboardInterrupt:
    result = batchgrid.resume(df)
```

### `batchgrid.ui(data=None, *, inline=None, height=720, open_browser=True, port=None, log=True) -> DataFrame | None`

See [In the browser](#in-the-browser). `inline` shows the workspace in the cell, `height` pixels tall;
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`,
`sheet`, `browser_error` and `closed`.

### Errors

Everything raises `batchgrid.BatchgridError`, and its `code` says what went wrong:

- `no_api_key`: no provider key is set.
- `invalid_plan`: the plan does not fit the data.
- `no_plan`: the planner asked a question instead of returning a plan. This raises `NoPlanError`,
  and its `reply` holds the question.
- `missing_secrets`: the plan needs keys that are not saved. This raises `MissingSecretsError`,
  and its `names` lists them.
- `cost_limit`: the run would go over `max_cost`. This raises `CostLimitError`, and its `plan`
  holds the priced plan.
- `run_failed`: the run itself failed.

### Choosing the CLI

The `batchgrid` command pip installs hands everything to the Node CLI, so `batchgrid config`,
`batchgrid ui` and the rest work the same whether batchgrid came from pip or npm. The package looks
for the CLI in this order:

1. the `cli=[...]` argument
2. the `BATCHGRID_CLI` environment variable, e.g. `node /path/to/cli/dist/index.js`
3. the npm `batchgrid` on the PATH (never this package's own command)
4. `npx "batchgrid@>=0.3.1 <1"`

## Development

```bash
pnpm --filter batchgrid build   # the tests drive the real CLI
cd python
uv venv && uv pip install -e ".[dev,progress]"
.venv/bin/python -m pytest
```

The tests replace only the model: a local server stands in for the OpenAI API.
