Skip to main content

fastapi-streams

CI Python 3.12+ MIT License

Decorate a FastAPI endpoint. Get typed streaming clients in TypeScript and Python.

Define your events with Pydantic and add @router.stream(). fastapi-streams turns that definition into an OpenAPI 3.2 stream contract and generated SDKs with named event values, typed async iteration, and shared SSE transport code.

FastAPI decorator → OpenAPI 3.2 → shared HTTP IR → TypeScript + Python clients

Why this exists

An endpoint labeled text/event-stream does not give consumers a typed SDK. They still need to know which JSON events arrive, how to parse a fragmented stream, and how to stop it cleanly.

OpenAPI 3.2 can describe each SSE event with itemSchema and its JSON payload with contentSchema. fastapi-streams connects that contract to both clients, so event models, endpoint parameters, and client types come from one definition. A CI check catches drift between the Python app, the contract, and generated code.

1. Decorate your FastAPI endpoint

Install from GitHub with the optional codegen extra:

uv add 'fastapi-streams[codegen] @ git+https://github.com/mathisarends/fastapi-streams.git'

Save this as app.py:

from collections.abc import AsyncIterator
from typing import Annotated, Literal

from fastapi import FastAPI
from pydantic import BaseModel, Field

from fastapi_streams import StreamRouter


class TokenDelta(BaseModel):
    type: Literal["token.delta"]
    text: str


class RunCompleted(BaseModel):
    type: Literal["run.completed"]
    run_id: str


type RunEvent = Annotated[TokenDelta | RunCompleted, Field(discriminator="type")]

router = StreamRouter(prefix="/runs", tags=["agentRuns"])


@router.stream("/{run_id}/events", operation_id="stream_run_events")
async def stream_run_events(run_id: str) -> AsyncIterator[RunEvent]:
    yield TokenDelta(type="token.delta", text="Hello")
    yield RunCompleted(type="run.completed", run_id=run_id)


app = FastAPI()
app.include_router(router, prefix="/v1")

The result is GET /v1/runs/{run_id}/events. StreamRouter extends APIRouter: normal Depends, security dependencies, validation, middleware, and nested routers keep working. FastAPI handles SSE framing, serialization, and heartbeats. Use methods=["POST"] for request-body-driven streams.

2. Export once, generate both clients

uv run fastapi-streams schema \
  --app app:app --output openapi.json

uv run fastapi-streams generate \
  --schema openapi.json --language typescript --output generated/typescript

uv run fastapi-streams generate \
  --schema openapi.json --language python --output generated/python

These commands import the app and read its contract; no running server is needed. The exporter validates OpenAPI 3.2 and the event schemas. Both generators consume one HTTP intermediate representation (IR), carrying operations, parameters, and event types into the generated SDKs.

3. Consume typed events

The RunEvent union produces RunEventType, with the same named values in both languages. The discriminator narrows each event to its matching model.

TypeScript — Fetch, AsyncIterable<RunEvent>, no runtime dependencies:

import { createClient, RunEventType } from "./generated/typescript/index.js";

const client = createClient({ baseUrl: "http://localhost:8000" });

for await (const event of client.agentRuns.streamRunEvents({ runId: "demo" })) {
  switch (event.type) {
    case RunEventType.TOKEN_DELTA:
      console.log(event.text); // TokenDelta
      break;
    case RunEventType.RUN_COMPLETED:
      console.log(event.run_id); // RunCompleted
      break;
    default: {
      const exhaustive: never = event;
      throw new Error(`Unexpected event: ${exhaustive}`);
    }
  }
}

Python — HTTPX, Pydantic models, and StrEnum values:

import asyncio
from typing import assert_never

from generated.python import Client, RunEventType


async def main() -> None:
    async with Client("http://localhost:8000") as client:
        async for event in client.agent_runs.stream_run_events(run_id="demo"):
            match event.type:
                case RunEventType.TOKEN_DELTA:
                    print(event.text)
                case RunEventType.RUN_COMPLETED:
                    print(event.run_id)
                case _:
                    assert_never(event)


asyncio.run(main())

Install httpx and pydantic in a Python consumer (uv add httpx pydantic). HTTPX remains the default; opt in to HTTPX2 at generation time with --python-http-client httpx2 and install httpx2 instead. See Python HTTP library selection. Both SDKs handle fragmented SSE frames, UTF-8 decoding, HTTP errors, and resource cleanup. TypeScript supports AbortSignal; Python supports task cancellation and explicit async-generator closure. See client usage.

4. Catch contract drift in CI

Commit the exported schema and generated clients. Then check the complete chain:

uv run fastapi-streams check \
  --app app:app --schema openapi.json \
  --language typescript --output generated/typescript

uv run fastapi-streams check \
  --app app:app --schema openapi.json \
  --language python --output generated/python

check fails if the app's contract changed or generated files are stale. It writes nothing and needs no running server. Pair it with TypeScript's compiler and Pyright to catch invalid parameters and event access in consumer code.

Check What it verifies
Contract validation OpenAPI structure, JSON Schemas, and local references.
fastapi-streams check App → committed contract → generated client consistency.
TypeScript / Pyright Parameter types, event fields, and exhaustive narrowing.

Codegen and CI reference →

Run the complete example

git clone https://github.com/mathisarends/fastapi-streams.git
cd fastapi-streams
uv sync --all-extras
npm ci
uv run uvicorn examples.agent_runs:app --reload

In another terminal:

# TypeScript
npx tsc -p tests/typescript/tsconfig.json
node tests/typescript/dist/examples/agent_runs/consume.js

# Python
uv run python -m examples.agent_runs.consume

The example includes GET and authenticated POST streams, both generated SDKs, and committed OpenAPI regression fixtures in tests/fixtures/. Verify all generated artifacts with uv run python scripts/codegen.py --check.

Documentation

See the changelog and upgrade steps when updating the library and regenerating committed clients.

Guide Topics
Quickstart Setup, event models, and FastAPI integration.
Contracts and codegen OpenAPI 3.2, both generators, and drift checks.
Client usage Enums, authentication, cancellation, and errors.
Architecture Native SSE, shared IR, templates, and transports.
Development Tests, supported scope, and roadmap.
Releasing Tag-based releases and credential-free PyPI publishing.

v0.0.1 supports GET/POST JSON SSE streams. Ordinary REST endpoints stay in the contract for your existing REST SDK. See the supported scope.

Python 3.12+ · FastAPI 0.143.3+ · Pydantic v2 · MIT

Metadata

Release files for fastapi-streams 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fastapi-streams 0.1.0
File Size Uploaded
fastapi_streams-0.1.0.tar.gz 107.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fastapi-streams 0.1.0
File Interpreter ABI Platform
fastapi_streams-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 157.0 kB

Release files / fastapi_streams-0.1.0.tar.gz

Download URL fastapi_streams-0.1.0.tar.gz
Size 107.9 kB
Tags Source
SHA-256 checksum
How to use checksums
f85387703c1cf59ba5ffc72365c02b8130e344c10786ed15dc2b633034e0857a
BLAKE2b-256 checksum
How to use checksums
527179627d7ce56c754272f132fe75a4c972becdd183490aa05c4be70197c906
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release files / fastapi_streams-0.1.0-py3-none-any.whl

Download URL fastapi_streams-0.1.0-py3-none-any.whl
Size 49.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
de3d1229f99679b5090d8b5c3981ac4390555df13eae835e6ef99c8c00554fe0
BLAKE2b-256 checksum
How to use checksums
dae2c75e8cfb4782b146c96c339292ca348ed20bd02f201762f61f78cb26989a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page