Skip to content

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.