Skip to content

feat(client): Add streaming batch (many) API to Python client#419

Draft
jan-auer wants to merge 1 commit intomainfrom
feat/python-many-streaming-api
Draft

feat(client): Add streaming batch (many) API to Python client#419
jan-auer wants to merge 1 commit intomainfrom
feat/python-many-streaming-api

Conversation

@jan-auer
Copy link
Copy Markdown
Member

@jan-auer jan-auer commented Apr 2, 2026

Adds session.many() to the Python client for executing multiple get/put/delete operations in batches, with a fully streaming request/response pipeline.

The batch protocol uses multipart/form-data with per-part custom headers (x-sn-batch-operation-kind, x-sn-batch-operation-key, etc.). No existing Python library supports both streaming encoding with arbitrary per-part headers and streaming decoding, so both are implemented in multipart.py.

Streaming pipeline (no buffering):

  • Request: _build_batch_parts (generator) → encode_multipart (yields bytes lazily) → urllib3 chunked transfer encoding
  • Response: response.stream(65536)iter_multipart_response (incremental boundary detection across arbitrary chunks) → _parse_batch_response (generator) → caller receives Iterator[ManyResponse]

Operation classification:

  • bytes Put bodies: compressed eagerly (required for size-based batching), then classified — ≤1 MB goes in a batch request, >1 MB goes individually via the standard PUT endpoint
  • IO[bytes] Put bodies: always sent individually to avoid eager reading
  • Get and Delete: always batched (zero body size)

Concurrency: ThreadPoolExecutor dispatches batch chunks and individual ops concurrently (default concurrency=3). Results arrive in completion order, matching the Rust client's buffer_unordered semantics. Sequential execution (concurrency=1) preserves input order.

Introduces `session.many()` for executing multiple get/put/delete
operations with automatic batching and streaming end-to-end — no
buffering of request or response bodies.

- New `many.py`: `Put`, `Get`, `Delete` operation types, `ManyResponse`
  result, and `execute_many()` orchestrator. Small puts are batched via
  multipart; puts over 1 MB or with `IO[bytes]` bodies go individually.
  Concurrent execution via `ThreadPoolExecutor` with configurable
  concurrency (default 3, matching Rust client semantics).
- New `multipart.py`: custom multipart encoder (lazy `Iterator[bytes]`,
  supports per-part custom headers and `IO[bytes]` bodies) and streaming
  decoder (`iter_multipart_response`) that parses parts incrementally
  across arbitrary-sized chunks without buffering the full response.
- `client.py`: exposes `Session.many()` and re-exports operation types
  and `ManyResponse` from the top-level package.
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 2, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • (client) Add streaming batch (many) API to Python client by jan-auer in #419

🤖 This preview updates automatically when you update the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant