Skip to content

Add streaming document download (stream_document_content) #30

Description

@baraline

Gap

download_document_content (_async/clients/api/management/_document.py:215) returns response.content
after a non-streaming dispatch through self._session.request(...). httpx's send defaults to
stream=False and then awaits response.aread(), so the whole body is materialised in memory. No public
surface exposes streaming or the session, and no workaround is documented.

Proposed API

async def stream_document_content(
    self, document_id: GlpiId, *, chunk_size: int = 65536
) -> AsyncIterator[bytes]: ...

Codegen cost: one token

Verified by running the real unasync over a streaming probe:

async with     -> with          FREE (the bare `async` token is stripped)
async for      -> for           FREE
AsyncIterator  -> Iterator      FREE (unasync's built-in table)
aiter_bytes    -> aiter_bytes   NOT REWRITTEN   <-- the only rule needed

Three coordinated edits:

  1. "aiter_bytes": "iter_bytes" in TOKEN_REPLACEMENTS (unasync_build.py:88-99)
  2. "aiter_bytes" in _INTENTIONAL_RENAMES (testing/tests/test_unasync_codegen.py:44-55) in the same
    commit
    -- exactly the pattern aclose/aread already follow. Without it the guard reports
    ('new.py', 5, 'aiter_bytes').
  3. A test-harness change that is easy to miss: testing/tests/test_method_invocation.py:169 installs
    its stub only at client._session.request, and httpx.Client.stream does not route through
    Client.request. A public stream_document_content would fail
    test_every_public_method_reaches_the_transport on both surfaces and attempt a real socket connect to
    https://glpi.example.test
    . _install_stub needs a .stream stub -- hand-written for each surface
    (a @contextmanager for sync, an @asynccontextmanager for async), since that file is not
    unasync-generated.

Two further gotchas

  • ensure_response_status reads response.text (_http.py:281,284), which raises ResponseNotRead on an
    unread stream. Call aread()/read() first -- aread is already a substitution key.
  • Do not decorate the generator with @retry: per
    docs/superpowers/specs/2026-07-15-real-async-io-design.md:345-346, tenacity silently degrades to the
    sync path on an async generator.

Scope note

aiter_text / aiter_lines / aiter_raw are equally un-rewritten. Only aiter_bytes is needed for this
API; anyone generalising to text or line streaming needs the rest.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions