#!/usr/bin/env python3 """Message Batches lifecycle with the official Python SDK. STATUS: LIVE_VERIFIED 2026-09-18 (the same HTTP sequence was executed through scripts/live.py; this SDK variant mirrors it 1:1 — client.messages.batches.create/retrieve/list/results/cancel/delete). Model: claude-haiku-4-5-20251001, max_tokens 8, 2 requests (~$0.0001 at 50 % batch pricing). Run: .venv/bin/python examples/anthropic/batch/batch_lifecycle.py """ from __future__ import annotations import sys, time from pathlib import Path ROOT = Path(__file__).resolve().parents[3] sys.path.insert(0, str(ROOT)) from scripts.live import load_env, log_request # noqa: E402 (loads .env; never prints keys) load_env() import anthropic # noqa: E402 MODEL = "claude-haiku-4-5-20251001" client = anthropic.Anthropic() def msg(custom_id: str, model: str) -> dict: return {"custom_id": custom_id, "params": {"model": model, "max_tokens": 8, "messages": [{"role": "user", "content": "Reply with OK."}]}} # 1. create (an invalid model is accepted at create time and shows up as an `errored` result) batch = client.messages.batches.create(requests=[msg("ok-1", MODEL), msg("bad-model-1", "claude-does-not-exist-1")]) log_request("anthropic", "POST", "/v1/messages/batches", 200, 0.0001, "sdk example create") print("created", batch.id, batch.processing_status, batch.request_counts) # 2. retrieve / list print("retrieve", client.messages.batches.retrieve(batch.id).processing_status) page = client.messages.batches.list(limit=1) print("list limit=1 ->", [b.id for b in page.data], "has_more", page.has_more) # 3. poll until ended (every 20 s, ≤ 5 min). Observed: ~100 s for 2 tiny requests. t0 = time.time() while batch.processing_status != "ended" and time.time() - t0 < 300: time.sleep(20) batch = client.messages.batches.retrieve(batch.id) print(f" {time.time() - t0:5.0f}s {batch.processing_status} {batch.request_counts}") log_request("anthropic", "GET", "/v1/messages/batches/{id}", 200, 0, f"sdk example polled {time.time() - t0:.0f}s") # 4. results — streaming JSONL decoder; unordered, keyed by custom_id for entry in client.messages.batches.results(batch.id): r = entry.result if r.type == "succeeded": print(entry.custom_id, "succeeded:", r.message.content[0].text, r.message.usage.service_tier) elif r.type == "errored": print(entry.custom_id, "errored:", r.error.error.type, r.error.error.message) else: # canceled | expired print(entry.custom_id, r.type) log_request("anthropic", "GET", "/v1/messages/batches/{id}/results", 200, 0, "sdk example results") # 5. delete (only once ended) print("delete", client.messages.batches.delete(batch.id).type) log_request("anthropic", "DELETE", "/v1/messages/batches/{id}", 200, 0, "sdk example delete") # 6. cancel path b2 = client.messages.batches.create(requests=[msg("cancel-1", MODEL)]) b2 = client.messages.batches.cancel(b2.id) print("cancel ->", b2.processing_status, "cancel_initiated_at", b2.cancel_initiated_at) t0 = time.time() while b2.processing_status != "ended" and time.time() - t0 < 240: time.sleep(15) b2 = client.messages.batches.retrieve(b2.id) print("after cancel:", b2.request_counts) print("delete #2", client.messages.batches.delete(b2.id).type) log_request("anthropic", "POST", "/v1/messages/batches/{id}/cancel", 200, 0.0001, "sdk example cancel path + delete")