batchor 0.0.1__tar.gz → 0.0.3__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.
- {batchor-0.0.1 → batchor-0.0.3}/.github/workflows/ci.yml +1 -1
- {batchor-0.0.1 → batchor-0.0.3}/PKG-INFO +15 -48
- {batchor-0.0.1 → batchor-0.0.3}/README.md +13 -47
- {batchor-0.0.1 → batchor-0.0.3}/docs/design_docs/ARCHITECTURE.md +40 -9
- {batchor-0.0.1 → batchor-0.0.3}/docs/design_docs/OPENAI_BATCHING.md +13 -0
- {batchor-0.0.1 → batchor-0.0.3}/docs/design_docs/STORAGE_AND_RUNS.md +27 -2
- {batchor-0.0.1 → batchor-0.0.3}/docs/design_docs/STORAGE_MIGRATIONS.md +2 -1
- {batchor-0.0.1 → batchor-0.0.3}/docs/getting-started/python-api.md +2 -0
- {batchor-0.0.1 → batchor-0.0.3}/docs/smoke-test.md +15 -2
- {batchor-0.0.1 → batchor-0.0.3}/pyproject.toml +3 -1
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/core/exceptions.py +8 -2
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/core/models.py +7 -0
- batchor-0.0.3/src/batchor/runtime/__init__.py +1 -0
- batchor-0.0.3/src/batchor/runtime/artifacts.py +177 -0
- batchor-0.0.3/src/batchor/runtime/context.py +164 -0
- batchor-0.0.3/src/batchor/runtime/ingestion.py +380 -0
- batchor-0.0.3/src/batchor/runtime/polling.py +551 -0
- batchor-0.0.3/src/batchor/runtime/results.py +119 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/runtime/retry.py +63 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/runtime/run_handle.py +113 -46
- batchor-0.0.3/src/batchor/runtime/runner.py +597 -0
- batchor-0.0.3/src/batchor/runtime/submission.py +557 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/runtime/validation.py +3 -1
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/sources/base.py +10 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/sources/composite.py +4 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/sources/files.py +16 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/storage/memory.py +58 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/storage/postgres_store.py +18 -4
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/storage/sqlite_lifecycle.py +98 -1
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/storage/sqlite_protocol.py +1 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/storage/sqlite_queries.py +3 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/storage/sqlite_results.py +1 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/storage/sqlite_schema.py +2 -1
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/storage/state_models.py +26 -0
- {batchor-0.0.1 → batchor-0.0.3}/tests/integration/test_batchor_runner.py +309 -105
- batchor-0.0.3/tests/unit/test_batchor_results.py +130 -0
- batchor-0.0.3/tests/unit/test_batchor_retry.py +143 -0
- batchor-0.0.3/tests/unit/test_batchor_runtime_ingestion.py +935 -0
- batchor-0.0.3/tests/unit/test_batchor_runtime_polling.py +416 -0
- batchor-0.0.3/tests/unit/test_batchor_runtime_submission.py +333 -0
- {batchor-0.0.1 → batchor-0.0.3}/tests/unit/test_batchor_sources.py +282 -0
- batchor-0.0.3/tests/unit/test_batchor_storage.py +55 -0
- batchor-0.0.3/tests/unit/test_batchor_storage_contracts.py +438 -0
- {batchor-0.0.1 → batchor-0.0.3}/tests/unit/test_batchor_tokens.py +86 -0
- batchor-0.0.3/tests/unit/test_batchor_validation.py +379 -0
- {batchor-0.0.1 → batchor-0.0.3}/uv.lock +1 -1
- batchor-0.0.1/src/batchor/runtime/__init__.py +0 -1
- batchor-0.0.1/src/batchor/runtime/runner.py +0 -1015
- batchor-0.0.1/src/batchor/runtime/runner_execution.py +0 -753
- batchor-0.0.1/tests/unit/test_batchor_retry.py +0 -29
- batchor-0.0.1/tests/unit/test_batchor_storage.py +0 -22
- batchor-0.0.1/tests/unit/test_batchor_storage_contracts.py +0 -201
- batchor-0.0.1/tests/unit/test_batchor_validation.py +0 -174
- {batchor-0.0.1 → batchor-0.0.3}/.agents/plugins/marketplace.json +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/.agents/skills/batchor-dev/SKILL.md +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/.agents/skills/batchor-dev/agents/openai.yaml +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/.github/workflows/docs.yml +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/.github/workflows/publish.yml +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/.gitignore +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/.pre-commit-config.yaml +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/AGENTS.md +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/CONTRIBUTING.md +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/LICENSE +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/SUPPORT.md +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/VERSIONING.md +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/docs/assets/stylesheets/extra.css +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/docs/design_docs/BOUNDARY_AND_PHILOSOPHY.md +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/docs/design_docs/ROADMAP.md +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/docs/doc-map.md +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/docs/getting-started/cli.md +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/docs/getting-started/installation.md +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/docs/getting-started/use-cases.md +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/docs/index.md +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/docs/policies/contributing.md +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/docs/policies/support.md +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/docs/policies/versioning.md +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/docs/reference/api.md +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/mkdocs.yml +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/plugins/batchor-agent-tools/.codex-plugin/plugin.json +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/plugins/batchor-agent-tools/.mcp.json +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/plugins/batchor-agent-tools/scripts/batchor_repo_mcp.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/__init__.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/artifacts/__init__.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/artifacts/base.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/artifacts/local.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/cli.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/core/__init__.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/core/enums.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/core/types.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/providers/__init__.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/providers/base.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/providers/openai.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/providers/registry.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/py.typed +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/runtime/tokens.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/sources/__init__.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/storage/__init__.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/storage/postgres.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/storage/registry.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/storage/sqlite.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/storage/sqlite_codec.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/storage/sqlite_store.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/src/batchor/storage/state.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/tests/integration/test_batchor_live_openai.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/tests/unit/test_agent_tooling.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/tests/unit/test_batchor_architecture.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/tests/unit/test_batchor_artifacts.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/tests/unit/test_batchor_cli.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/tests/unit/test_batchor_openai_provider.py +0 -0
- {batchor-0.0.1 → batchor-0.0.3}/tests/unit/test_batchor_sqlite_storage_flow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: batchor
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: Structured-first OpenAI Batch runner with typed Pydantic results.
|
|
5
5
|
Project-URL: Documentation, https://github.com/AnsonDev42/batchor#readme
|
|
6
6
|
Project-URL: Repository, https://github.com/AnsonDev42/batchor
|
|
@@ -88,6 +88,7 @@ Classifier: Operating System :: OS Independent
|
|
|
88
88
|
Classifier: Programming Language :: Python :: 3
|
|
89
89
|
Classifier: Programming Language :: Python :: 3.12
|
|
90
90
|
Classifier: Programming Language :: Python :: 3.13
|
|
91
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
91
92
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
92
93
|
Requires-Python: <4,>=3.12
|
|
93
94
|
Requires-Dist: openai>=2.7.1
|
|
@@ -102,12 +103,15 @@ Description-Content-Type: text/markdown
|
|
|
102
103
|
|
|
103
104
|
# batchor
|
|
104
105
|
|
|
106
|
+
`batchor` grew out of a recurring problem in academic research: running large datasets through LLMs in batch — reliably, reproducibly, and without reinventing the same glue code across every project. The patterns that kept emerging (durable state, typed results, safe resume after failure) were extracted into this library so they do not have to be rebuilt each time.
|
|
107
|
+
|
|
105
108
|
`batchor` is a durable OpenAI Batch runner for Python teams that want:
|
|
106
109
|
|
|
107
110
|
- typed Pydantic results
|
|
108
111
|
- resumable durable runs
|
|
109
112
|
- replayable request artifacts
|
|
110
113
|
- deterministic source checkpoints
|
|
114
|
+
- provider-side enqueue limit controls to stay within token budgets
|
|
111
115
|
- library-first run controls
|
|
112
116
|
- a small operator CLI for CSV and JSONL jobs
|
|
113
117
|
|
|
@@ -154,6 +158,7 @@ Important constraints:
|
|
|
154
158
|
- structured-output rehydration requires an importable module-level Pydantic model
|
|
155
159
|
- raw output artifacts are retained by default and must be exported before raw pruning
|
|
156
160
|
- pause/resume/cancel and incremental terminal-result APIs are library-first today
|
|
161
|
+
- OpenAI control-plane or batch-level 429 quota/billing exhaustion auto-pauses the run instead of burning attempts across the remaining backlog
|
|
157
162
|
|
|
158
163
|
## Mental model
|
|
159
164
|
|
|
@@ -173,6 +178,7 @@ Durability is split on purpose:
|
|
|
173
178
|
- the artifact store keeps replayable request JSONL and downloaded raw batch payloads
|
|
174
179
|
|
|
175
180
|
That split is what allows retries and fresh-process resume without keeping every request inline in the control-plane store.
|
|
181
|
+
On resume, existing active provider batches are polled before `batchor` materializes or submits new local work, so completed batches and retry backoff are reconciled first. Deterministic sources also use stored checkpoint completion metadata to avoid opening a fully materialized Parquet or composite source just to discover there are no rows left.
|
|
176
182
|
|
|
177
183
|
## Architecture
|
|
178
184
|
|
|
@@ -256,6 +262,7 @@ Supported Python versions:
|
|
|
256
262
|
|
|
257
263
|
- `3.12`
|
|
258
264
|
- `3.13`
|
|
265
|
+
- `3.14`
|
|
259
266
|
|
|
260
267
|
## Authentication
|
|
261
268
|
|
|
@@ -334,6 +341,8 @@ run.wait()
|
|
|
334
341
|
print(run.results()[0].output)
|
|
335
342
|
```
|
|
336
343
|
|
|
344
|
+
While waiting, `batchor` polls active batches before submitting more work. If a refresh consumes a completed batch, records terminal item state, or submits another provider batch, `Run.wait()` immediately continues draining instead of sleeping for the next poll interval.
|
|
345
|
+
|
|
337
346
|
Structured-output models are validated up front against the OpenAI strict-schema subset used by `batchor`.
|
|
338
347
|
|
|
339
348
|
- root schema must be an object
|
|
@@ -479,6 +488,8 @@ run.cancel()
|
|
|
479
488
|
|
|
480
489
|
Run control and incremental terminal-result APIs are Python-first in this release. The CLI does not yet expose `pause`, `resume`, `cancel`, or incremental terminal-result export commands.
|
|
481
490
|
|
|
491
|
+
OpenAI insufficient-quota failures during upload/create/polling, or as a batch-level terminal error, are treated as an automatic pause with `summary().control_reason == "openai_insufficient_quota"`. Row-level insufficient-quota records inside a completed batch output stay item-scoped: those rows become retryable without consuming attempts, emit `openai_insufficient_quota` failure telemetry, and use retry backoff before resubmission. Auto-pause does not replace an in-progress cancellation.
|
|
492
|
+
|
|
482
493
|
## CLI quickstart
|
|
483
494
|
|
|
484
495
|
The CLI is intentionally narrower than the Python API:
|
|
@@ -539,12 +550,13 @@ def observer(event: RunEvent) -> None:
|
|
|
539
550
|
runner = BatchRunner(observer=observer)
|
|
540
551
|
```
|
|
541
552
|
|
|
542
|
-
Current events include run creation/resume, item ingestion, batch submission/polling/completion, item completion/failure, and artifact export/prune.
|
|
553
|
+
Current events include run creation/resume, automatic quota pause, item ingestion, batch submission/polling/completion, item completion/failure, and artifact export/prune.
|
|
543
554
|
|
|
544
555
|
## Storage notes
|
|
545
556
|
|
|
546
557
|
- SQLite remains the default durable backend.
|
|
547
|
-
- `PostgresStorage` is available for shared control-plane state, but the CLI remains SQLite-only today.
|
|
558
|
+
- `PostgresStorage` is available for shared control-plane state, but the CLI remains SQLite-only today. Plain `postgresql://` and `postgres://` DSNs are normalized to the package's psycopg v3 driver URL.
|
|
559
|
+
- Item `attempt_count` means consumed provider attempts: successful submitted completions increment it, counted item-level failures increment it, and local or batch-level retry/reset paths that did not consume an item attempt leave it unchanged.
|
|
548
560
|
- Durable artifacts now go through an `ArtifactStore` seam. The built-in implementation is `LocalArtifactStore`, intended for local disk or a shared mounted volume.
|
|
549
561
|
- Fresh-process resume requeues any locally claimed but not yet submitted items before continuing, so a process crash after request-artifact persistence does not strand work in `queued_local`.
|
|
550
562
|
|
|
@@ -591,51 +603,6 @@ The default pytest configuration enforces an `85%` coverage floor.
|
|
|
591
603
|
|
|
592
604
|
GitHub Actions pull requests run:
|
|
593
605
|
|
|
594
|
-
- `test (3.12)` and `test (3.13)`: `uv run ty check src` plus `uv run pytest -q`
|
|
595
|
-
- `docs`: `uv run mkdocs build --strict`
|
|
596
|
-
- `build (3.13)`: `uv build`
|
|
597
|
-
- `postgres-contract`: `uv run pytest tests/unit/test_batchor_storage_contracts.py --no-cov -q` with an ephemeral PostgreSQL service
|
|
598
|
-
|
|
599
|
-
The live OpenAI smoke remains manual-only and is not part of required CI.
|
|
600
|
-
|
|
601
|
-
Manual live OpenAI smoke:
|
|
602
|
-
|
|
603
|
-
```bash
|
|
604
|
-
export OPENAI_API_KEY=sk-...
|
|
605
|
-
export BATCHOR_RUN_LIVE_TESTS=1
|
|
606
|
-
uv run pytest tests/integration/test_batchor_live_openai.py --no-cov -q
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
If `BATCHOR_LIVE_OPENAI_MODEL` is unset, the harness defaults to `gpt-5-nano`.
|
|
610
|
-
If `BATCHOR_LIVE_OPENAI_REASONING_EFFORT` is unset, no reasoning field is sent.
|
|
611
|
-
The live smoke also requires an OpenAI account with Batch API access and available billing quota.
|
|
612
|
-
|
|
613
|
-
## Documentation guide
|
|
614
|
-
|
|
615
|
-
- `docs/design_docs/ARCHITECTURE.md`: canonical runtime diagrams and module boundaries
|
|
616
|
-
- `docs/design_docs/STORAGE_AND_RUNS.md`: durable run, resume, and artifact lifecycle semantics
|
|
617
|
-
- `docs/getting-started/use-cases.md`: practical single-file, multi-file, and pipeline examples
|
|
618
|
-
- `docs/getting-started/python-api.md`: Python-first workflows
|
|
619
|
-
- `docs/getting-started/cli.md`: operator CLI workflows
|
|
620
|
-
- `docs/reference/api.md`: generated API surface
|
|
621
|
-
- `docs/design_docs/ARCHITECTURE.md`: package boundaries
|
|
622
|
-
- `docs/design_docs/OPENAI_BATCHING.md`: OpenAI-specific behavior
|
|
623
|
-
- `docs/design_docs/STORAGE_AND_RUNS.md`: durability, rehydration, and artifacts
|
|
624
|
-
- `docs/smoke-test.md`: minimum validation bar
|
|
625
|
-
|
|
626
|
-
## Scope
|
|
627
|
-
|
|
628
|
-
Today `batchor` is intentionally narrow:
|
|
629
|
-
|
|
630
|
-
- one built-in provider: OpenAI Batch
|
|
631
|
-
- one default durable backend: SQLite
|
|
632
|
-
- one opt-in durable backend: Postgres
|
|
633
|
-
- one ephemeral backend: in-memory storage
|
|
634
|
-
- structured output is Python API-first
|
|
635
|
-
- CLI job creation is file-backed and supports both text and structured-output jobs
|
|
636
|
-
|
|
637
|
-
Anything outside that scope should be treated as out of scope or `TBD`, not implied support.
|
|
638
|
-
|
|
639
606
|
## License
|
|
640
607
|
|
|
641
608
|
This project is licensed under PolyForm Noncommercial 1.0.0. See [LICENSE](LICENSE).
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# batchor
|
|
2
2
|
|
|
3
|
+
`batchor` grew out of a recurring problem in academic research: running large datasets through LLMs in batch — reliably, reproducibly, and without reinventing the same glue code across every project. The patterns that kept emerging (durable state, typed results, safe resume after failure) were extracted into this library so they do not have to be rebuilt each time.
|
|
4
|
+
|
|
3
5
|
`batchor` is a durable OpenAI Batch runner for Python teams that want:
|
|
4
6
|
|
|
5
7
|
- typed Pydantic results
|
|
6
8
|
- resumable durable runs
|
|
7
9
|
- replayable request artifacts
|
|
8
10
|
- deterministic source checkpoints
|
|
11
|
+
- provider-side enqueue limit controls to stay within token budgets
|
|
9
12
|
- library-first run controls
|
|
10
13
|
- a small operator CLI for CSV and JSONL jobs
|
|
11
14
|
|
|
@@ -52,6 +55,7 @@ Important constraints:
|
|
|
52
55
|
- structured-output rehydration requires an importable module-level Pydantic model
|
|
53
56
|
- raw output artifacts are retained by default and must be exported before raw pruning
|
|
54
57
|
- pause/resume/cancel and incremental terminal-result APIs are library-first today
|
|
58
|
+
- OpenAI control-plane or batch-level 429 quota/billing exhaustion auto-pauses the run instead of burning attempts across the remaining backlog
|
|
55
59
|
|
|
56
60
|
## Mental model
|
|
57
61
|
|
|
@@ -71,6 +75,7 @@ Durability is split on purpose:
|
|
|
71
75
|
- the artifact store keeps replayable request JSONL and downloaded raw batch payloads
|
|
72
76
|
|
|
73
77
|
That split is what allows retries and fresh-process resume without keeping every request inline in the control-plane store.
|
|
78
|
+
On resume, existing active provider batches are polled before `batchor` materializes or submits new local work, so completed batches and retry backoff are reconciled first. Deterministic sources also use stored checkpoint completion metadata to avoid opening a fully materialized Parquet or composite source just to discover there are no rows left.
|
|
74
79
|
|
|
75
80
|
## Architecture
|
|
76
81
|
|
|
@@ -154,6 +159,7 @@ Supported Python versions:
|
|
|
154
159
|
|
|
155
160
|
- `3.12`
|
|
156
161
|
- `3.13`
|
|
162
|
+
- `3.14`
|
|
157
163
|
|
|
158
164
|
## Authentication
|
|
159
165
|
|
|
@@ -232,6 +238,8 @@ run.wait()
|
|
|
232
238
|
print(run.results()[0].output)
|
|
233
239
|
```
|
|
234
240
|
|
|
241
|
+
While waiting, `batchor` polls active batches before submitting more work. If a refresh consumes a completed batch, records terminal item state, or submits another provider batch, `Run.wait()` immediately continues draining instead of sleeping for the next poll interval.
|
|
242
|
+
|
|
235
243
|
Structured-output models are validated up front against the OpenAI strict-schema subset used by `batchor`.
|
|
236
244
|
|
|
237
245
|
- root schema must be an object
|
|
@@ -377,6 +385,8 @@ run.cancel()
|
|
|
377
385
|
|
|
378
386
|
Run control and incremental terminal-result APIs are Python-first in this release. The CLI does not yet expose `pause`, `resume`, `cancel`, or incremental terminal-result export commands.
|
|
379
387
|
|
|
388
|
+
OpenAI insufficient-quota failures during upload/create/polling, or as a batch-level terminal error, are treated as an automatic pause with `summary().control_reason == "openai_insufficient_quota"`. Row-level insufficient-quota records inside a completed batch output stay item-scoped: those rows become retryable without consuming attempts, emit `openai_insufficient_quota` failure telemetry, and use retry backoff before resubmission. Auto-pause does not replace an in-progress cancellation.
|
|
389
|
+
|
|
380
390
|
## CLI quickstart
|
|
381
391
|
|
|
382
392
|
The CLI is intentionally narrower than the Python API:
|
|
@@ -437,12 +447,13 @@ def observer(event: RunEvent) -> None:
|
|
|
437
447
|
runner = BatchRunner(observer=observer)
|
|
438
448
|
```
|
|
439
449
|
|
|
440
|
-
Current events include run creation/resume, item ingestion, batch submission/polling/completion, item completion/failure, and artifact export/prune.
|
|
450
|
+
Current events include run creation/resume, automatic quota pause, item ingestion, batch submission/polling/completion, item completion/failure, and artifact export/prune.
|
|
441
451
|
|
|
442
452
|
## Storage notes
|
|
443
453
|
|
|
444
454
|
- SQLite remains the default durable backend.
|
|
445
|
-
- `PostgresStorage` is available for shared control-plane state, but the CLI remains SQLite-only today.
|
|
455
|
+
- `PostgresStorage` is available for shared control-plane state, but the CLI remains SQLite-only today. Plain `postgresql://` and `postgres://` DSNs are normalized to the package's psycopg v3 driver URL.
|
|
456
|
+
- Item `attempt_count` means consumed provider attempts: successful submitted completions increment it, counted item-level failures increment it, and local or batch-level retry/reset paths that did not consume an item attempt leave it unchanged.
|
|
446
457
|
- Durable artifacts now go through an `ArtifactStore` seam. The built-in implementation is `LocalArtifactStore`, intended for local disk or a shared mounted volume.
|
|
447
458
|
- Fresh-process resume requeues any locally claimed but not yet submitted items before continuing, so a process crash after request-artifact persistence does not strand work in `queued_local`.
|
|
448
459
|
|
|
@@ -489,51 +500,6 @@ The default pytest configuration enforces an `85%` coverage floor.
|
|
|
489
500
|
|
|
490
501
|
GitHub Actions pull requests run:
|
|
491
502
|
|
|
492
|
-
- `test (3.12)` and `test (3.13)`: `uv run ty check src` plus `uv run pytest -q`
|
|
493
|
-
- `docs`: `uv run mkdocs build --strict`
|
|
494
|
-
- `build (3.13)`: `uv build`
|
|
495
|
-
- `postgres-contract`: `uv run pytest tests/unit/test_batchor_storage_contracts.py --no-cov -q` with an ephemeral PostgreSQL service
|
|
496
|
-
|
|
497
|
-
The live OpenAI smoke remains manual-only and is not part of required CI.
|
|
498
|
-
|
|
499
|
-
Manual live OpenAI smoke:
|
|
500
|
-
|
|
501
|
-
```bash
|
|
502
|
-
export OPENAI_API_KEY=sk-...
|
|
503
|
-
export BATCHOR_RUN_LIVE_TESTS=1
|
|
504
|
-
uv run pytest tests/integration/test_batchor_live_openai.py --no-cov -q
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
If `BATCHOR_LIVE_OPENAI_MODEL` is unset, the harness defaults to `gpt-5-nano`.
|
|
508
|
-
If `BATCHOR_LIVE_OPENAI_REASONING_EFFORT` is unset, no reasoning field is sent.
|
|
509
|
-
The live smoke also requires an OpenAI account with Batch API access and available billing quota.
|
|
510
|
-
|
|
511
|
-
## Documentation guide
|
|
512
|
-
|
|
513
|
-
- `docs/design_docs/ARCHITECTURE.md`: canonical runtime diagrams and module boundaries
|
|
514
|
-
- `docs/design_docs/STORAGE_AND_RUNS.md`: durable run, resume, and artifact lifecycle semantics
|
|
515
|
-
- `docs/getting-started/use-cases.md`: practical single-file, multi-file, and pipeline examples
|
|
516
|
-
- `docs/getting-started/python-api.md`: Python-first workflows
|
|
517
|
-
- `docs/getting-started/cli.md`: operator CLI workflows
|
|
518
|
-
- `docs/reference/api.md`: generated API surface
|
|
519
|
-
- `docs/design_docs/ARCHITECTURE.md`: package boundaries
|
|
520
|
-
- `docs/design_docs/OPENAI_BATCHING.md`: OpenAI-specific behavior
|
|
521
|
-
- `docs/design_docs/STORAGE_AND_RUNS.md`: durability, rehydration, and artifacts
|
|
522
|
-
- `docs/smoke-test.md`: minimum validation bar
|
|
523
|
-
|
|
524
|
-
## Scope
|
|
525
|
-
|
|
526
|
-
Today `batchor` is intentionally narrow:
|
|
527
|
-
|
|
528
|
-
- one built-in provider: OpenAI Batch
|
|
529
|
-
- one default durable backend: SQLite
|
|
530
|
-
- one opt-in durable backend: Postgres
|
|
531
|
-
- one ephemeral backend: in-memory storage
|
|
532
|
-
- structured output is Python API-first
|
|
533
|
-
- CLI job creation is file-backed and supports both text and structured-output jobs
|
|
534
|
-
|
|
535
|
-
Anything outside that scope should be treated as out of scope or `TBD`, not implied support.
|
|
536
|
-
|
|
537
503
|
## License
|
|
538
504
|
|
|
539
505
|
This project is licensed under PolyForm Noncommercial 1.0.0. See [LICENSE](LICENSE).
|
|
@@ -133,13 +133,16 @@ Internally that expands to:
|
|
|
133
133
|
|
|
134
134
|
1. Resolve provider and storage implementations.
|
|
135
135
|
2. Persist run config and ingest items into durable state.
|
|
136
|
-
3.
|
|
137
|
-
4.
|
|
138
|
-
5.
|
|
139
|
-
6.
|
|
140
|
-
7.
|
|
141
|
-
8.
|
|
142
|
-
9.
|
|
136
|
+
3. On resume, poll any already-active provider batches before ingesting or submitting new work.
|
|
137
|
+
4. Claim a bounded submission window from pending items.
|
|
138
|
+
5. Build or replay request JSONL rows.
|
|
139
|
+
6. Persist request artifacts before upload.
|
|
140
|
+
7. Submit one or more OpenAI batch files.
|
|
141
|
+
8. Poll active batches.
|
|
142
|
+
9. Download output/error files.
|
|
143
|
+
10. Parse terminal item results back into the state store.
|
|
144
|
+
|
|
145
|
+
When a caller uses `Run.wait()`, the runtime repeats that poll-and-submit pass until the run is terminal. A pass that changes durable work state, such as consuming completed batches or submitting more items, immediately triggers the next pass rather than sleeping for the configured poll interval.
|
|
143
146
|
|
|
144
147
|
## Execution Sequence
|
|
145
148
|
|
|
@@ -175,6 +178,9 @@ sequenceDiagram
|
|
|
175
178
|
Provider-->>BatchRunner: successes + errors
|
|
176
179
|
BatchRunner->>StateStore: mark_items_completed(successes)
|
|
177
180
|
BatchRunner->>StateStore: mark_items_failed(errors)
|
|
181
|
+
opt row-level insufficient quota
|
|
182
|
+
BatchRunner->>StateStore: record_batch_retry_failure(row_insufficient_quota)
|
|
183
|
+
end
|
|
178
184
|
else status in {failed, cancelled, expired}
|
|
179
185
|
BatchRunner->>Provider: download_file_content(output_file_id, error_file_id)
|
|
180
186
|
Provider-->>BatchRunner: output/error JSONL
|
|
@@ -182,6 +188,11 @@ sequenceDiagram
|
|
|
182
188
|
BatchRunner->>StateStore: record_batch_retry_failure()
|
|
183
189
|
BatchRunner->>StateStore: reset_batch_items_to_pending()
|
|
184
190
|
end
|
|
191
|
+
|
|
192
|
+
opt control-plane or batch-level OpenAI insufficient quota detected
|
|
193
|
+
BatchRunner->>StateStore: set_run_control_state(paused, control_reason)
|
|
194
|
+
BatchRunner-->>User: RunPausedError / run_auto_paused event
|
|
195
|
+
end
|
|
185
196
|
end
|
|
186
197
|
|
|
187
198
|
BatchRunner->>StateStore: claim_items_for_submission(limit)
|
|
@@ -217,7 +228,7 @@ stateDiagram-v2
|
|
|
217
228
|
QUEUED_LOCAL --> FAILED_PERMANENT : rejected pre-submission (e.g. token budget exceeded, max attempts)
|
|
218
229
|
QUEUED_LOCAL --> SUBMITTED : batch created and registered with provider
|
|
219
230
|
|
|
220
|
-
SUBMITTED --> COMPLETED : batch completed, result parsed OK
|
|
231
|
+
SUBMITTED --> COMPLETED : batch completed, result parsed OK — attempt consumed
|
|
221
232
|
SUBMITTED --> FAILED_RETRYABLE : batch error / item error — attempt count below max
|
|
222
233
|
SUBMITTED --> FAILED_PERMANENT : batch error / item error — attempt count reached max
|
|
223
234
|
|
|
@@ -243,7 +254,7 @@ stateDiagram-v2
|
|
|
243
254
|
|
|
244
255
|
state "Control State" as cs {
|
|
245
256
|
[*] --> RUNNING2 : run created
|
|
246
|
-
RUNNING2 --> PAUSED : pause_run()
|
|
257
|
+
RUNNING2 --> PAUSED : pause_run() or quota auto-pause
|
|
247
258
|
PAUSED --> RUNNING2 : resume_run() called
|
|
248
259
|
RUNNING2 --> CANCEL_REQUESTED : cancel_run() called
|
|
249
260
|
|
|
@@ -258,6 +269,15 @@ stateDiagram-v2
|
|
|
258
269
|
Detailed storage, resume, and artifact-retention semantics live in
|
|
259
270
|
[`STORAGE_AND_RUNS.md`](STORAGE_AND_RUNS.md).
|
|
260
271
|
|
|
272
|
+
Paused runs may include a durable `control_reason`. Manual pauses use
|
|
273
|
+
`"manual"`; control-plane or batch-level OpenAI quota/billing exhaustion uses
|
|
274
|
+
`"openai_insufficient_quota"` and preserves retryable work for later resume.
|
|
275
|
+
Row-level quota records inside completed batch output remain item-level
|
|
276
|
+
retryable failures and use retry backoff without pausing the run.
|
|
277
|
+
Quota auto-pause never replaces `cancel_requested`, and non-checkpointed
|
|
278
|
+
finite iterables continue materializing after auto-pause so unpersisted input
|
|
279
|
+
rows are not lost.
|
|
280
|
+
|
|
261
281
|
## Module boundaries
|
|
262
282
|
|
|
263
283
|
### `core/`
|
|
@@ -302,6 +322,8 @@ Owns execution behavior:
|
|
|
302
322
|
- `Run`
|
|
303
323
|
- Typer CLI entrypoint for operator workflows
|
|
304
324
|
- persisted run control state with `pause`, `resume`, and drain-style `cancel`
|
|
325
|
+
- automatic OpenAI control-plane/batch-level insufficient-quota pause with durable `control_reason`
|
|
326
|
+
- item-level insufficient-quota retries with backoff and no attempt consumption
|
|
305
327
|
- optional observer callback for provider lifecycle events
|
|
306
328
|
- token estimation and request chunking
|
|
307
329
|
- bounded pending-item claim windows before submission
|
|
@@ -319,6 +341,14 @@ Owns execution behavior:
|
|
|
319
341
|
|
|
320
342
|
This is where the durable lifecycle lives. It bridges the domain models, providers, storage, and artifact store.
|
|
321
343
|
|
|
344
|
+
The public facade is still `BatchRunner` plus `Run`, but the current internal layering under `runtime/` is intentionally split so execution concerns do not collapse back into one file:
|
|
345
|
+
|
|
346
|
+
- `context.py`: persisted config building, resume config comparison, output-model resolution, and provider-backed `RunContext` creation
|
|
347
|
+
- `ingestion.py`: checkpoint-aware item materialization, ingest checkpoint updates, and resume-aware ingestion flow
|
|
348
|
+
- `submission.py`: pending-item claiming, request replay/build, token-budget gating, request artifact persistence, and batch submission
|
|
349
|
+
- `polling.py`: refresh orchestration, active-batch polling, terminal batch consumption, and batch-failure reset/backoff handling
|
|
350
|
+
- `artifacts.py` and `results.py`: request replay helpers, raw artifact writes/deletes, public result mapping, and JSONL result serialization
|
|
351
|
+
|
|
322
352
|
### `sources/`
|
|
323
353
|
|
|
324
354
|
Owns streaming input adapters:
|
|
@@ -347,6 +377,7 @@ The storage layer persists:
|
|
|
347
377
|
|
|
348
378
|
- run config
|
|
349
379
|
- run control state
|
|
380
|
+
- run control reason
|
|
350
381
|
- item state and attempts
|
|
351
382
|
- active batch metadata
|
|
352
383
|
- ingest checkpoints
|
|
@@ -55,6 +55,7 @@ Before request artifacts exist, built-in deterministic sources can resume ingest
|
|
|
55
55
|
That currently includes CSV, JSONL, Parquet, and `CompositeItemSource` for ordered composition of checkpointed sources.
|
|
56
56
|
When `CompositeItemSource` is used, child-source row IDs are auto-namespaced into run-unique `item_id` values, while the original row ID is preserved in lineage metadata.
|
|
57
57
|
Custom non-file sources must implement a durable checkpoint contract explicitly; arbitrary iterables and live DB cursors are still `TBD`.
|
|
58
|
+
Before resumed ingestion continues, active OpenAI batches are polled once. If that poll consumes terminal batches or records enqueue/backoff failures, those state changes are applied before any new prompt rendering, request replay, or submission happens.
|
|
58
59
|
|
|
59
60
|
## Raw output retention
|
|
60
61
|
|
|
@@ -99,6 +100,7 @@ For structured outputs:
|
|
|
99
100
|
- the result is validated through the declared Pydantic model
|
|
100
101
|
|
|
101
102
|
Validation failures consume item attempts because they represent item-level response failure, not a transient provider control-plane problem.
|
|
103
|
+
Successful submitted responses also consume one item attempt. A successful first provider response is exported with `attempt_count == 1`; a counted retryable item failure followed by success is exported with the total consumed attempts.
|
|
102
104
|
|
|
103
105
|
## Token estimation
|
|
104
106
|
|
|
@@ -125,6 +127,7 @@ From those settings, `batchor` derives:
|
|
|
125
127
|
- an effective per-batch token limit
|
|
126
128
|
|
|
127
129
|
Submission is constrained by both token budget and generic chunking rules such as max request count and max request file size.
|
|
130
|
+
During `Run.wait()`, each drain cycle polls active batches before submitting new work, so capacity freed by completed batches is visible before the next submit attempt. If that cycle makes durable progress, the wait loop immediately runs another cycle instead of sleeping.
|
|
128
131
|
|
|
129
132
|
## Batch splitting
|
|
130
133
|
|
|
@@ -137,6 +140,7 @@ Splitting considers:
|
|
|
137
140
|
- estimated request tokens
|
|
138
141
|
|
|
139
142
|
Submission claims only a bounded pending-item window per refresh. This is intentional: large backlogs should not pay full prompt-build and token-estimation cost long before provider capacity exists to send them.
|
|
143
|
+
The default claim window is still large enough to prepare multiple provider batches per cycle, so runs with high remote enqueue headroom can keep filling capacity without waiting for one local batch at a time.
|
|
140
144
|
|
|
141
145
|
If a single request exceeds the allowed OpenAI token limit by itself, that item is marked as a permanent item failure instead of aborting the whole run.
|
|
142
146
|
|
|
@@ -150,16 +154,23 @@ Control-plane failures:
|
|
|
150
154
|
- retryable control-plane failures do not consume item attempts
|
|
151
155
|
- batch submit failures can trigger batch-level backoff
|
|
152
156
|
- transient poll failures do not stall unrelated submissions when other capacity remains
|
|
157
|
+
- resume honors existing batch backoff before materializing more source rows
|
|
158
|
+
- OpenAI 429 insufficient-quota or billing exhaustion during upload/create/polling auto-pauses the run with `control_reason="openai_insufficient_quota"` instead of continuing through the backlog
|
|
159
|
+
- batch-level terminal insufficient-quota failures also auto-pause after submitted rows are reset to pending
|
|
153
160
|
|
|
154
161
|
Item-level failures:
|
|
155
162
|
|
|
163
|
+
- successful submitted responses consume attempts
|
|
156
164
|
- structured-output parse failures consume attempts
|
|
157
165
|
- validation failures consume attempts
|
|
158
166
|
- oversized requests become permanent item failures
|
|
167
|
+
- item-level insufficient-quota records inside completed batch output are retryable without consuming attempts
|
|
168
|
+
- row-level insufficient-quota records emit `openai_insufficient_quota` failure telemetry and record retry backoff before resubmission instead of pausing the whole run
|
|
159
169
|
|
|
160
170
|
Cleanup behavior:
|
|
161
171
|
|
|
162
172
|
- if upload succeeds but batch creation fails, `batchor` makes a best-effort attempt to delete the uploaded OpenAI input file
|
|
173
|
+
- if upload or batch creation fails because OpenAI reports insufficient quota, local queued items are released back to pending before the run is paused
|
|
163
174
|
- if a process dies after local artifact persistence but before durable batch registration, fresh-process resume requeues those items and resubmits from persisted request artifacts
|
|
164
175
|
|
|
165
176
|
## Run control
|
|
@@ -169,6 +180,8 @@ Run control is local control-plane state, not a separate OpenAI provider feature
|
|
|
169
180
|
- `pause` stops new ingestion, new submission, and provider polling
|
|
170
181
|
- `resume` restarts those local activities
|
|
171
182
|
- `cancel` stops new ingestion/submission, continues polling already-submitted batches, and then marks any remaining local non-terminal items as `run_cancelled`
|
|
183
|
+
- automatic quota pause uses the same `paused` control state as manual pause for control-plane and batch-level quota failures, but records `control_reason="openai_insufficient_quota"` so operators can tell why `wait()` exited
|
|
184
|
+
- automatic quota pause is ignored once `cancel_requested` is set, because cancellation is not reversible
|
|
172
185
|
|
|
173
186
|
Provider-side remote batch cancellation is not implemented in v1.
|
|
174
187
|
|
|
@@ -18,6 +18,7 @@ The public handle exposes:
|
|
|
18
18
|
- `run_id`
|
|
19
19
|
- cached `status`
|
|
20
20
|
- cached `control_state`
|
|
21
|
+
- cached `control_reason`
|
|
21
22
|
- `is_finished`
|
|
22
23
|
- `refresh()`
|
|
23
24
|
- `wait()`
|
|
@@ -36,7 +37,7 @@ Important semantics:
|
|
|
36
37
|
|
|
37
38
|
- `status` is cached from the last summary read or refresh
|
|
38
39
|
- `refresh()` performs one poll-and-submit pass
|
|
39
|
-
- `wait()` repeatedly refreshes until the run is terminal
|
|
40
|
+
- `wait()` repeatedly refreshes until the run is terminal, and skips the poll-interval sleep after refreshes that complete items, fail items, or submit additional batches
|
|
40
41
|
- `results()` and artifact lifecycle operations are terminal-only
|
|
41
42
|
- terminal currently means either `completed` or `completed_with_failures`
|
|
42
43
|
- `read_terminal_results()` and `export_terminal_results()` are incremental APIs for already-terminal items and are safe to call before the whole run finishes
|
|
@@ -49,6 +50,7 @@ The control plane is the state store:
|
|
|
49
50
|
|
|
50
51
|
- run config
|
|
51
52
|
- run control state
|
|
53
|
+
- run control reason
|
|
52
54
|
- item rows and attempts
|
|
53
55
|
- active batch rows
|
|
54
56
|
- parsed outputs and failure records
|
|
@@ -80,6 +82,7 @@ Current storage responsibilities include:
|
|
|
80
82
|
|
|
81
83
|
- persisting public run config
|
|
82
84
|
- persisting run control state
|
|
85
|
+
- persisting run control reason
|
|
83
86
|
- persisting deterministic-source ingest checkpoints when available
|
|
84
87
|
- persisting item state and attempts
|
|
85
88
|
- persisting terminal result sequence metadata for incremental reads
|
|
@@ -111,6 +114,7 @@ For SQLite-backed runs, the default artifact root is a sibling `*_artifacts/` di
|
|
|
111
114
|
## Postgres behavior
|
|
112
115
|
|
|
113
116
|
Postgres exists as an opt-in control-plane backend for cases where SQLite is not enough, such as shared state across processes or hosts.
|
|
117
|
+
`PostgresStorage` uses psycopg v3. Plain `postgresql://` and `postgres://` DSNs are normalized to `postgresql+psycopg://` before engine creation; explicit SQLAlchemy driver schemes are preserved.
|
|
114
118
|
|
|
115
119
|
Important operational rule:
|
|
116
120
|
|
|
@@ -118,6 +122,16 @@ Important operational rule:
|
|
|
118
122
|
|
|
119
123
|
Postgres stores the control plane, not the large request/output files themselves.
|
|
120
124
|
|
|
125
|
+
## Attempt accounting
|
|
126
|
+
|
|
127
|
+
Item `attempt_count` records consumed provider attempts.
|
|
128
|
+
|
|
129
|
+
- Successful submitted completions increment the counter once.
|
|
130
|
+
- Counted item-level provider or parse failures increment the counter before retry/permanent-failure decisions.
|
|
131
|
+
- Local pre-submission rejections, control-plane quota pauses, batch-level reset-to-pending paths, and missing output rows marked with `count_attempt=False` do not increment it.
|
|
132
|
+
|
|
133
|
+
This keeps terminal exports aligned with audit expectations: a first-attempt success reports `attempt_count == 1`, and a retryable counted failure followed by success reports `attempt_count == 2`.
|
|
134
|
+
|
|
121
135
|
## File-source checkpoints
|
|
122
136
|
|
|
123
137
|
For deterministic built-in sources, storage persists a source checkpoint with:
|
|
@@ -163,6 +177,7 @@ Successful rehydration depends on:
|
|
|
163
177
|
- credentials being available when a refresh needs to talk to the provider
|
|
164
178
|
|
|
165
179
|
Fresh-process resume also requeues any `queued_local` items back to `pending` before submission resumes.
|
|
180
|
+
When `start(job, run_id=...)` resumes a run with active provider batches, it first performs a poll-only reconciliation pass. Terminal provider batches are consumed, failed batches update retry backoff, and any active backoff prevents new source materialization or submission until the backoff expires.
|
|
166
181
|
|
|
167
182
|
Resume compatibility intentionally ignores non-persisted secret fields such as provider API keys.
|
|
168
183
|
|
|
@@ -176,6 +191,8 @@ Built-in deterministic sources currently include:
|
|
|
176
191
|
- `JsonlItemSource`
|
|
177
192
|
- `ParquetItemSource`
|
|
178
193
|
|
|
194
|
+
For checkpointed ingestion, durable stores append materialized item chunks and advance the ingest checkpoint in one storage operation. Parquet and composite sources can also identify an end-of-source checkpoint from cheap metadata, so a resume after all rows were materialized but before the final completion flag was written can mark ingestion complete without re-reading the source data or re-rendering prompts.
|
|
195
|
+
|
|
179
196
|
Once an item has a durable request artifact pointer, `batchor` prunes large inline request-building fields from the control-plane store and relies on the artifact for later retries.
|
|
180
197
|
|
|
181
198
|
## Artifact lifecycle
|
|
@@ -222,7 +239,15 @@ Semantics:
|
|
|
222
239
|
- `resume` restarts those local activities from persisted state
|
|
223
240
|
- `cancel` stops new ingestion/submission, keeps polling active provider batches, and then permanently fails remaining local non-terminal items with `error_class="run_cancelled"`
|
|
224
241
|
|
|
225
|
-
`
|
|
242
|
+
Manual pauses record `control_reason="manual"`. OpenAI upload/create/polling and batch-level insufficient-quota pauses record `control_reason="openai_insufficient_quota"`, clear batch-control-plane backoff, and preserve affected items for later retry without consuming attempts.
|
|
243
|
+
|
|
244
|
+
Row-level insufficient-quota records inside completed batch outputs do not pause the run. Those items are stored as `failed_retryable` with `error_class="openai_insufficient_quota"`, do not consume attempts, and rely on retry backoff before they are submitted again.
|
|
245
|
+
|
|
246
|
+
Auto-pause must not overwrite `cancel_requested`. Cancellation is non-reversible, so quota errors observed while draining already-submitted batches leave the run in cancel flow.
|
|
247
|
+
|
|
248
|
+
For non-checkpointed finite iterables, ingestion continues materializing remaining items after an auto-pause so `resume()` cannot silently lose input rows that were not yet persisted. Checkpointed sources may stop on pause because their stored checkpoint can resume materialization later.
|
|
249
|
+
|
|
250
|
+
`wait()` fails fast on paused runs instead of sleeping indefinitely. The raised `RunPausedError` carries the same `control_reason` as the summary.
|
|
226
251
|
Provider-side remote cancellation is still `TBD`.
|
|
227
252
|
|
|
228
253
|
## Python API versus CLI
|
|
@@ -7,7 +7,8 @@ This document describes the current SQLite schema-versioning story for `batchor`
|
|
|
7
7
|
- SQLite remains the default durable backend
|
|
8
8
|
- the SQLite schema is additive and self-healing for supported columns/tables
|
|
9
9
|
- storage metadata now persists a `schema_version`
|
|
10
|
-
- the current published schema version is `
|
|
10
|
+
- the current published schema version is `4`
|
|
11
|
+
- schema version `4` adds the nullable `runs.control_reason` column for durable pause reasons
|
|
11
12
|
|
|
12
13
|
## Compatibility Rules
|
|
13
14
|
|
|
@@ -303,6 +303,8 @@ runner = BatchRunner(
|
|
|
303
303
|
)
|
|
304
304
|
```
|
|
305
305
|
|
|
306
|
+
`PostgresStorage` uses psycopg v3. Plain `postgresql://` and `postgres://` DSNs are accepted and normalized to `postgresql+psycopg://`; explicit SQLAlchemy driver schemes are left unchanged.
|
|
307
|
+
|
|
306
308
|
## Artifacts, export, prune, and retention
|
|
307
309
|
|
|
308
310
|
`batchor` stores request artifacts for replay and raw output artifacts for audit/export.
|
|
@@ -31,6 +31,7 @@ uv run pytest -q
|
|
|
31
31
|
This path uses the default pytest configuration, which:
|
|
32
32
|
|
|
33
33
|
- runs in parallel
|
|
34
|
+
- collects this package's `tests/` directory by default, so local reference checkouts are not treated as part of the smoke suite
|
|
34
35
|
- enforces strict pytest config/marker handling
|
|
35
36
|
- enforces the `85%` coverage gate
|
|
36
37
|
|
|
@@ -77,15 +78,21 @@ Expected:
|
|
|
77
78
|
- composite deterministic sources can namespace duplicate row IDs across explicit inputs and resume across source boundaries
|
|
78
79
|
- Parquet source adapters can resume from opaque checkpoints and project only required columns
|
|
79
80
|
- retry/resume from persisted request artifacts still works for SQLite-backed runs
|
|
81
|
+
- resumed runs reconcile existing active batches and persisted backoff before materializing more source rows
|
|
80
82
|
- transient batch-poll failures do not block unrelated pending submissions from being sent when capacity remains
|
|
81
83
|
- paused runs stop polling/submission until resumed
|
|
84
|
+
- OpenAI control-plane and batch-level insufficient-quota provider failures auto-pause with a durable `control_reason` and do not consume item attempts
|
|
85
|
+
- OpenAI row-level insufficient-quota records in completed batch output remain retryable, do not consume attempts, and back off without pausing the run
|
|
86
|
+
- quota auto-pause preserves `cancel_requested` and does not strand non-checkpointed input rows during initial ingestion
|
|
82
87
|
- cancelled runs drain already-submitted work and mark remaining local items as `run_cancelled`
|
|
83
88
|
- incremental terminal-result reads/exports remain sequence-based and idempotent across repeated calls
|
|
84
89
|
- raw output/error artifacts can be exported and require export before raw pruning
|
|
85
90
|
- raw output/error artifact persistence can be disabled without breaking parsed terminal results or request-artifact replay
|
|
86
91
|
- terminal runs, including `completed_with_failures`, can prune request artifacts without losing persisted results
|
|
87
92
|
- shared storage-contract behavior remains aligned across SQLite and opt-in Postgres
|
|
93
|
+
- completed submitted items report consumed attempts consistently across storage backends
|
|
88
94
|
- OpenAI request splitting and enqueue-limit logic still behave as expected
|
|
95
|
+
- wait-mode refresh cycles keep draining immediately after poll/submission progress, without introducing idle poll sleeps while more local work can be sent
|
|
89
96
|
- structured-output parsing remains stable
|
|
90
97
|
|
|
91
98
|
Notes:
|
|
@@ -127,19 +134,25 @@ This is not part of default CI. Prefer fake-provider tests for automated coverag
|
|
|
127
134
|
Manual only. Recommended local flow:
|
|
128
135
|
|
|
129
136
|
```bash
|
|
130
|
-
export OPENAI_API_KEY=sk-...
|
|
131
137
|
export BATCHOR_RUN_LIVE_TESTS=1
|
|
132
138
|
export BATCHOR_LIVE_OPENAI_MODEL=gpt-5-nano
|
|
133
139
|
uv run pytest tests/integration/test_batchor_live_openai.py --no-cov -q
|
|
134
140
|
```
|
|
135
141
|
|
|
142
|
+
Minimum single-item live smoke:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
export BATCHOR_RUN_LIVE_TESTS=1
|
|
146
|
+
uv run pytest tests/integration/test_batchor_live_openai.py -k text_job_smoke --no-cov -q
|
|
147
|
+
```
|
|
148
|
+
|
|
136
149
|
Behavior:
|
|
137
150
|
|
|
138
151
|
- runs a single-item text smoke and a two-CSV composition smoke against the real OpenAI Batch API
|
|
139
152
|
- uses SQLite durability and the normal `BatchRunner` flow
|
|
140
153
|
- defaults to `gpt-5-nano` unless `BATCHOR_LIVE_OPENAI_MODEL` is set
|
|
141
154
|
- sends no reasoning field unless `BATCHOR_LIVE_OPENAI_REASONING_EFFORT` is set
|
|
142
|
-
- loads `.env` when present through the test harness for local use
|
|
155
|
+
- loads `.env` when present through the test harness for local use, so `OPENAI_API_KEY` may come from the shell or `.env`
|
|
143
156
|
- is skipped unless `BATCHOR_RUN_LIVE_TESTS=1`
|
|
144
157
|
- requires an OpenAI account with Batch API access and available billing quota
|
|
145
158
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "batchor"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.3"
|
|
8
8
|
description = "Structured-first OpenAI Batch runner with typed Pydantic results."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -29,6 +29,7 @@ classifiers = [
|
|
|
29
29
|
"Programming Language :: Python :: 3",
|
|
30
30
|
"Programming Language :: Python :: 3.12",
|
|
31
31
|
"Programming Language :: Python :: 3.13",
|
|
32
|
+
"Programming Language :: Python :: 3.14",
|
|
32
33
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
33
34
|
]
|
|
34
35
|
|
|
@@ -60,6 +61,7 @@ packages = ["src/batchor"]
|
|
|
60
61
|
[tool.pytest.ini_options]
|
|
61
62
|
addopts = "-q -n auto --strict-config --strict-markers --cov=src --cov-report=term-missing --cov-fail-under=85"
|
|
62
63
|
pythonpath = ["src"]
|
|
64
|
+
testpaths = ["tests"]
|
|
63
65
|
markers = [
|
|
64
66
|
"unit: fast isolated tests",
|
|
65
67
|
"integration: fake-provider and multi-component tests",
|