Process multiple documents¶
read_many() coordinates a bounded number of document reads while preserving
one independent receipt per input. A failed document does not discard completed
or partial results from other documents.
from decimal import Decimal
import docgate
batch = docgate.read_many(
["resume-1.pdf", "resume-2.pdf", "resume-3.pdf"],
providers=[ocr, vision],
max_concurrency=8,
per_document_budget=Decimal("0.02"),
total_budget=Decimal("10.00"),
)
for outcome in batch.outcomes:
print(outcome.index, outcome.status)
print(batch.receipt)
Isolation and ordering¶
Outcomes always use zero-based input order, regardless of completion order.
Each DocumentOutcome is complete, partial, or failed. Failed outcomes
contain only the exception type, not exception text that might reveal a path or
customer identifier.
BatchResult.to_dict() excludes document text by default. Pass
include_text=True only when the receiving system intentionally needs content.
Bounded input consumption¶
read_many() accepts any iterable and keeps at most max_concurrency documents
in flight. It does not first convert the source iterable into a list. The
returned result necessarily retains completed outcomes; a streaming async API
is planned for jobs that should persist and release each outcome immediately.
Provider instances are shared across workers. Built-in HTTP adapters are
stateless. A custom CallableProvider must be thread-safe when concurrency is
greater than one.
Budgets¶
per_document_budget is enforced independently by each document. A
total_budget uses atomic reservations before provider calls so concurrent
workers cannot collectively exceed the estimated ceiling. Cache and text-layer
hits reserve nothing.
Concurrent scheduling can affect which document receives the final available
reservation. Outcome ordering remains stable, but callers requiring strict
input-order budget allocation should use max_concurrency=1.
The aggregate value is estimated spend, not a provider invoice. See Budgets, pricing, and cache.
Metadata-only progress¶
def progress(event: docgate.BatchProgressEvent) -> None:
print(event.index, event.event, event.status)
batch = docgate.read_many(sources, on_progress=progress)
Events contain only a zero-based index, event name, and status. They never carry the source, filename, path, bytes, extracted text, or credentials. Callbacks may run concurrently and should be thread-safe.