fastapi-streams 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. fastapi_streams-0.0.1/.gitattributes +1 -0
  2. fastapi_streams-0.0.1/.github/workflows/ci.yml +50 -0
  3. fastapi_streams-0.0.1/.github/workflows/compatibility.yml +47 -0
  4. fastapi_streams-0.0.1/.gitignore +14 -0
  5. fastapi_streams-0.0.1/.pre-commit-config.yaml +40 -0
  6. fastapi_streams-0.0.1/AGENTS.md +11 -0
  7. fastapi_streams-0.0.1/LICENSE +21 -0
  8. fastapi_streams-0.0.1/PKG-INFO +238 -0
  9. fastapi_streams-0.0.1/README.md +220 -0
  10. fastapi_streams-0.0.1/docs/README.md +27 -0
  11. fastapi_streams-0.0.1/docs/architecture.md +89 -0
  12. fastapi_streams-0.0.1/docs/clients.md +129 -0
  13. fastapi_streams-0.0.1/docs/codegen.md +80 -0
  14. fastapi_streams-0.0.1/docs/development.md +112 -0
  15. fastapi_streams-0.0.1/docs/quickstart.md +94 -0
  16. fastapi_streams-0.0.1/examples/__init__.py +0 -0
  17. fastapi_streams-0.0.1/examples/agent_runs/__init__.py +83 -0
  18. fastapi_streams-0.0.1/examples/agent_runs/consume.py +26 -0
  19. fastapi_streams-0.0.1/examples/agent_runs/consume.ts +27 -0
  20. fastapi_streams-0.0.1/examples/agent_runs/generated/.fastapi-streams.json +11 -0
  21. fastapi_streams-0.0.1/examples/agent_runs/generated/agent-runs/api.ts +49 -0
  22. fastapi_streams-0.0.1/examples/agent_runs/generated/agent-runs/index.ts +2 -0
  23. fastapi_streams-0.0.1/examples/agent_runs/generated/client.ts +11 -0
  24. fastapi_streams-0.0.1/examples/agent_runs/generated/index.ts +8 -0
  25. fastapi_streams-0.0.1/examples/agent_runs/generated/transport/errors.ts +26 -0
  26. fastapi_streams-0.0.1/examples/agent_runs/generated/transport/sse.ts +264 -0
  27. fastapi_streams-0.0.1/examples/agent_runs/generated/types.ts +43 -0
  28. fastapi_streams-0.0.1/examples/agent_runs/generated_python/.fastapi-streams.json +11 -0
  29. fastapi_streams-0.0.1/examples/agent_runs/generated_python/__init__.py +7 -0
  30. fastapi_streams-0.0.1/examples/agent_runs/generated_python/agent_runs/__init__.py +2 -0
  31. fastapi_streams-0.0.1/examples/agent_runs/generated_python/agent_runs/api.py +54 -0
  32. fastapi_streams-0.0.1/examples/agent_runs/generated_python/client.py +36 -0
  33. fastapi_streams-0.0.1/examples/agent_runs/generated_python/models.py +86 -0
  34. fastapi_streams-0.0.1/examples/agent_runs/generated_python/py.typed +0 -0
  35. fastapi_streams-0.0.1/examples/agent_runs/generated_python/runtime.py +217 -0
  36. fastapi_streams-0.0.1/fastapi_streams/__init__.py +4 -0
  37. fastapi_streams-0.0.1/fastapi_streams/codegen/__init__.py +0 -0
  38. fastapi_streams-0.0.1/fastapi_streams/codegen/cli.py +120 -0
  39. fastapi_streams-0.0.1/fastapi_streams/codegen/enums.py +172 -0
  40. fastapi_streams-0.0.1/fastapi_streams/codegen/ir.py +524 -0
  41. fastapi_streams-0.0.1/fastapi_streams/codegen/names.py +88 -0
  42. fastapi_streams-0.0.1/fastapi_streams/codegen/openapi.py +154 -0
  43. fastapi_streams-0.0.1/fastapi_streams/codegen/python.py +473 -0
  44. fastapi_streams-0.0.1/fastapi_streams/codegen/schemas/LICENSE +201 -0
  45. fastapi_streams-0.0.1/fastapi_streams/codegen/schemas/README.md +8 -0
  46. fastapi_streams-0.0.1/fastapi_streams/codegen/schemas/openapi-3.2.json +1729 -0
  47. fastapi_streams-0.0.1/fastapi_streams/codegen/templates/errors.ts +26 -0
  48. fastapi_streams-0.0.1/fastapi_streams/codegen/templates/python/api.py.j2 +52 -0
  49. fastapi_streams-0.0.1/fastapi_streams/codegen/templates/python/client.py.j2 +40 -0
  50. fastapi_streams-0.0.1/fastapi_streams/codegen/templates/python/runtime.py +217 -0
  51. fastapi_streams-0.0.1/fastapi_streams/codegen/templates/sse.ts +264 -0
  52. fastapi_streams-0.0.1/fastapi_streams/codegen/templates/typescript/api.ts.j2 +52 -0
  53. fastapi_streams-0.0.1/fastapi_streams/codegen/templates/typescript/client.ts.j2 +15 -0
  54. fastapi_streams-0.0.1/fastapi_streams/codegen/templating.py +19 -0
  55. fastapi_streams-0.0.1/fastapi_streams/codegen/typescript.py +220 -0
  56. fastapi_streams-0.0.1/fastapi_streams/codegen/writer.py +64 -0
  57. fastapi_streams-0.0.1/fastapi_streams/py.typed +0 -0
  58. fastapi_streams-0.0.1/fastapi_streams/router.py +133 -0
  59. fastapi_streams-0.0.1/package-lock.json +45 -0
  60. fastapi_streams-0.0.1/package.json +14 -0
  61. fastapi_streams-0.0.1/pyproject.toml +86 -0
  62. fastapi_streams-0.0.1/scripts/check_package.py +63 -0
  63. fastapi_streams-0.0.1/scripts/codegen.py +47 -0
  64. fastapi_streams-0.0.1/scripts/smoke_package.py +171 -0
  65. fastapi_streams-0.0.1/scripts/test_compat.py +67 -0
  66. fastapi_streams-0.0.1/tests/__init__.py +0 -0
  67. fastapi_streams-0.0.1/tests/codegen/helpers.py +72 -0
  68. fastapi_streams-0.0.1/tests/codegen/test_additional_properties.py +88 -0
  69. fastapi_streams-0.0.1/tests/codegen/test_codegen.py +128 -0
  70. fastapi_streams-0.0.1/tests/codegen/test_contract_boundaries.py +200 -0
  71. fastapi_streams-0.0.1/tests/codegen/test_discriminators.py +42 -0
  72. fastapi_streams-0.0.1/tests/codegen/test_field_names.py +40 -0
  73. fastapi_streams-0.0.1/tests/codegen/test_http_backends.py +242 -0
  74. fastapi_streams-0.0.1/tests/codegen/test_intersections.py +73 -0
  75. fastapi_streams-0.0.1/tests/codegen/test_nested_models.py +117 -0
  76. fastapi_streams-0.0.1/tests/codegen/test_python_client.py +408 -0
  77. fastapi_streams-0.0.1/tests/codegen/test_python_typing.py +49 -0
  78. fastapi_streams-0.0.1/tests/codegen/test_required_properties.py +46 -0
  79. fastapi_streams-0.0.1/tests/codegen/test_schema_matrix.py +362 -0
  80. fastapi_streams-0.0.1/tests/contracts/test_openapi.py +48 -0
  81. fastapi_streams-0.0.1/tests/fixtures/README.md +16 -0
  82. fastapi_streams-0.0.1/tests/fixtures/__init__.py +0 -0
  83. fastapi_streams-0.0.1/tests/fixtures/agent_runs.openapi.json +366 -0
  84. fastapi_streams-0.0.1/tests/fixtures/compat-client.ts +65 -0
  85. fastapi_streams-0.0.1/tests/fixtures/generated/.fastapi-streams.json +11 -0
  86. fastapi_streams-0.0.1/tests/fixtures/generated/client.ts +11 -0
  87. fastapi_streams-0.0.1/tests/fixtures/generated/fixtures/api.ts +31 -0
  88. fastapi_streams-0.0.1/tests/fixtures/generated/fixtures/index.ts +2 -0
  89. fastapi_streams-0.0.1/tests/fixtures/generated/index.ts +8 -0
  90. fastapi_streams-0.0.1/tests/fixtures/generated/transport/errors.ts +26 -0
  91. fastapi_streams-0.0.1/tests/fixtures/generated/transport/sse.ts +264 -0
  92. fastapi_streams-0.0.1/tests/fixtures/generated/types.ts +45 -0
  93. fastapi_streams-0.0.1/tests/fixtures/generated_python/.fastapi-streams.json +11 -0
  94. fastapi_streams-0.0.1/tests/fixtures/generated_python/__init__.py +8 -0
  95. fastapi_streams-0.0.1/tests/fixtures/generated_python/client.py +36 -0
  96. fastapi_streams-0.0.1/tests/fixtures/generated_python/fixtures/__init__.py +2 -0
  97. fastapi_streams-0.0.1/tests/fixtures/generated_python/fixtures/api.py +35 -0
  98. fastapi_streams-0.0.1/tests/fixtures/generated_python/models.py +97 -0
  99. fastapi_streams-0.0.1/tests/fixtures/generated_python/py.typed +0 -0
  100. fastapi_streams-0.0.1/tests/fixtures/generated_python/runtime.py +217 -0
  101. fastapi_streams-0.0.1/tests/fixtures/integration_app.py +77 -0
  102. fastapi_streams-0.0.1/tests/fixtures/models.openapi.json +275 -0
  103. fastapi_streams-0.0.1/tests/fixtures/models_app.py +53 -0
  104. fastapi_streams-0.0.1/tests/integration/test_end_to_end.py +295 -0
  105. fastapi_streams-0.0.1/tests/router/test_router.py +250 -0
  106. fastapi_streams-0.0.1/tests/typescript/live-client.ts +26 -0
  107. fastapi_streams-0.0.1/tests/typescript/models.test.ts +36 -0
  108. fastapi_streams-0.0.1/tests/typescript/narrowing.ts +31 -0
  109. fastapi_streams-0.0.1/tests/typescript/sse.test.ts +233 -0
  110. fastapi_streams-0.0.1/tests/typescript/tsconfig.json +16 -0
  111. fastapi_streams-0.0.1/tests/typing/client_usage.py +27 -0
  112. fastapi_streams-0.0.1/uv.lock +1218 -0
@@ -0,0 +1 @@
1
+ * text=auto eol=lf
@@ -0,0 +1,50 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ verify:
12
+ runs-on: ubuntu-latest
13
+ timeout-minutes: 15
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ python: ["3.12", "3.13", "3.14"]
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python }}
23
+ - uses: actions/setup-node@v4
24
+ with:
25
+ node-version: "24"
26
+ cache: npm
27
+ - uses: astral-sh/setup-uv@v6
28
+ with:
29
+ version: "0.9.2"
30
+ enable-cache: true
31
+ - run: uv sync --frozen --all-extras
32
+ - run: npm ci --ignore-scripts
33
+ - run: uv run ruff check .
34
+ - run: uv run ruff format --check .
35
+ - run: uv run pyright
36
+ - run: uv run pylint fastapi_streams
37
+ - run: uv run pre-commit run --all-files
38
+ - run: npm run typecheck
39
+ - run: npm run test:coverage
40
+ - run: uv run pytest -q --cov --cov-report=term-missing --cov-report=xml:coverage-python.xml
41
+ - uses: actions/upload-artifact@v4
42
+ if: always()
43
+ with:
44
+ name: coverage-python-${{ matrix.python }}
45
+ path: |
46
+ coverage-python.xml
47
+ coverage-typescript.lcov
48
+ - run: uv run python scripts/codegen.py --check
49
+ - run: uv build
50
+ - run: uv run python scripts/check_package.py
@@ -0,0 +1,47 @@
1
+ name: Dependency compatibility
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_dispatch:
7
+ schedule:
8
+ - cron: '17 7 * * 1'
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ compatibility:
15
+ name: ${{ matrix.os }} / Python ${{ matrix.python }} / ${{ matrix.dependencies }}
16
+ runs-on: ${{ matrix.os }}
17
+ timeout-minutes: 15
18
+ strategy:
19
+ fail-fast: false
20
+ matrix:
21
+ os: [ubuntu-latest, windows-latest]
22
+ python: ['3.12', '3.13', '3.14']
23
+ dependencies: [lowest, latest]
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ - uses: actions/setup-python@v5
27
+ with:
28
+ python-version: ${{ matrix.python }}
29
+ - uses: actions/setup-node@v4
30
+ with:
31
+ node-version: '24'
32
+ cache: npm
33
+ - uses: astral-sh/setup-uv@v6
34
+ with:
35
+ version: '0.9.2'
36
+ enable-cache: true
37
+ - run: npm ci --ignore-scripts
38
+ - name: Resolve dependencies and run native FastAPI and client integration tests
39
+ run: python scripts/test_compat.py --python ${{ matrix.python }} --dependencies ${{ matrix.dependencies }}
40
+ - uses: actions/upload-artifact@v4
41
+ if: always()
42
+ with:
43
+ name: compatibility-${{ matrix.os }}-${{ matrix.python }}-${{ matrix.dependencies }}
44
+ include-hidden-files: true
45
+ path: |
46
+ .pytest_cache/dependencies-*.txt
47
+ .pytest_cache/results-*.xml
@@ -0,0 +1,14 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .coverage
7
+ .coverage.*
8
+ coverage*.xml
9
+ coverage*.lcov
10
+ htmlcov/
11
+ dist/
12
+ node_modules/
13
+ tests/typescript/dist/
14
+ *.egg-info/
@@ -0,0 +1,40 @@
1
+ minimum_pre_commit_version: '4.0.0'
2
+ repos:
3
+ - repo: local
4
+ hooks:
5
+ - id: ruff-check
6
+ name: Ruff lint
7
+ entry: uv run --frozen ruff check
8
+ language: system
9
+ types: [python]
10
+ exclude: ^(examples/agent_runs/generated_python|tests/fixtures/generated_python)/
11
+ - id: ruff-format
12
+ name: Ruff format
13
+ entry: uv run --frozen ruff format --check
14
+ language: system
15
+ types: [python]
16
+ exclude: ^(examples/agent_runs/generated_python|tests/fixtures/generated_python)/
17
+ - id: pyright
18
+ name: Pyright
19
+ entry: uv run --frozen pyright
20
+ language: system
21
+ pass_filenames: false
22
+ types: [python]
23
+ - id: pylint
24
+ name: Pylint
25
+ entry: uv run --frozen pylint fastapi_streams
26
+ language: system
27
+ pass_filenames: false
28
+ files: ^fastapi_streams/.*\.py$
29
+ - id: typescript
30
+ name: TypeScript
31
+ entry: npm run typecheck
32
+ language: system
33
+ pass_filenames: false
34
+ files: \.(ts|json)$
35
+ - id: codegen
36
+ name: Generated contract and client drift
37
+ entry: uv run --frozen python scripts/codegen.py --check
38
+ language: system
39
+ pass_filenames: false
40
+ files: ^(fastapi_streams/|examples/|tests/fixtures/|scripts/codegen\.py|pyproject\.toml|uv\.lock)
@@ -0,0 +1,11 @@
1
+ # Repository guidance
2
+
3
+ ## Python module layout
4
+
5
+ In new or edited Python source modules, put the public API (public classes and
6
+ functions) first, immediately after imports and module constants. Define private
7
+ implementation helpers below the public API. Keep the executable `__main__`
8
+ entrypoint at the end of the module.
9
+
10
+ Generated client files are owned by the generators; update their templates rather
11
+ than editing generated output by hand.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mathis Arends
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,238 @@
1
+ Metadata-Version: 2.5
2
+ Name: fastapi-streams
3
+ Version: 0.0.1
4
+ Summary: Typed FastAPI streams with generated TypeScript and Python clients.
5
+ Project-URL: Documentation, https://github.com/mathisarends/fastapi-streams/tree/main/docs
6
+ Project-URL: Source, https://github.com/mathisarends/fastapi-streams
7
+ Project-URL: Issues, https://github.com/mathisarends/fastapi-streams/issues
8
+ Author: Mathis Arends
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Requires-Python: >=3.12
12
+ Requires-Dist: fastapi<1.0,>=0.143.3
13
+ Requires-Dist: pydantic<3,>=2.11.2
14
+ Provides-Extra: codegen
15
+ Requires-Dist: jinja2<4,>=3.1; extra == 'codegen'
16
+ Requires-Dist: jsonschema<5,>=4.23; extra == 'codegen'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # fastapi-streams
20
+
21
+ [![CI](https://github.com/mathisarends/fastapi-streams/actions/workflows/ci.yml/badge.svg)](https://github.com/mathisarends/fastapi-streams/actions/workflows/ci.yml)
22
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue)](pyproject.toml)
23
+ [![MIT License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
24
+
25
+ **Decorate a FastAPI endpoint. Get typed streaming clients in TypeScript and Python.**
26
+
27
+ Define your events with Pydantic and add `@router.stream()`. `fastapi-streams`
28
+ turns that definition into an OpenAPI 3.2 stream contract and generated SDKs with
29
+ named event values, typed async iteration, and shared SSE transport code.
30
+
31
+ ```text
32
+ FastAPI decorator → OpenAPI 3.2 → shared HTTP IR → TypeScript + Python clients
33
+ ```
34
+
35
+ ## Why this exists
36
+
37
+ An endpoint labeled `text/event-stream` does not give consumers a typed SDK.
38
+ They still need to know which JSON events arrive, how to parse a fragmented
39
+ stream, and how to stop it cleanly.
40
+
41
+ OpenAPI 3.2 can describe each SSE event with `itemSchema` and its JSON payload
42
+ with `contentSchema`. `fastapi-streams` connects that contract to both clients,
43
+ so event models, endpoint parameters, and client types come from one definition.
44
+ A CI check catches drift between the Python app, the contract, and generated code.
45
+
46
+ ## 1. Decorate your FastAPI endpoint
47
+
48
+ Install from GitHub with the optional codegen extra:
49
+
50
+ ```sh
51
+ uv add 'fastapi-streams[codegen] @ git+https://github.com/mathisarends/fastapi-streams.git'
52
+ ```
53
+
54
+ Save this as `app.py`:
55
+
56
+ ```python
57
+ from collections.abc import AsyncIterator
58
+ from typing import Annotated, Literal
59
+
60
+ from fastapi import FastAPI
61
+ from pydantic import BaseModel, Field
62
+
63
+ from fastapi_streams import StreamRouter
64
+
65
+
66
+ class TokenDelta(BaseModel):
67
+ type: Literal["token.delta"]
68
+ text: str
69
+
70
+
71
+ class RunCompleted(BaseModel):
72
+ type: Literal["run.completed"]
73
+ run_id: str
74
+
75
+
76
+ type RunEvent = Annotated[TokenDelta | RunCompleted, Field(discriminator="type")]
77
+
78
+ router = StreamRouter(prefix="/runs", tags=["agentRuns"])
79
+
80
+
81
+ @router.stream("/{run_id}/events", operation_id="stream_run_events")
82
+ async def stream_run_events(run_id: str) -> AsyncIterator[RunEvent]:
83
+ yield TokenDelta(type="token.delta", text="Hello")
84
+ yield RunCompleted(type="run.completed", run_id=run_id)
85
+
86
+
87
+ app = FastAPI()
88
+ app.include_router(router, prefix="/v1")
89
+ ```
90
+
91
+ The result is `GET /v1/runs/{run_id}/events`. `StreamRouter` extends `APIRouter`:
92
+ normal `Depends`, security dependencies, validation, middleware, and nested
93
+ routers keep working. FastAPI handles SSE framing, serialization, and heartbeats.
94
+ Use `methods=["POST"]` for request-body-driven streams.
95
+
96
+ ## 2. Export once, generate both clients
97
+
98
+ ```sh
99
+ uv run fastapi-streams schema \
100
+ --app app:app --output openapi.json
101
+
102
+ uv run fastapi-streams generate \
103
+ --schema openapi.json --language typescript --output generated/typescript
104
+
105
+ uv run fastapi-streams generate \
106
+ --schema openapi.json --language python --output generated/python
107
+ ```
108
+
109
+ These commands import the app and read its contract; no running server is needed.
110
+ The exporter validates OpenAPI 3.2 and the event schemas. Both generators consume
111
+ one HTTP intermediate representation (IR), carrying operations, parameters, and
112
+ event types into the generated SDKs.
113
+
114
+ ## 3. Consume typed events
115
+
116
+ The `RunEvent` union produces `RunEventType`, with the same named values in both
117
+ languages. The discriminator narrows each event to its matching model.
118
+
119
+ **TypeScript — Fetch, `AsyncIterable<RunEvent>`, no runtime dependencies:**
120
+
121
+ ```typescript
122
+ import { createClient, RunEventType } from "./generated/typescript/index.js";
123
+
124
+ const client = createClient({ baseUrl: "http://localhost:8000" });
125
+
126
+ for await (const event of client.agentRuns.streamRunEvents({ runId: "demo" })) {
127
+ switch (event.type) {
128
+ case RunEventType.TOKEN_DELTA:
129
+ console.log(event.text); // TokenDelta
130
+ break;
131
+ case RunEventType.RUN_COMPLETED:
132
+ console.log(event.run_id); // RunCompleted
133
+ break;
134
+ default: {
135
+ const exhaustive: never = event;
136
+ throw new Error(`Unexpected event: ${exhaustive}`);
137
+ }
138
+ }
139
+ }
140
+ ```
141
+
142
+ **Python — HTTPX, Pydantic models, and `StrEnum` values:**
143
+
144
+ ```python
145
+ import asyncio
146
+ from typing import assert_never
147
+
148
+ from generated.python import Client, RunEventType
149
+
150
+
151
+ async def main() -> None:
152
+ async with Client("http://localhost:8000") as client:
153
+ async for event in client.agent_runs.stream_run_events(run_id="demo"):
154
+ match event.type:
155
+ case RunEventType.TOKEN_DELTA:
156
+ print(event.text)
157
+ case RunEventType.RUN_COMPLETED:
158
+ print(event.run_id)
159
+ case _:
160
+ assert_never(event)
161
+
162
+
163
+ asyncio.run(main())
164
+ ```
165
+
166
+ Install `httpx` and `pydantic` in a Python consumer (`uv add httpx pydantic`).
167
+ HTTPX remains the default; opt in to HTTPX2 at generation time with
168
+ `--python-http-client httpx2` and install `httpx2` instead. See
169
+ [Python HTTP library selection](docs/clients.md#python-http-library).
170
+ Both SDKs handle fragmented SSE frames, UTF-8 decoding, HTTP errors, and resource
171
+ cleanup. TypeScript supports `AbortSignal`; Python supports task cancellation and
172
+ explicit async-generator closure. See [client usage](docs/clients.md).
173
+
174
+ ## 4. Catch contract drift in CI
175
+
176
+ Commit the exported schema and generated clients. Then check the complete chain:
177
+
178
+ ```sh
179
+ uv run fastapi-streams check \
180
+ --app app:app --schema openapi.json \
181
+ --language typescript --output generated/typescript
182
+
183
+ uv run fastapi-streams check \
184
+ --app app:app --schema openapi.json \
185
+ --language python --output generated/python
186
+ ```
187
+
188
+ `check` fails if the app's contract changed or generated files are stale. It
189
+ writes nothing and needs no running server. Pair it with TypeScript's compiler
190
+ and Pyright to catch invalid parameters and event access in consumer code.
191
+
192
+ | Check | What it verifies |
193
+ | --- | --- |
194
+ | Contract validation | OpenAPI structure, JSON Schemas, and local references. |
195
+ | `fastapi-streams check` | App → committed contract → generated client consistency. |
196
+ | TypeScript / Pyright | Parameter types, event fields, and exhaustive narrowing. |
197
+
198
+ [Codegen and CI reference →](docs/codegen.md)
199
+
200
+ ## Run the complete example
201
+
202
+ ```sh
203
+ git clone https://github.com/mathisarends/fastapi-streams.git
204
+ cd fastapi-streams
205
+ uv sync --all-extras
206
+ npm ci
207
+ uv run uvicorn examples.agent_runs:app --reload
208
+ ```
209
+
210
+ In another terminal:
211
+
212
+ ```sh
213
+ # TypeScript
214
+ npx tsc -p tests/typescript/tsconfig.json
215
+ node tests/typescript/dist/examples/agent_runs/consume.js
216
+
217
+ # Python
218
+ uv run python -m examples.agent_runs.consume
219
+ ```
220
+
221
+ The example includes GET and authenticated POST streams, both generated SDKs,
222
+ and committed OpenAPI regression fixtures in `tests/fixtures/`. Verify all generated artifacts with
223
+ `uv run python scripts/codegen.py --check`.
224
+
225
+ ## Documentation
226
+
227
+ | Guide | Topics |
228
+ | --- | --- |
229
+ | [Quickstart](docs/quickstart.md) | Setup, event models, and FastAPI integration. |
230
+ | [Contracts and codegen](docs/codegen.md) | OpenAPI 3.2, both generators, and drift checks. |
231
+ | [Client usage](docs/clients.md) | Enums, authentication, cancellation, and errors. |
232
+ | [Architecture](docs/architecture.md) | Native SSE, shared IR, templates, and transports. |
233
+ | [Development](docs/development.md) | Tests, supported scope, and roadmap. |
234
+
235
+ v0.0.1 supports GET/POST JSON SSE streams. Ordinary REST endpoints stay in the
236
+ contract for your existing REST SDK. See the [supported scope](docs/development.md#supported-scope-and-roadmap).
237
+
238
+ Python 3.12+ · FastAPI 0.143.3+ · Pydantic v2 · [MIT](LICENSE)
@@ -0,0 +1,220 @@
1
+ # fastapi-streams
2
+
3
+ [![CI](https://github.com/mathisarends/fastapi-streams/actions/workflows/ci.yml/badge.svg)](https://github.com/mathisarends/fastapi-streams/actions/workflows/ci.yml)
4
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue)](pyproject.toml)
5
+ [![MIT License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
6
+
7
+ **Decorate a FastAPI endpoint. Get typed streaming clients in TypeScript and Python.**
8
+
9
+ Define your events with Pydantic and add `@router.stream()`. `fastapi-streams`
10
+ turns that definition into an OpenAPI 3.2 stream contract and generated SDKs with
11
+ named event values, typed async iteration, and shared SSE transport code.
12
+
13
+ ```text
14
+ FastAPI decorator → OpenAPI 3.2 → shared HTTP IR → TypeScript + Python clients
15
+ ```
16
+
17
+ ## Why this exists
18
+
19
+ An endpoint labeled `text/event-stream` does not give consumers a typed SDK.
20
+ They still need to know which JSON events arrive, how to parse a fragmented
21
+ stream, and how to stop it cleanly.
22
+
23
+ OpenAPI 3.2 can describe each SSE event with `itemSchema` and its JSON payload
24
+ with `contentSchema`. `fastapi-streams` connects that contract to both clients,
25
+ so event models, endpoint parameters, and client types come from one definition.
26
+ A CI check catches drift between the Python app, the contract, and generated code.
27
+
28
+ ## 1. Decorate your FastAPI endpoint
29
+
30
+ Install from GitHub with the optional codegen extra:
31
+
32
+ ```sh
33
+ uv add 'fastapi-streams[codegen] @ git+https://github.com/mathisarends/fastapi-streams.git'
34
+ ```
35
+
36
+ Save this as `app.py`:
37
+
38
+ ```python
39
+ from collections.abc import AsyncIterator
40
+ from typing import Annotated, Literal
41
+
42
+ from fastapi import FastAPI
43
+ from pydantic import BaseModel, Field
44
+
45
+ from fastapi_streams import StreamRouter
46
+
47
+
48
+ class TokenDelta(BaseModel):
49
+ type: Literal["token.delta"]
50
+ text: str
51
+
52
+
53
+ class RunCompleted(BaseModel):
54
+ type: Literal["run.completed"]
55
+ run_id: str
56
+
57
+
58
+ type RunEvent = Annotated[TokenDelta | RunCompleted, Field(discriminator="type")]
59
+
60
+ router = StreamRouter(prefix="/runs", tags=["agentRuns"])
61
+
62
+
63
+ @router.stream("/{run_id}/events", operation_id="stream_run_events")
64
+ async def stream_run_events(run_id: str) -> AsyncIterator[RunEvent]:
65
+ yield TokenDelta(type="token.delta", text="Hello")
66
+ yield RunCompleted(type="run.completed", run_id=run_id)
67
+
68
+
69
+ app = FastAPI()
70
+ app.include_router(router, prefix="/v1")
71
+ ```
72
+
73
+ The result is `GET /v1/runs/{run_id}/events`. `StreamRouter` extends `APIRouter`:
74
+ normal `Depends`, security dependencies, validation, middleware, and nested
75
+ routers keep working. FastAPI handles SSE framing, serialization, and heartbeats.
76
+ Use `methods=["POST"]` for request-body-driven streams.
77
+
78
+ ## 2. Export once, generate both clients
79
+
80
+ ```sh
81
+ uv run fastapi-streams schema \
82
+ --app app:app --output openapi.json
83
+
84
+ uv run fastapi-streams generate \
85
+ --schema openapi.json --language typescript --output generated/typescript
86
+
87
+ uv run fastapi-streams generate \
88
+ --schema openapi.json --language python --output generated/python
89
+ ```
90
+
91
+ These commands import the app and read its contract; no running server is needed.
92
+ The exporter validates OpenAPI 3.2 and the event schemas. Both generators consume
93
+ one HTTP intermediate representation (IR), carrying operations, parameters, and
94
+ event types into the generated SDKs.
95
+
96
+ ## 3. Consume typed events
97
+
98
+ The `RunEvent` union produces `RunEventType`, with the same named values in both
99
+ languages. The discriminator narrows each event to its matching model.
100
+
101
+ **TypeScript — Fetch, `AsyncIterable<RunEvent>`, no runtime dependencies:**
102
+
103
+ ```typescript
104
+ import { createClient, RunEventType } from "./generated/typescript/index.js";
105
+
106
+ const client = createClient({ baseUrl: "http://localhost:8000" });
107
+
108
+ for await (const event of client.agentRuns.streamRunEvents({ runId: "demo" })) {
109
+ switch (event.type) {
110
+ case RunEventType.TOKEN_DELTA:
111
+ console.log(event.text); // TokenDelta
112
+ break;
113
+ case RunEventType.RUN_COMPLETED:
114
+ console.log(event.run_id); // RunCompleted
115
+ break;
116
+ default: {
117
+ const exhaustive: never = event;
118
+ throw new Error(`Unexpected event: ${exhaustive}`);
119
+ }
120
+ }
121
+ }
122
+ ```
123
+
124
+ **Python — HTTPX, Pydantic models, and `StrEnum` values:**
125
+
126
+ ```python
127
+ import asyncio
128
+ from typing import assert_never
129
+
130
+ from generated.python import Client, RunEventType
131
+
132
+
133
+ async def main() -> None:
134
+ async with Client("http://localhost:8000") as client:
135
+ async for event in client.agent_runs.stream_run_events(run_id="demo"):
136
+ match event.type:
137
+ case RunEventType.TOKEN_DELTA:
138
+ print(event.text)
139
+ case RunEventType.RUN_COMPLETED:
140
+ print(event.run_id)
141
+ case _:
142
+ assert_never(event)
143
+
144
+
145
+ asyncio.run(main())
146
+ ```
147
+
148
+ Install `httpx` and `pydantic` in a Python consumer (`uv add httpx pydantic`).
149
+ HTTPX remains the default; opt in to HTTPX2 at generation time with
150
+ `--python-http-client httpx2` and install `httpx2` instead. See
151
+ [Python HTTP library selection](docs/clients.md#python-http-library).
152
+ Both SDKs handle fragmented SSE frames, UTF-8 decoding, HTTP errors, and resource
153
+ cleanup. TypeScript supports `AbortSignal`; Python supports task cancellation and
154
+ explicit async-generator closure. See [client usage](docs/clients.md).
155
+
156
+ ## 4. Catch contract drift in CI
157
+
158
+ Commit the exported schema and generated clients. Then check the complete chain:
159
+
160
+ ```sh
161
+ uv run fastapi-streams check \
162
+ --app app:app --schema openapi.json \
163
+ --language typescript --output generated/typescript
164
+
165
+ uv run fastapi-streams check \
166
+ --app app:app --schema openapi.json \
167
+ --language python --output generated/python
168
+ ```
169
+
170
+ `check` fails if the app's contract changed or generated files are stale. It
171
+ writes nothing and needs no running server. Pair it with TypeScript's compiler
172
+ and Pyright to catch invalid parameters and event access in consumer code.
173
+
174
+ | Check | What it verifies |
175
+ | --- | --- |
176
+ | Contract validation | OpenAPI structure, JSON Schemas, and local references. |
177
+ | `fastapi-streams check` | App → committed contract → generated client consistency. |
178
+ | TypeScript / Pyright | Parameter types, event fields, and exhaustive narrowing. |
179
+
180
+ [Codegen and CI reference →](docs/codegen.md)
181
+
182
+ ## Run the complete example
183
+
184
+ ```sh
185
+ git clone https://github.com/mathisarends/fastapi-streams.git
186
+ cd fastapi-streams
187
+ uv sync --all-extras
188
+ npm ci
189
+ uv run uvicorn examples.agent_runs:app --reload
190
+ ```
191
+
192
+ In another terminal:
193
+
194
+ ```sh
195
+ # TypeScript
196
+ npx tsc -p tests/typescript/tsconfig.json
197
+ node tests/typescript/dist/examples/agent_runs/consume.js
198
+
199
+ # Python
200
+ uv run python -m examples.agent_runs.consume
201
+ ```
202
+
203
+ The example includes GET and authenticated POST streams, both generated SDKs,
204
+ and committed OpenAPI regression fixtures in `tests/fixtures/`. Verify all generated artifacts with
205
+ `uv run python scripts/codegen.py --check`.
206
+
207
+ ## Documentation
208
+
209
+ | Guide | Topics |
210
+ | --- | --- |
211
+ | [Quickstart](docs/quickstart.md) | Setup, event models, and FastAPI integration. |
212
+ | [Contracts and codegen](docs/codegen.md) | OpenAPI 3.2, both generators, and drift checks. |
213
+ | [Client usage](docs/clients.md) | Enums, authentication, cancellation, and errors. |
214
+ | [Architecture](docs/architecture.md) | Native SSE, shared IR, templates, and transports. |
215
+ | [Development](docs/development.md) | Tests, supported scope, and roadmap. |
216
+
217
+ v0.0.1 supports GET/POST JSON SSE streams. Ordinary REST endpoints stay in the
218
+ contract for your existing REST SDK. See the [supported scope](docs/development.md#supported-scope-and-roadmap).
219
+
220
+ Python 3.12+ · FastAPI 0.143.3+ · Pydantic v2 · [MIT](LICENSE)
@@ -0,0 +1,27 @@
1
+ # Documentation
2
+
3
+ Define typed events in FastAPI, export their OpenAPI 3.2 contract, and generate
4
+ TypeScript Fetch and Python HTTPX clients from the same definitions.
5
+
6
+ | Guide | What you will find |
7
+ | --- | --- |
8
+ | [Quickstart](quickstart.md) | Run the complete example and define your first stream. |
9
+ | [Contracts and codegen](codegen.md) | Export OpenAPI, generate both clients, and detect drift in CI. |
10
+ | [Client usage](clients.md) | Named event values, typed iteration, authentication, cancellation, and errors. |
11
+ | [Development](development.md) | Local checks, test coverage, supported features, and roadmap. |
12
+ | [Architecture](architecture.md) | Server integration, shared IR, generation, and transport boundaries. |
13
+
14
+ Start with the [quickstart](quickstart.md), then use the
15
+ [codegen guide](codegen.md) to integrate the clients into your project.
16
+
17
+ Python 3.12+, FastAPI 0.143.3+, and Pydantic v2 are required. Code generation is
18
+ an optional extra: `fastapi-streams[codegen]`. No running server is needed to
19
+ export contracts or generate clients.
20
+
21
+ The [example application](../examples/agent_runs/__init__.py) includes GET and
22
+ authenticated POST streams. Its [TypeScript](../examples/agent_runs/consume.ts)
23
+ and [Python](../examples/agent_runs/consume.py) consumers demonstrate the complete
24
+ journey. Generated clients and the [contract fixture](../tests/fixtures/agent_runs.openapi.json)
25
+ are committed so you can inspect their output.
26
+
27
+ [Back to the project](../README.md)